From 06c76ab2431e6c26b925a5aa12a16224e516a69f Mon Sep 17 00:00:00 2001 From: kurtshen Date: Sat, 17 Jul 2021 17:40:02 +0800 Subject: [PATCH 1/2] feat: support replaceWithoutPrefix --- src/Mentions.tsx | 98 +++++++++++++++++++++++++------------- src/util.ts | 37 ++++++++++---- tests/FullProcess.spec.jsx | 12 +++++ 3 files changed, 104 insertions(+), 43 deletions(-) diff --git a/src/Mentions.tsx b/src/Mentions.tsx index d9995ab..33c639a 100644 --- a/src/Mentions.tsx +++ b/src/Mentions.tsx @@ -2,22 +2,28 @@ import classNames from 'classnames'; import toArray from 'rc-util/lib/Children/toArray'; import KeyCode from 'rc-util/lib/KeyCode'; import * as React from 'react'; -import TextArea, { TextAreaProps } from 'rc-textarea'; +import type { TextAreaProps } from 'rc-textarea'; +import TextArea from 'rc-textarea'; import KeywordTrigger from './KeywordTrigger'; import { MentionsContextProvider } from './MentionsContext'; -import Option, { OptionProps } from './Option'; +import type { OptionProps } from './Option'; +import Option from './Option'; +import type { + Omit} from './util'; import { filterOption as defaultFilterOption, getBeforeSelectionText, getLastMeasureIndex, omit, - Omit, replaceWithMeasure, setInputSelection, validateSearch as defaultValidateSearch, } from './util'; -type BaseTextareaAttrs = Omit; +type BaseTextareaAttrs = Omit< + TextAreaProps, + 'prefix' | 'onChange' | 'onSelect' +>; export type Placement = 'top' | 'bottom'; export type Direction = 'ltr' | 'rtl'; @@ -36,6 +42,7 @@ export interface MentionsProps extends BaseTextareaAttrs { prefixCls?: string; value?: string; filterOption?: false | typeof defaultFilterOption; + replaceWithoutPrefix?: boolean; validateSearch?: typeof defaultValidateSearch; onChange?: (text: string) => void; onSelect?: (option: OptionProps, prefix: string) => void; @@ -72,7 +79,10 @@ class Mentions extends React.Component { rows: 1, }; - public static getDerivedStateFromProps(props: MentionsProps, prevState: MentionsState) { + public static getDerivedStateFromProps( + props: MentionsProps, + prevState: MentionsState, + ) { const newState: Partial = {}; if ('value' in props && props.value !== prevState.value) { @@ -115,12 +125,14 @@ class Mentions extends React.Component { } }; - public onChange: React.ChangeEventHandler = ({ target: { value } }) => { + public onChange: React.ChangeEventHandler = ({ + target: { value }, + }) => { this.triggerChange(value); }; // Check if hit the measure keyword - public onKeyDown: React.KeyboardEventHandler = (event) => { + public onKeyDown: React.KeyboardEventHandler = event => { const { which } = event; const { activeIndex, measuring } = this.state; const { onKeyDown: clientOnKeyDown } = this.props; @@ -170,16 +182,19 @@ class Mentions extends React.Component { * 2. Contains `space` * 3. ESC or select one */ - public onKeyUp: React.KeyboardEventHandler = (event) => { + public onKeyUp: React.KeyboardEventHandler = event => { const { key, which } = event; const { measureText: prevMeasureText, measuring } = this.state; - const { prefix = '', onKeyUp: clientOnKeyUp, onSearch, validateSearch } = this.props; + const { + prefix = '', + onKeyUp: clientOnKeyUp, + onSearch, + validateSearch, + } = this.props; const target = event.target as HTMLTextAreaElement; const selectionStartText = getBeforeSelectionText(target); - const { location: measureIndex, prefix: measurePrefix } = getLastMeasureIndex( - selectionStartText, - prefix, - ); + const { location: measureIndex, prefix: measurePrefix } = + getLastMeasureIndex(selectionStartText, prefix); // If the client implements an onKeyUp handler, call it if (clientOnKeyUp) { @@ -187,12 +202,17 @@ class Mentions extends React.Component { } // Skip if match the white key list - if ([KeyCode.ESC, KeyCode.UP, KeyCode.DOWN, KeyCode.ENTER].indexOf(which) !== -1) { + if ( + [KeyCode.ESC, KeyCode.UP, KeyCode.DOWN, KeyCode.ENTER].indexOf(which) !== + -1 + ) { return; } if (measureIndex !== -1) { - const measureText = selectionStartText.slice(measureIndex + measurePrefix.length); + const measureText = selectionStartText.slice( + measureIndex + measurePrefix.length, + ); const validateMeasure: boolean = validateSearch(measureText, this.props); const matchOption = !!this.getOptions(measureText).length; @@ -222,19 +242,20 @@ class Mentions extends React.Component { } }; - public onPressEnter: React.KeyboardEventHandler = (event) => { - const { measuring } = this.state; - const { onPressEnter } = this.props; - if (!measuring && onPressEnter) { - onPressEnter(event); - } - }; + public onPressEnter: React.KeyboardEventHandler = + event => { + const { measuring } = this.state; + const { onPressEnter } = this.props; + if (!measuring && onPressEnter) { + onPressEnter(event); + } + }; - public onInputFocus: React.FocusEventHandler = (event) => { + public onInputFocus: React.FocusEventHandler = event => { this.onFocus(event); }; - public onInputBlur: React.FocusEventHandler = (event) => { + public onInputBlur: React.FocusEventHandler = event => { this.onBlur(event); }; @@ -269,16 +290,20 @@ class Mentions extends React.Component { public selectOption = (option: OptionProps) => { const { value, measureLocation, measurePrefix } = this.state; - const { split, onSelect } = this.props; + const { split, onSelect, replaceWithoutPrefix } = this.props; const { value: mentionValue = '' } = option; - const { text, selectionLocation } = replaceWithMeasure(value, { - measureLocation, - targetText: mentionValue, - prefix: measurePrefix, - selectionStart: this.textarea.selectionStart, - split, - }); + const { text, selectionLocation } = replaceWithMeasure( + value, + { + measureLocation, + targetText: mentionValue, + prefix: measurePrefix, + selectionStart: this.textarea.selectionStart, + split, + }, + replaceWithoutPrefix, + ); this.triggerChange(text); this.stopMeasure(() => { // We need restore the selection position @@ -322,7 +347,11 @@ class Mentions extends React.Component { return list; }; - public startMeasure(measureText: string, measurePrefix: string, measureLocation: number) { + public startMeasure( + measureText: string, + measurePrefix: string, + measureLocation: number, + ) { this.setState({ measuring: true, measureText, @@ -352,7 +381,8 @@ class Mentions extends React.Component { } public render() { - const { value, measureLocation, measurePrefix, measuring, activeIndex } = this.state; + const { value, measureLocation, measurePrefix, measuring, activeIndex } = + this.state; const { prefixCls, placement, diff --git a/src/util.ts b/src/util.ts index 91ee2f3..e558cb4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,5 +1,5 @@ -import { MentionsProps } from './Mentions'; -import { OptionProps } from './Option'; +import type { MentionsProps } from './Mentions'; +import type { OptionProps } from './Option'; export type Omit = Pick>; @@ -34,7 +34,10 @@ interface MeasureIndex { /** * Find the last match prefix index */ -export function getLastMeasureIndex(text: string, prefix: string | string[] = ''): MeasureIndex { +export function getLastMeasureIndex( + text: string, + prefix: string | string[] = '', +): MeasureIndex { const prefixList: string[] = Array.isArray(prefix) ? prefix : [prefix]; return prefixList.reduce( (lastMatch: MeasureIndex, prefixStr): MeasureIndex => { @@ -90,13 +93,21 @@ function reduceText(text: string, targetText: string, split: string) { * targetText: light * => little @light test */ -export function replaceWithMeasure(text: string, measureConfig: MeasureConfig) { - const { measureLocation, prefix, targetText, selectionStart, split } = measureConfig; +export function replaceWithMeasure( + text: string, + measureConfig: MeasureConfig, + replaceWithoutPrefix, +) { + const { measureLocation, prefix, targetText, selectionStart, split } = + measureConfig; // Before text will append one space if have other text let beforeMeasureText = text.slice(0, measureLocation); if (beforeMeasureText[beforeMeasureText.length - split.length] === split) { - beforeMeasureText = beforeMeasureText.slice(0, beforeMeasureText.length - split.length); + beforeMeasureText = beforeMeasureText.slice( + 0, + beforeMeasureText.length - split.length, + ); } if (beforeMeasureText) { beforeMeasureText = `${beforeMeasureText}${split}`; @@ -112,7 +123,9 @@ export function replaceWithMeasure(text: string, measureConfig: MeasureConfig) { restText = restText.slice(split.length); } - const connectedStartText = `${beforeMeasureText}${prefix}${targetText}${split}`; + const connectedStartText = `${beforeMeasureText}${ + replaceWithoutPrefix ? '' : prefix + }${targetText}${split}`; return { text: `${connectedStartText}${restText}`, @@ -120,7 +133,10 @@ export function replaceWithMeasure(text: string, measureConfig: MeasureConfig) { }; } -export function setInputSelection(input: HTMLTextAreaElement, location: number) { +export function setInputSelection( + input: HTMLTextAreaElement, + location: number, +) { input.setSelectionRange(location, location); /** @@ -136,7 +152,10 @@ export function validateSearch(text: string, props: MentionsProps) { return !split || text.indexOf(split) === -1; } -export function filterOption(input: string, { value = '' }: OptionProps): boolean { +export function filterOption( + input: string, + { value = '' }: OptionProps, +): boolean { const lowerCase = input.toLowerCase(); return value.toLowerCase().indexOf(lowerCase) !== -1; } diff --git a/tests/FullProcess.spec.jsx b/tests/FullProcess.spec.jsx index 4c26cae..6ff17ec 100644 --- a/tests/FullProcess.spec.jsx +++ b/tests/FullProcess.spec.jsx @@ -158,4 +158,16 @@ describe('Full Process', () => { expect(wrapper.state().measuring).toBe(false); }); + + it('replace without prefix', () => { + const onChange = jest.fn(); + const wrapper = createMentions({ onChange, replaceWithoutPrefix: true }); + + simulateInput(wrapper, '@b'); + wrapper.find('textarea').simulate('keyDown', { + keyCode: KeyCode.ENTER, + }); + + expect(onChange).toBeCalledWith('bamboo '); + }); }); From 1363b255421a6edef8e1fbdb1364e32d7793d8db Mon Sep 17 00:00:00 2001 From: Shen Ke <626707841@qq.com> Date: Sun, 18 Jul 2021 23:47:12 +0800 Subject: [PATCH 2/2] chore: update util replaceWithMeasure type --- src/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.ts b/src/util.ts index e558cb4..64f0b18 100644 --- a/src/util.ts +++ b/src/util.ts @@ -96,7 +96,7 @@ function reduceText(text: string, targetText: string, split: string) { export function replaceWithMeasure( text: string, measureConfig: MeasureConfig, - replaceWithoutPrefix, + replaceWithoutPrefix?: boolean, ) { const { measureLocation, prefix, targetText, selectionStart, split } = measureConfig;