From 7d0cd6fa11f6db9c0ee26a7f2ab92c83e5b645d9 Mon Sep 17 00:00:00 2001 From: Sarah Higley Date: Wed, 1 Mar 2023 11:39:51 -0800 Subject: [PATCH 1/3] feat: space character insertion while typing in freeform combobox --- .../src/components/Combobox/Combobox.test.tsx | 52 ++++++++++++++++ .../src/components/Combobox/useCombobox.tsx | 62 ++++++++++++++----- 2 files changed, 99 insertions(+), 15 deletions(-) diff --git a/packages/react-components/react-combobox/src/components/Combobox/Combobox.test.tsx b/packages/react-components/react-combobox/src/components/Combobox/Combobox.test.tsx index 0eece7c10057d6..0375144dfb9c4d 100644 --- a/packages/react-components/react-combobox/src/components/Combobox/Combobox.test.tsx +++ b/packages/react-components/react-combobox/src/components/Combobox/Combobox.test.tsx @@ -662,6 +662,58 @@ describe('Combobox', () => { }); }); + /* Freeform space key behavior */ + it('inserts space character when typing in a freeform combobox', () => { + const onOptionSelect = jest.fn(); + + const { getByRole } = render( + + + + + , + ); + + userEvent.type(getByRole('combobox'), 're '); + + expect(onOptionSelect).not.toHaveBeenCalled(); + expect((getByRole('combobox') as HTMLInputElement).value).toEqual('re '); + }); + + it('uses space to select after arrowing through options in a freeform combobox', () => { + const onOptionSelect = jest.fn(); + + const { getByRole } = render( + + + + + , + ); + + userEvent.type(getByRole('combobox'), 're{ArrowDown} '); + + expect(onOptionSelect).toHaveBeenCalledTimes(1); + expect((getByRole('combobox') as HTMLInputElement).value).toEqual('Green'); + }); + + it('inserts space character in closed freeform combobox', () => { + const onOptionSelect = jest.fn(); + + const { getByRole } = render( + + + + + , + ); + + userEvent.type(getByRole('combobox'), 'r{ArrowDown}{Escape} '); + + expect(onOptionSelect).not.toHaveBeenCalled(); + expect((getByRole('combobox') as HTMLInputElement).value).toEqual('r '); + }); + /* Active option */ it('should set active option on click', () => { const { getByTestId } = render( diff --git a/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx b/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx index a9c6761cce3d24..2ae98632d43a11 100644 --- a/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx +++ b/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx @@ -62,6 +62,9 @@ export const useCombobox_unstable = (props: ComboboxProps, ref: React.Ref(); React.useEffect(() => { @@ -146,20 +149,6 @@ export const useCombobox_unstable = (props: ComboboxProps, ref: React.Ref) => { - if (!open && getDropdownActionFromKey(ev) === 'Type') { - baseState.setOpen(ev, true); - } - - // clear activedescendant when moving the text insertion cursor - if (ev.key === ArrowLeft || ev.key === ArrowRight) { - setHideActiveDescendant(true); - } else { - setHideActiveDescendant(false); - } - }; - // resolve input and listbox slot props let triggerSlot: Slot<'input'>; let listboxSlot: Slot | undefined; @@ -174,9 +163,9 @@ export const useCombobox_unstable = (props: ComboboxProps, ref: React.Ref) => { + if (!open && getDropdownActionFromKey(ev) === 'Type') { + baseState.setOpen(ev, true); + } + + // clear activedescendant when moving the text insertion cursor + if (ev.key === ArrowLeft || ev.key === ArrowRight) { + setHideActiveDescendant(true); + } else { + setHideActiveDescendant(false); + } + + // update typing state to true if the user is typing + const action = getDropdownActionFromKey(ev, { open, multiselect }); + if (action === 'Type') { + setTyping(true); + } + // otherwise, update the typing state to false if opening or navigating dropdown options + // other actions, like closing the dropdown, should not impact typing state. + else if ( + (action === 'Open' && ev.key !== ' ') || + action === 'Next' || + action === 'Previous' || + action === 'First' || + action === 'Last' || + action === 'PageUp' || + action === 'PageDown' + ) { + setTyping(false); + } + + // allow space to insert a character if freeform & the last action was typing, or if the popup is closed + if (freeform && (typing || !open) && ev.key === ' ') { + resolvedPropsOnKeyDown?.(ev); + return; + } + + // if we're not allowing space to type, continue with default behavior + defaultOnTriggerKeyDown?.(ev); + }); + /* handle open/close + focus change when clicking expandIcon */ const { onMouseDown: onIconMouseDown, onClick: onIconClick } = state.expandIcon || {}; const onExpandIconMouseDown = useEventCallback( From ebe0b2b84a31ba49c08ccbdf040dcf6255f19998 Mon Sep 17 00:00:00 2001 From: Sarah Higley Date: Wed, 1 Mar 2023 11:41:13 -0800 Subject: [PATCH 2/3] change file --- ...eact-combobox-aa8ff159-8fa3-4a0b-acf2-8b9bad6c66b7.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-combobox-aa8ff159-8fa3-4a0b-acf2-8b9bad6c66b7.json diff --git a/change/@fluentui-react-combobox-aa8ff159-8fa3-4a0b-acf2-8b9bad6c66b7.json b/change/@fluentui-react-combobox-aa8ff159-8fa3-4a0b-acf2-8b9bad6c66b7.json new file mode 100644 index 00000000000000..02fbdfb85912e8 --- /dev/null +++ b/change/@fluentui-react-combobox-aa8ff159-8fa3-4a0b-acf2-8b9bad6c66b7.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: allow space character insertion while typing in freeform combobox", + "packageName": "@fluentui/react-combobox", + "email": "sarah.higley@microsoft.com", + "dependentChangeType": "patch" +} From 19649748ef18ffc5a0b089691823daedec05b8eb Mon Sep 17 00:00:00 2001 From: Sarah Higley Date: Wed, 26 Apr 2023 07:55:15 -0600 Subject: [PATCH 3/3] use ref instead of state to prevent unnecessary rerenders --- .../src/components/Combobox/useCombobox.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx b/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx index 2ae98632d43a11..43c181bfb31307 100644 --- a/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx +++ b/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx @@ -63,7 +63,8 @@ export const useCombobox_unstable = (props: ComboboxProps, ref: React.Ref(); @@ -232,7 +233,7 @@ export const useCombobox_unstable = (props: ComboboxProps, ref: React.Ref