diff --git a/src/Mentions.tsx b/src/Mentions.tsx index 1f3ae35..6e5cfd2 100644 --- a/src/Mentions.tsx +++ b/src/Mentions.tsx @@ -451,6 +451,7 @@ const InternalMentions = forwardRef( * 1. Selection is out of range * 2. Contains `space` * 3. ESC or select one + * 4. The measured prefix is removed from the text */ const onInternalKeyUp: React.KeyboardEventHandler< HTMLTextAreaElement @@ -480,29 +481,47 @@ const InternalMentions = forwardRef( const validateMeasure: boolean = validateSearch(nextMeasureText, split); const matchOption = !!getOptions(nextMeasureText).length; - if (validateMeasure) { - // adding AltGraph also fort azert keyboard - if ( - key === nextMeasurePrefix || - key === 'Shift' || - which === KeyCode.ALT || - key === 'AltGraph' || - mergedMeasuring || - (nextMeasureText !== mergedMeasureText && matchOption) - ) { - startMeasure(nextMeasureText, nextMeasurePrefix, measureIndex); - } - } else if (mergedMeasuring) { - // Stop if measureText is invalidate + // adding AltGraph also fort azert keyboard + const isTypingPrefix = + key === nextMeasurePrefix || + key === 'Shift' || + which === KeyCode.ALT || + key === 'AltGraph'; + + // The measured prefix may be removed from the text (e.g. user deletes + // the `@` char just typed). Stop measuring instead of re-anchor to a + // previous prefix in the text which user is not typing at. + const measurePrefixExist = + isTypingPrefix || + !mergedMeasuring || + selectionStartText.slice( + mergedMeasureLocation, + mergedMeasureLocation + mergedMeasurePrefix.length, + ) === mergedMeasurePrefix; + + if (!measurePrefixExist) { stopMeasure(); - } + } else { + if (validateMeasure) { + if ( + isTypingPrefix || + mergedMeasuring || + (nextMeasureText !== mergedMeasureText && matchOption) + ) { + startMeasure(nextMeasureText, nextMeasurePrefix, measureIndex); + } + } else if (mergedMeasuring) { + // Stop if measureText is invalidate + stopMeasure(); + } - /** - * We will trigger `onSearch` to developer since they may use for async update. - * If met `space` means user finished searching. - */ - if (onSearch && validateMeasure) { - onSearch(nextMeasureText, nextMeasurePrefix); + /** + * We will trigger `onSearch` to developer since they may use for async update. + * If met `space` means user finished searching. + */ + if (onSearch && validateMeasure) { + onSearch(nextMeasureText, nextMeasurePrefix); + } } } else if (mergedMeasuring) { stopMeasure(); diff --git a/tests/FullProcess.spec.tsx b/tests/FullProcess.spec.tsx index e7f6478..334b455 100644 --- a/tests/FullProcess.spec.tsx +++ b/tests/FullProcess.spec.tsx @@ -145,6 +145,33 @@ describe('Full Process', () => { expectMeasuring(container, false); }); + it('stop measure if remove prefix and previous prefix exists', () => { + const { container } = createMentions({ defaultValue: '@zz' }); + + // Type `@` again which will start measure + simulateInput(container, '@zz@'); + expectMeasuring(container); + + // Remove the measured `@`, measure should stop instead of + // re-anchor to the previous `@` in the text + const textarea = container.querySelector('textarea'); + fireEvent.keyDown(textarea, { + keyCode: KeyCode.BACKSPACE, + which: KeyCode.BACKSPACE, + key: 'Backspace', + }); + fireEvent.change(textarea, { + target: { value: '@zz', selectionStart: '@zz'.length }, + }); + fireEvent.keyUp(textarea, { + keyCode: KeyCode.BACKSPACE, + which: KeyCode.BACKSPACE, + key: 'Backspace', + }); + + expectMeasuring(container, false); + }); + it('should not call onPressEnter when measuring', () => { const onPressEnter = jest.fn(); const { container } = createMentions({ onPressEnter });