diff --git a/packages/@react-spectrum/ai/test/utils/promptFieldTestUtils.tsx b/packages/@react-spectrum/ai/test/utils/promptFieldTestUtils.tsx index 926d308a47b..37810a7c86f 100644 --- a/packages/@react-spectrum/ai/test/utils/promptFieldTestUtils.tsx +++ b/packages/@react-spectrum/ai/test/utils/promptFieldTestUtils.tsx @@ -47,8 +47,11 @@ export const TINY_PNG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=='; /** - * Jsdom doesn't implement Range.getBoundingClientRect / getClientRects, which the completion - * popover relies on for positioning. Install stubs so the popover can open. Call in beforeAll. + * Install DOM stubs that jsdom is missing but TokenField/the completion popover rely on. Call + * in beforeAll. + * - Range.getBoundingClientRect / getClientRects: the popover uses these for positioning. + * - InputEvent.getTargetRanges: TokenField reads it on beforeinput to find the edited range. + * Returning [] makes it fall back to the current selection (its pre-existing code path). */ export function installRangePolyfill(): void { let proto = Range.prototype as any; @@ -72,6 +75,9 @@ export function installRangePolyfill(): void { [Symbol.iterator]: function* () {} }); } + if (typeof InputEvent !== 'undefined' && !(InputEvent.prototype as any).getTargetRanges) { + (InputEvent.prototype as any).getTargetRanges = () => []; + } } // Completion data, trimmed from PromptField.stories.tsx. diff --git a/packages/react-aria/src/tokenfield/useTokenField.ts b/packages/react-aria/src/tokenfield/useTokenField.ts index 3dfdb1eff2e..ee050095218 100644 --- a/packages/react-aria/src/tokenfield/useTokenField.ts +++ b/packages/react-aria/src/tokenfield/useTokenField.ts @@ -195,11 +195,14 @@ export function useTokenField( stopComposition(); } - let selection = window.getSelection(); - if (!selection || selection.rangeCount === 0) { - return; + let range = e.getTargetRanges()[0]; + if (!range) { + let selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + return; + } + range = selection.getRangeAt(0); } - let range = selection.getRangeAt(0); let [start, end] = rangeToPositions(ref.current!, range); // https://www.w3.org/TR/input-events-2/#interface-InputEvent-Attributes @@ -274,7 +277,7 @@ export function useTokenField( case 'deleteContent': case 'deleteByCut': case 'deleteCompositionText': { - if (!range.collapsed) { + if (!range.collapsed && !isSamePosition(start, end)) { apply(tokens => tokens.replaceRange(start, end, '')); break; }