@navikt/ds-react 6.13.0 → 6.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/accordion/AccordionContext.d.ts +0 -1
- package/cjs/collapsible/Collapsible.context.d.ts +0 -1
- package/cjs/date/context/useDateInputContext.d.ts +0 -1
- package/cjs/date/datepicker/parts/HeadRow.d.ts +0 -1
- package/cjs/date/datepicker/parts/HeadRow.js +2 -3
- package/cjs/date/datepicker/parts/HeadRow.js.map +1 -1
- package/cjs/date/datepicker/parts/Row.d.ts +0 -1
- package/cjs/date/datepicker/parts/TableHead.d.ts +0 -1
- package/cjs/date/datepicker/parts/WeekNumber.d.ts +0 -1
- package/cjs/date/datepicker/types.d.ts +0 -1
- package/cjs/date/monthpicker/types.d.ts +0 -1
- package/cjs/date/utils/check-dates.js +2 -2
- package/cjs/date/utils/check-dates.js.map +1 -1
- package/cjs/date/utils/get-initial-year.js +1 -2
- package/cjs/date/utils/get-initial-year.js.map +1 -1
- package/cjs/date/utils/get-month-weeks.js +2 -3
- package/cjs/date/utils/get-month-weeks.js.map +1 -1
- package/cjs/date/utils/is-match.js +2 -3
- package/cjs/date/utils/is-match.js.map +1 -1
- package/cjs/dropdown/Menu/index.d.ts +1 -1
- package/cjs/dropdown/context.d.ts +0 -1
- package/cjs/expansion-card/context.d.ts +0 -1
- package/cjs/form/checkbox/useCheckbox.d.ts +3 -3
- package/cjs/form/combobox/Combobox.d.ts +1 -1
- package/cjs/form/combobox/Combobox.js.map +1 -1
- package/cjs/form/combobox/ComboboxProvider.js +3 -1
- package/cjs/form/combobox/ComboboxProvider.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/AddNewOption.d.ts +3 -0
- package/cjs/form/combobox/FilteredOptions/AddNewOption.js +41 -0
- package/cjs/form/combobox/FilteredOptions/AddNewOption.js.map +1 -0
- package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +13 -57
- package/cjs/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/FilteredOptionsItem.d.ts +6 -0
- package/cjs/form/combobox/FilteredOptions/FilteredOptionsItem.js +43 -0
- package/cjs/form/combobox/FilteredOptions/FilteredOptionsItem.js.map +1 -0
- package/cjs/form/combobox/FilteredOptions/LoadingMessage.d.ts +3 -0
- package/cjs/form/combobox/FilteredOptions/LoadingMessage.js +16 -0
- package/cjs/form/combobox/FilteredOptions/LoadingMessage.js.map +1 -0
- package/cjs/form/combobox/FilteredOptions/MaxSelectedMessage.d.ts +3 -0
- package/cjs/form/combobox/FilteredOptions/MaxSelectedMessage.js +20 -0
- package/cjs/form/combobox/FilteredOptions/MaxSelectedMessage.js.map +1 -0
- package/cjs/form/combobox/FilteredOptions/NoSearchHitsMessage.d.ts +3 -0
- package/cjs/form/combobox/FilteredOptions/NoSearchHitsMessage.js +14 -0
- package/cjs/form/combobox/FilteredOptions/NoSearchHitsMessage.js.map +1 -0
- package/cjs/form/combobox/FilteredOptions/filtered-options-util.d.ts +1 -0
- package/cjs/form/combobox/FilteredOptions/filtered-options-util.js +6 -1
- package/cjs/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +5 -5
- package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
- package/cjs/form/combobox/Input/Input.context.d.ts +20 -7
- package/cjs/form/combobox/Input/Input.context.js +6 -12
- package/cjs/form/combobox/Input/Input.context.js.map +1 -1
- package/cjs/form/combobox/Input/Input.d.ts +2 -1
- package/cjs/form/combobox/Input/Input.js +45 -20
- package/cjs/form/combobox/Input/Input.js.map +1 -1
- package/cjs/form/combobox/Input/InputController.d.ts +1 -1
- package/cjs/form/combobox/Input/InputController.js +1 -1
- package/cjs/form/combobox/Input/InputController.js.map +1 -1
- package/cjs/form/combobox/types.d.ts +8 -4
- package/cjs/form/fieldset/context.d.ts +0 -1
- package/cjs/form/fieldset/useFieldset.d.ts +1 -1
- package/cjs/form/file-upload/FileUpload.context.d.ts +0 -1
- package/cjs/form/file-upload/parts/dropzone/dropzone.types.d.ts +0 -1
- package/cjs/form/file-upload/parts/item/utils/format-file-size.js +1 -2
- package/cjs/form/file-upload/parts/item/utils/format-file-size.js.map +1 -1
- package/cjs/form/file-upload/useFileUpload.d.ts +1 -2
- package/cjs/form/file-upload/utils/is-accepted-file-type.js +1 -2
- package/cjs/form/file-upload/utils/is-accepted-file-type.js.map +1 -1
- package/cjs/form/file-upload/utils/is-accepted-size.js +1 -2
- package/cjs/form/file-upload/utils/is-accepted-size.js.map +1 -1
- package/cjs/form/radio/useRadio.d.ts +3 -3
- package/cjs/form/search/context.d.ts +0 -1
- package/cjs/layout/base/PrimitiveAsChildProps.d.ts +0 -1
- package/cjs/layout/grid/HGrid.js +4 -1
- package/cjs/layout/grid/HGrid.js.map +1 -1
- package/cjs/layout/stack/Stack.js +7 -2
- package/cjs/layout/stack/Stack.js.map +1 -1
- package/cjs/layout/utilities/css.js +2 -3
- package/cjs/layout/utilities/css.js.map +1 -1
- package/cjs/list/context.d.ts +0 -1
- package/cjs/list/types.d.ts +0 -1
- package/cjs/modal/ModalUtils.js +3 -3
- package/cjs/modal/ModalUtils.js.map +1 -1
- package/cjs/modal/types.d.ts +0 -1
- package/cjs/overlays/dismissablelayer/DismissableLayer.d.ts +1 -1
- package/cjs/overlays/dismissablelayer/util/dispatchCustomEvent.js +2 -2
- package/cjs/overlays/dismissablelayer/util/dispatchCustomEvent.js.map +1 -1
- package/cjs/overlays/dismissablelayer/util/useEscapeKeydown.js +1 -2
- package/cjs/overlays/dismissablelayer/util/useEscapeKeydown.js.map +1 -1
- package/cjs/overlays/dismissablelayer/util/useFocusOutside.js +1 -2
- package/cjs/overlays/dismissablelayer/util/useFocusOutside.js.map +1 -1
- package/cjs/overlays/dismissablelayer/util/usePointerDownOutside.js +1 -2
- package/cjs/overlays/dismissablelayer/util/usePointerDownOutside.js.map +1 -1
- package/cjs/overlays/floating/Floating.utils.js +2 -3
- package/cjs/overlays/floating/Floating.utils.js.map +1 -1
- package/cjs/overlays/floating-menu/Menu.d.ts +106 -0
- package/cjs/overlays/floating-menu/Menu.js +593 -0
- package/cjs/overlays/floating-menu/Menu.js.map +1 -0
- package/cjs/overlays/floating-menu/parts/FocusScope.d.ts +22 -0
- package/cjs/overlays/floating-menu/parts/FocusScope.js +89 -0
- package/cjs/overlays/floating-menu/parts/FocusScope.js.map +1 -0
- package/cjs/overlays/floating-menu/parts/RovingFocus.d.ts +9 -0
- package/cjs/overlays/floating-menu/parts/RovingFocus.js +112 -0
- package/cjs/overlays/floating-menu/parts/RovingFocus.js.map +1 -0
- package/cjs/overlays/floating-menu/parts/SlottedDivElement.d.ts +7 -0
- package/cjs/overlays/floating-menu/parts/SlottedDivElement.js +46 -0
- package/cjs/overlays/floating-menu/parts/SlottedDivElement.js.map +1 -0
- package/cjs/slot/merge-props.js +1 -2
- package/cjs/slot/merge-props.js.map +1 -1
- package/cjs/stepper/context.d.ts +0 -1
- package/cjs/table/context.d.ts +0 -1
- package/cjs/tabs/Tabs.context.d.ts +1 -2
- package/cjs/tabs/parts/tab/useTab.d.ts +1 -2
- package/cjs/tabs/parts/tab/useTab.js +1 -2
- package/cjs/tabs/parts/tab/useTab.js.map +1 -1
- package/cjs/tabs/parts/tablist/useScrollButtons.d.ts +0 -1
- package/cjs/tabs/parts/tablist/useScrollButtons.js +1 -2
- package/cjs/tabs/parts/tablist/useScrollButtons.js.map +1 -1
- package/cjs/tabs/parts/tablist/useTabList.js +3 -3
- package/cjs/tabs/parts/tablist/useTabList.js.map +1 -1
- package/cjs/tabs/parts/tabpanel/useTabPanel.js +1 -2
- package/cjs/tabs/parts/tabpanel/useTabPanel.js.map +1 -1
- package/cjs/tabs/useTabs.d.ts +0 -1
- package/cjs/tabs/useTabs.js +1 -2
- package/cjs/tabs/useTabs.js.map +1 -1
- package/cjs/timeline/hooks/usePeriodContext.d.ts +0 -1
- package/cjs/timeline/hooks/useRowContext.d.ts +0 -1
- package/cjs/timeline/hooks/useTimelineContext.d.ts +0 -1
- package/cjs/timeline/period/types.d.ts +0 -1
- package/cjs/timeline/zoom/index.d.ts +1 -1
- package/cjs/toggle-group/ToggleGroup.context.d.ts +1 -2
- package/cjs/toggle-group/parts/useToggleItem.d.ts +1 -2
- package/cjs/toggle-group/parts/useToggleItem.js +3 -3
- package/cjs/toggle-group/parts/useToggleItem.js.map +1 -1
- package/cjs/toggle-group/useToggleGroup.d.ts +0 -1
- package/cjs/toggle-group/useToggleGroup.js +1 -2
- package/cjs/toggle-group/useToggleGroup.js.map +1 -1
- package/cjs/util/composeEventHandlers.d.ts +1 -2
- package/cjs/util/composeEventHandlers.js +1 -2
- package/cjs/util/composeEventHandlers.js.map +1 -1
- package/cjs/util/copy.js +1 -1
- package/cjs/util/copy.js.map +1 -1
- package/cjs/util/create-context.js +1 -2
- package/cjs/util/create-context.js.map +1 -1
- package/cjs/util/debounce.js +1 -1
- package/cjs/util/debounce.js.map +1 -1
- package/cjs/util/hooks/descendants/useDescendant.d.ts +1 -1
- package/cjs/util/hooks/descendants/useDescendant.js +1 -2
- package/cjs/util/hooks/descendants/useDescendant.js.map +1 -1
- package/cjs/util/hooks/descendants/utils.js +4 -4
- package/cjs/util/hooks/descendants/utils.js.map +1 -1
- package/cjs/util/hooks/useCallbackRef.d.ts +0 -1
- package/cjs/util/hooks/useCallbackRef.js +1 -2
- package/cjs/util/hooks/useCallbackRef.js.map +1 -1
- package/cjs/util/hooks/useControllableState.js +1 -2
- package/cjs/util/hooks/useControllableState.js.map +1 -1
- package/cjs/util/hooks/useId.js +1 -2
- package/cjs/util/hooks/useId.js.map +1 -1
- package/cjs/util/hooks/useMergeRefs.d.ts +1 -1
- package/cjs/util/hooks/useMergeRefs.js +2 -3
- package/cjs/util/hooks/useMergeRefs.js.map +1 -1
- package/cjs/util/i18n/get.js +1 -2
- package/cjs/util/i18n/get.js.map +1 -1
- package/cjs/util/i18n/i18n.context.d.ts +0 -1
- package/cjs/util/i18n/i18n.context.js +2 -2
- package/cjs/util/i18n/i18n.context.js.map +1 -1
- package/cjs/util/i18n/merge.js +1 -2
- package/cjs/util/i18n/merge.js.map +1 -1
- package/cjs/util/omit.js +1 -2
- package/cjs/util/omit.js.map +1 -1
- package/cjs/util/types/AsChild.d.ts +0 -1
- package/cjs/util/types/AsChildProps.d.ts +0 -1
- package/cjs/util/virtualfocus/Context.d.ts +43 -0
- package/cjs/util/virtualfocus/Context.js +9 -0
- package/cjs/util/virtualfocus/Context.js.map +1 -0
- package/cjs/util/virtualfocus/SlottedDivElement.d.ts +7 -0
- package/cjs/util/virtualfocus/SlottedDivElement.js +46 -0
- package/cjs/util/virtualfocus/SlottedDivElement.js.map +1 -0
- package/cjs/util/virtualfocus/VirtualFocus.d.ts +62 -0
- package/cjs/util/virtualfocus/VirtualFocus.js +90 -0
- package/cjs/util/virtualfocus/VirtualFocus.js.map +1 -0
- package/cjs/util/virtualfocus/parts/VirtualFocusAnchor.d.ts +33 -0
- package/cjs/util/virtualfocus/parts/VirtualFocusAnchor.js +80 -0
- package/cjs/util/virtualfocus/parts/VirtualFocusAnchor.js.map +1 -0
- package/cjs/util/virtualfocus/parts/VirtualFocusContent.d.ts +4 -0
- package/cjs/util/virtualfocus/parts/VirtualFocusContent.js +45 -0
- package/cjs/util/virtualfocus/parts/VirtualFocusContent.js.map +1 -0
- package/cjs/util/virtualfocus/parts/VirtualFocusItem.d.ts +18 -0
- package/cjs/util/virtualfocus/parts/VirtualFocusItem.js +64 -0
- package/cjs/util/virtualfocus/parts/VirtualFocusItem.js.map +1 -0
- package/esm/accordion/AccordionContext.d.ts +0 -1
- package/esm/collapsible/Collapsible.context.d.ts +0 -1
- package/esm/date/context/useDateInputContext.d.ts +0 -1
- package/esm/date/datepicker/parts/HeadRow.d.ts +0 -1
- package/esm/date/datepicker/parts/Row.d.ts +0 -1
- package/esm/date/datepicker/parts/TableHead.d.ts +0 -1
- package/esm/date/datepicker/parts/WeekNumber.d.ts +0 -1
- package/esm/date/datepicker/types.d.ts +0 -1
- package/esm/date/monthpicker/types.d.ts +0 -1
- package/esm/dropdown/Menu/index.d.ts +1 -1
- package/esm/dropdown/context.d.ts +0 -1
- package/esm/expansion-card/context.d.ts +0 -1
- package/esm/form/checkbox/useCheckbox.d.ts +3 -3
- package/esm/form/combobox/Combobox.d.ts +1 -1
- package/esm/form/combobox/Combobox.js.map +1 -1
- package/esm/form/combobox/ComboboxProvider.js +3 -1
- package/esm/form/combobox/ComboboxProvider.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/AddNewOption.d.ts +3 -0
- package/esm/form/combobox/FilteredOptions/AddNewOption.js +36 -0
- package/esm/form/combobox/FilteredOptions/AddNewOption.js.map +1 -0
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js +13 -57
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/FilteredOptionsItem.d.ts +6 -0
- package/esm/form/combobox/FilteredOptions/FilteredOptionsItem.js +38 -0
- package/esm/form/combobox/FilteredOptions/FilteredOptionsItem.js.map +1 -0
- package/esm/form/combobox/FilteredOptions/LoadingMessage.d.ts +3 -0
- package/esm/form/combobox/FilteredOptions/LoadingMessage.js +11 -0
- package/esm/form/combobox/FilteredOptions/LoadingMessage.js.map +1 -0
- package/esm/form/combobox/FilteredOptions/MaxSelectedMessage.d.ts +3 -0
- package/esm/form/combobox/FilteredOptions/MaxSelectedMessage.js +15 -0
- package/esm/form/combobox/FilteredOptions/MaxSelectedMessage.js.map +1 -0
- package/esm/form/combobox/FilteredOptions/NoSearchHitsMessage.d.ts +3 -0
- package/esm/form/combobox/FilteredOptions/NoSearchHitsMessage.js +9 -0
- package/esm/form/combobox/FilteredOptions/NoSearchHitsMessage.js.map +1 -0
- package/esm/form/combobox/FilteredOptions/filtered-options-util.d.ts +1 -0
- package/esm/form/combobox/FilteredOptions/filtered-options-util.js +6 -1
- package/esm/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +5 -5
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
- package/esm/form/combobox/Input/Input.context.d.ts +20 -7
- package/esm/form/combobox/Input/Input.context.js +7 -13
- package/esm/form/combobox/Input/Input.context.js.map +1 -1
- package/esm/form/combobox/Input/Input.d.ts +2 -1
- package/esm/form/combobox/Input/Input.js +46 -21
- package/esm/form/combobox/Input/Input.js.map +1 -1
- package/esm/form/combobox/Input/InputController.d.ts +1 -1
- package/esm/form/combobox/Input/InputController.js +1 -1
- package/esm/form/combobox/Input/InputController.js.map +1 -1
- package/esm/form/combobox/types.d.ts +8 -4
- package/esm/form/fieldset/context.d.ts +0 -1
- package/esm/form/fieldset/useFieldset.d.ts +1 -1
- package/esm/form/file-upload/FileUpload.context.d.ts +0 -1
- package/esm/form/file-upload/parts/dropzone/dropzone.types.d.ts +0 -1
- package/esm/form/file-upload/useFileUpload.d.ts +1 -2
- package/esm/form/radio/useRadio.d.ts +3 -3
- package/esm/form/search/context.d.ts +0 -1
- package/esm/layout/base/PrimitiveAsChildProps.d.ts +0 -1
- package/esm/layout/grid/HGrid.js +4 -1
- package/esm/layout/grid/HGrid.js.map +1 -1
- package/esm/layout/stack/Stack.js +7 -2
- package/esm/layout/stack/Stack.js.map +1 -1
- package/esm/list/context.d.ts +0 -1
- package/esm/list/types.d.ts +0 -1
- package/esm/modal/types.d.ts +0 -1
- package/esm/overlays/dismissablelayer/DismissableLayer.d.ts +1 -1
- package/esm/overlays/floating-menu/Menu.d.ts +106 -0
- package/esm/overlays/floating-menu/Menu.js +551 -0
- package/esm/overlays/floating-menu/Menu.js.map +1 -0
- package/esm/overlays/floating-menu/parts/FocusScope.d.ts +22 -0
- package/esm/overlays/floating-menu/parts/FocusScope.js +63 -0
- package/esm/overlays/floating-menu/parts/FocusScope.js.map +1 -0
- package/esm/overlays/floating-menu/parts/RovingFocus.d.ts +9 -0
- package/esm/overlays/floating-menu/parts/RovingFocus.js +86 -0
- package/esm/overlays/floating-menu/parts/RovingFocus.js.map +1 -0
- package/esm/overlays/floating-menu/parts/SlottedDivElement.d.ts +7 -0
- package/esm/overlays/floating-menu/parts/SlottedDivElement.js +20 -0
- package/esm/overlays/floating-menu/parts/SlottedDivElement.js.map +1 -0
- package/esm/stepper/context.d.ts +0 -1
- package/esm/table/context.d.ts +0 -1
- package/esm/tabs/Tabs.context.d.ts +1 -2
- package/esm/tabs/parts/tab/useTab.d.ts +1 -2
- package/esm/tabs/parts/tablist/useScrollButtons.d.ts +0 -1
- package/esm/tabs/parts/tablist/useTabList.js +2 -1
- package/esm/tabs/parts/tablist/useTabList.js.map +1 -1
- package/esm/tabs/useTabs.d.ts +0 -1
- package/esm/timeline/hooks/usePeriodContext.d.ts +0 -1
- package/esm/timeline/hooks/useRowContext.d.ts +0 -1
- package/esm/timeline/hooks/useTimelineContext.d.ts +0 -1
- package/esm/timeline/period/types.d.ts +0 -1
- package/esm/timeline/zoom/index.d.ts +1 -1
- package/esm/toggle-group/ToggleGroup.context.d.ts +1 -2
- package/esm/toggle-group/parts/useToggleItem.d.ts +1 -2
- package/esm/toggle-group/parts/useToggleItem.js +2 -1
- package/esm/toggle-group/parts/useToggleItem.js.map +1 -1
- package/esm/toggle-group/useToggleGroup.d.ts +0 -1
- package/esm/util/composeEventHandlers.d.ts +1 -2
- package/esm/util/hooks/descendants/useDescendant.d.ts +1 -1
- package/esm/util/hooks/useCallbackRef.d.ts +0 -1
- package/esm/util/hooks/useMergeRefs.d.ts +1 -1
- package/esm/util/i18n/i18n.context.d.ts +0 -1
- package/esm/util/types/AsChild.d.ts +0 -1
- package/esm/util/types/AsChildProps.d.ts +0 -1
- package/esm/util/virtualfocus/Context.d.ts +43 -0
- package/esm/util/virtualfocus/Context.js +5 -0
- package/esm/util/virtualfocus/Context.js.map +1 -0
- package/esm/util/virtualfocus/SlottedDivElement.d.ts +7 -0
- package/esm/util/virtualfocus/SlottedDivElement.js +20 -0
- package/esm/util/virtualfocus/SlottedDivElement.js.map +1 -0
- package/esm/util/virtualfocus/VirtualFocus.d.ts +62 -0
- package/esm/util/virtualfocus/VirtualFocus.js +63 -0
- package/esm/util/virtualfocus/VirtualFocus.js.map +1 -0
- package/esm/util/virtualfocus/parts/VirtualFocusAnchor.d.ts +33 -0
- package/esm/util/virtualfocus/parts/VirtualFocusAnchor.js +54 -0
- package/esm/util/virtualfocus/parts/VirtualFocusAnchor.js.map +1 -0
- package/esm/util/virtualfocus/parts/VirtualFocusContent.d.ts +4 -0
- package/esm/util/virtualfocus/parts/VirtualFocusContent.js +19 -0
- package/esm/util/virtualfocus/parts/VirtualFocusContent.js.map +1 -0
- package/esm/util/virtualfocus/parts/VirtualFocusItem.d.ts +18 -0
- package/esm/util/virtualfocus/parts/VirtualFocusItem.js +38 -0
- package/esm/util/virtualfocus/parts/VirtualFocusItem.js.map +1 -0
- package/package.json +3 -3
- package/src/form/combobox/Combobox.tsx +4 -1
- package/src/form/combobox/ComboboxProvider.tsx +3 -0
- package/src/form/combobox/FilteredOptions/AddNewOption.tsx +63 -0
- package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +11 -121
- package/src/form/combobox/FilteredOptions/FilteredOptionsItem.tsx +73 -0
- package/src/form/combobox/FilteredOptions/LoadingMessage.tsx +20 -0
- package/src/form/combobox/FilteredOptions/MaxSelectedMessage.tsx +27 -0
- package/src/form/combobox/FilteredOptions/NoSearchHitsMessage.tsx +19 -0
- package/src/form/combobox/FilteredOptions/filtered-options-util.ts +9 -1
- package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +8 -5
- package/src/form/combobox/Input/Input.context.tsx +27 -25
- package/src/form/combobox/Input/Input.tsx +60 -29
- package/src/form/combobox/Input/InputController.tsx +2 -0
- package/src/form/combobox/__tests__/combobox.test.tsx +174 -66
- package/src/form/combobox/types.ts +11 -7
- package/src/layout/grid/HGrid.tsx +4 -1
- package/src/layout/stack/Stack.tsx +6 -2
- package/src/overlays/floating-menu/Menu.tsx +1177 -0
- package/src/overlays/floating-menu/parts/FocusScope.tsx +84 -0
- package/src/overlays/floating-menu/parts/RovingFocus.tsx +121 -0
- package/src/overlays/floating-menu/parts/SlottedDivElement.tsx +17 -0
- package/src/tabs/parts/tablist/useTabList.ts +4 -1
- package/src/toggle-group/parts/useToggleItem.ts +4 -1
- package/src/util/composeEventHandlers.ts +1 -1
- package/src/util/virtualfocus/Context.tsx +27 -0
- package/src/util/virtualfocus/SlottedDivElement.tsx +17 -0
- package/src/util/virtualfocus/VirtualFocus.tsx +89 -0
- package/src/util/virtualfocus/parts/VirtualFocusAnchor.tsx +102 -0
- package/src/util/virtualfocus/parts/VirtualFocusContent.tsx +17 -0
- package/src/util/virtualfocus/parts/VirtualFocusItem.tsx +60 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useInputContext } from "../Input/Input.context";
|
|
3
|
+
import filteredOptionsUtil from "./filtered-options-util";
|
|
4
|
+
|
|
5
|
+
const NoSearchHitsMessage = () => {
|
|
6
|
+
const {
|
|
7
|
+
inputProps: { id },
|
|
8
|
+
} = useInputContext();
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
className="navds-combobox__list-item--no-options"
|
|
12
|
+
id={filteredOptionsUtil.getNoHitsId(id)}
|
|
13
|
+
>
|
|
14
|
+
Ingen søketreff
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default NoSearchHitsMessage;
|
|
@@ -4,11 +4,18 @@ const normalizeText = (text: string): string =>
|
|
|
4
4
|
typeof text === "string" ? text.toLocaleLowerCase().trim() : "";
|
|
5
5
|
|
|
6
6
|
const isPartOfText = (value: string, text: string) =>
|
|
7
|
-
normalizeText(text).
|
|
7
|
+
normalizeText(text).includes(normalizeText(value ?? ""));
|
|
8
8
|
|
|
9
9
|
const getMatchingValuesFromList = (value: string, list: ComboboxOption[]) =>
|
|
10
10
|
list.filter((listItem) => isPartOfText(value, listItem.label));
|
|
11
11
|
|
|
12
|
+
const getFirstValueStartingWith = (text: string, list: ComboboxOption[]) => {
|
|
13
|
+
const normalizedText = normalizeText(text);
|
|
14
|
+
return list.find((listItem) =>
|
|
15
|
+
normalizeText(listItem.label).startsWith(normalizedText),
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
12
19
|
const getFilteredOptionsId = (comboboxId: string) =>
|
|
13
20
|
`${comboboxId}-filtered-options`;
|
|
14
21
|
|
|
@@ -37,4 +44,5 @@ export default {
|
|
|
37
44
|
getIsLoadingId,
|
|
38
45
|
getNoHitsId,
|
|
39
46
|
getMaxSelectedOptionsId,
|
|
47
|
+
getFirstValueStartingWith,
|
|
40
48
|
};
|
|
@@ -106,16 +106,19 @@ const FilteredOptionsProvider = ({
|
|
|
106
106
|
}, [allowNewValues, customOptions, id, options, value]);
|
|
107
107
|
|
|
108
108
|
useClientLayoutEffect(() => {
|
|
109
|
+
const autoCompleteCandidate =
|
|
110
|
+
filteredOptionsUtils.getFirstValueStartingWith(
|
|
111
|
+
searchTerm,
|
|
112
|
+
filteredOptions,
|
|
113
|
+
)?.label;
|
|
109
114
|
if (
|
|
110
115
|
shouldAutocomplete &&
|
|
111
|
-
|
|
112
|
-
(previousSearchTerm?.length || 0) < searchTerm.length
|
|
113
|
-
filteredOptions.length > 0
|
|
116
|
+
autoCompleteCandidate &&
|
|
117
|
+
(previousSearchTerm?.length || 0) < searchTerm.length
|
|
114
118
|
) {
|
|
115
119
|
setValue(
|
|
116
|
-
`${searchTerm}${
|
|
120
|
+
`${searchTerm}${autoCompleteCandidate.substring(searchTerm.length)}`,
|
|
117
121
|
);
|
|
118
|
-
setSearchTerm(searchTerm);
|
|
119
122
|
}
|
|
120
123
|
}, [
|
|
121
124
|
filteredOptions,
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
ChangeEvent,
|
|
3
|
-
ChangeEventHandler,
|
|
4
|
-
useCallback,
|
|
5
|
-
useMemo,
|
|
6
|
-
useRef,
|
|
7
|
-
useState,
|
|
8
|
-
} from "react";
|
|
1
|
+
import React, { useCallback, useMemo, useRef, useState } from "react";
|
|
9
2
|
import { createContext } from "../../../util/create-context";
|
|
10
3
|
import { useClientLayoutEffect } from "../../../util/hooks";
|
|
11
4
|
import { FormFieldType, useFormField } from "../../useFormField";
|
|
@@ -13,12 +6,12 @@ import { ComboboxProps } from "../types";
|
|
|
13
6
|
|
|
14
7
|
interface InputContextValue extends FormFieldType {
|
|
15
8
|
clearInput: NonNullable<ComboboxProps["onClear"]>;
|
|
16
|
-
error?:
|
|
9
|
+
error?: ComboboxProps["error"];
|
|
17
10
|
focusInput: () => void;
|
|
18
11
|
inputRef: React.RefObject<HTMLInputElement>;
|
|
19
12
|
value: string;
|
|
20
13
|
setValue: (text: string) => void;
|
|
21
|
-
onChange:
|
|
14
|
+
onChange: (newValue: string) => void;
|
|
22
15
|
searchTerm: string;
|
|
23
16
|
setSearchTerm: React.Dispatch<React.SetStateAction<string>>;
|
|
24
17
|
shouldAutocomplete?: boolean;
|
|
@@ -31,7 +24,24 @@ const [InputContextProvider, useInputContext] =
|
|
|
31
24
|
errorMessage: "useInputContext must be used within an InputContextProvider",
|
|
32
25
|
});
|
|
33
26
|
|
|
34
|
-
|
|
27
|
+
interface Props {
|
|
28
|
+
children: React.ReactNode;
|
|
29
|
+
value: {
|
|
30
|
+
defaultValue: ComboboxProps["defaultValue"];
|
|
31
|
+
description: ComboboxProps["description"];
|
|
32
|
+
disabled: ComboboxProps["disabled"];
|
|
33
|
+
error: ComboboxProps["error"];
|
|
34
|
+
errorId: ComboboxProps["errorId"];
|
|
35
|
+
id: ComboboxProps["id"];
|
|
36
|
+
value: ComboboxProps["value"];
|
|
37
|
+
onChange: ComboboxProps["onChange"];
|
|
38
|
+
onClear: ComboboxProps["onClear"];
|
|
39
|
+
shouldAutocomplete: ComboboxProps["shouldAutocomplete"];
|
|
40
|
+
size: ComboboxProps["size"];
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const InputProvider = ({ children, value: props }: Props) => {
|
|
35
45
|
const {
|
|
36
46
|
defaultValue = "",
|
|
37
47
|
description,
|
|
@@ -68,30 +78,22 @@ const InputProvider = ({ children, value: props }) => {
|
|
|
68
78
|
const [searchTerm, setSearchTerm] = useState(value);
|
|
69
79
|
|
|
70
80
|
const onChange = useCallback(
|
|
71
|
-
(
|
|
72
|
-
const newValue = event.currentTarget.value;
|
|
81
|
+
(newValue: string) => {
|
|
73
82
|
externalValue ?? setInternalValue(newValue);
|
|
74
|
-
externalOnChange?.(event);
|
|
75
83
|
setSearchTerm(newValue);
|
|
84
|
+
externalOnChange?.(newValue);
|
|
76
85
|
},
|
|
77
86
|
[externalValue, externalOnChange],
|
|
78
87
|
);
|
|
79
88
|
|
|
80
|
-
const setValue = useCallback(
|
|
81
|
-
(text) => {
|
|
82
|
-
setInternalValue(text);
|
|
83
|
-
},
|
|
84
|
-
[setInternalValue],
|
|
85
|
-
);
|
|
86
|
-
|
|
87
89
|
const clearInput = useCallback(
|
|
88
90
|
(event: React.PointerEvent | React.KeyboardEvent | React.MouseEvent) => {
|
|
89
91
|
onClear?.(event);
|
|
90
|
-
externalOnChange?.(
|
|
91
|
-
|
|
92
|
+
externalOnChange?.("");
|
|
93
|
+
setInternalValue("");
|
|
92
94
|
setSearchTerm("");
|
|
93
95
|
},
|
|
94
|
-
[externalOnChange, onClear,
|
|
96
|
+
[externalOnChange, onClear, setInternalValue],
|
|
95
97
|
);
|
|
96
98
|
|
|
97
99
|
const focusInput = useCallback(() => {
|
|
@@ -111,7 +113,7 @@ const InputProvider = ({ children, value: props }) => {
|
|
|
111
113
|
focusInput,
|
|
112
114
|
inputRef,
|
|
113
115
|
value,
|
|
114
|
-
setValue,
|
|
116
|
+
setValue: setInternalValue,
|
|
115
117
|
onChange,
|
|
116
118
|
searchTerm,
|
|
117
119
|
setSearchTerm,
|
|
@@ -1,26 +1,38 @@
|
|
|
1
1
|
import cl from "clsx";
|
|
2
2
|
import React, {
|
|
3
|
-
ChangeEvent,
|
|
4
3
|
InputHTMLAttributes,
|
|
5
4
|
forwardRef,
|
|
6
5
|
useCallback,
|
|
6
|
+
useRef,
|
|
7
7
|
} from "react";
|
|
8
8
|
import { omit } from "../../../util";
|
|
9
|
+
import { useMergeRefs } from "../../../util/hooks";
|
|
9
10
|
import filteredOptionsUtil from "../FilteredOptions/filtered-options-util";
|
|
10
11
|
import { useFilteredOptionsContext } from "../FilteredOptions/filteredOptionsContext";
|
|
11
12
|
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
|
|
12
13
|
import { useInputContext } from "./Input.context";
|
|
13
14
|
|
|
14
15
|
interface InputProps
|
|
15
|
-
extends Omit<InputHTMLAttributes<HTMLInputElement>, "value"> {
|
|
16
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "disabled"> {
|
|
16
17
|
ref: React.Ref<HTMLInputElement>;
|
|
17
18
|
inputClassName?: string;
|
|
19
|
+
shouldShowSelectedOptions?: boolean;
|
|
18
20
|
value?: string;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
22
|
-
({ inputClassName, ...rest }, ref) => {
|
|
23
|
-
const
|
|
24
|
+
({ inputClassName, shouldShowSelectedOptions, ...rest }, ref) => {
|
|
25
|
+
const internalRef = useRef<HTMLInputElement>(null);
|
|
26
|
+
const mergedRefs = useMergeRefs(ref, internalRef);
|
|
27
|
+
const {
|
|
28
|
+
clearInput,
|
|
29
|
+
inputProps,
|
|
30
|
+
onChange,
|
|
31
|
+
size,
|
|
32
|
+
value,
|
|
33
|
+
searchTerm,
|
|
34
|
+
setValue,
|
|
35
|
+
} = useInputContext();
|
|
24
36
|
const {
|
|
25
37
|
selectedOptions,
|
|
26
38
|
removeSelectedOption,
|
|
@@ -56,7 +68,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
56
68
|
if (!isMultiSelect && !isTextInSelectedOptions(currentOption.label)) {
|
|
57
69
|
toggleIsListOpen(false);
|
|
58
70
|
}
|
|
59
|
-
} else if (
|
|
71
|
+
} else if (isTextInSelectedOptions(value)) {
|
|
60
72
|
event.preventDefault();
|
|
61
73
|
// Trying to set the same value that is already set, so just clearing the input
|
|
62
74
|
clearInput(event);
|
|
@@ -67,13 +79,13 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
67
79
|
allowNewValues && isValueNew
|
|
68
80
|
? { label: value, value }
|
|
69
81
|
: filteredOptions[0];
|
|
82
|
+
|
|
83
|
+
if (!selectedValue) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
70
87
|
toggleOption(selectedValue, event);
|
|
71
|
-
if (
|
|
72
|
-
!isMultiSelect &&
|
|
73
|
-
!isTextInSelectedOptions(
|
|
74
|
-
filteredOptions[0].label || selectedValue.label,
|
|
75
|
-
)
|
|
76
|
-
) {
|
|
88
|
+
if (!isMultiSelect && !isTextInSelectedOptions(selectedValue.label)) {
|
|
77
89
|
toggleIsListOpen(false);
|
|
78
90
|
}
|
|
79
91
|
}
|
|
@@ -96,10 +108,6 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
96
108
|
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
97
109
|
e.preventDefault();
|
|
98
110
|
switch (e.key) {
|
|
99
|
-
case "Escape":
|
|
100
|
-
clearInput(e);
|
|
101
|
-
toggleIsListOpen(false);
|
|
102
|
-
break;
|
|
103
111
|
case "Enter":
|
|
104
112
|
case "Accept":
|
|
105
113
|
onEnter(e);
|
|
@@ -118,10 +126,10 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
118
126
|
};
|
|
119
127
|
|
|
120
128
|
const handleKeyDown = useCallback(
|
|
121
|
-
(e) => {
|
|
129
|
+
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
122
130
|
setIsMouseLastUsedInputDevice(false);
|
|
123
131
|
if (e.key === "Backspace") {
|
|
124
|
-
if (value === "") {
|
|
132
|
+
if (value === "" && shouldShowSelectedOptions) {
|
|
125
133
|
const lastSelectedOption =
|
|
126
134
|
selectedOptions[selectedOptions.length - 1];
|
|
127
135
|
if (lastSelectedOption) {
|
|
@@ -132,17 +140,34 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
132
140
|
if (activeDecendantId || value) {
|
|
133
141
|
e.preventDefault();
|
|
134
142
|
}
|
|
143
|
+
} else if (e.key === "Escape") {
|
|
144
|
+
if (isListOpen || value) {
|
|
145
|
+
e.preventDefault(); // Prevents closing an encasing Modal, as Combobox reacts on keyup.
|
|
146
|
+
clearInput(e);
|
|
147
|
+
toggleIsListOpen(false);
|
|
148
|
+
}
|
|
149
|
+
} else if (["ArrowLeft", "ArrowRight"].includes(e.key)) {
|
|
150
|
+
/**
|
|
151
|
+
* In case user has an active selection and 'completes' the selection with ArrowLeft or ArrowRight
|
|
152
|
+
* we need to make sure to update the filter.
|
|
153
|
+
*/
|
|
154
|
+
if (value !== "" && value !== searchTerm) {
|
|
155
|
+
onChange(value);
|
|
156
|
+
}
|
|
135
157
|
} else if (e.key === "ArrowDown") {
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
if (virtualFocus.activeElement === null || !isListOpen) {
|
|
141
|
-
toggleIsListOpen(true);
|
|
142
|
-
}
|
|
143
|
-
virtualFocus.moveFocusDown();
|
|
158
|
+
// Reset the value to the search term to cancel autocomplete
|
|
159
|
+
// if the user moves focus down to the FilteredOptions
|
|
160
|
+
if (value !== searchTerm) {
|
|
161
|
+
setValue(searchTerm);
|
|
144
162
|
}
|
|
163
|
+
if (virtualFocus.activeElement === null || !isListOpen) {
|
|
164
|
+
toggleIsListOpen(true);
|
|
165
|
+
}
|
|
166
|
+
virtualFocus.moveFocusDown();
|
|
145
167
|
} else if (e.key === "ArrowUp") {
|
|
168
|
+
if (value !== "" && value !== searchTerm) {
|
|
169
|
+
onChange(value);
|
|
170
|
+
}
|
|
146
171
|
// Check that the FilteredOptions list is open and has virtual focus.
|
|
147
172
|
// Otherwise ignore keystrokes, so it doesn't interfere with text editing
|
|
148
173
|
if (isListOpen && activeDecendantId) {
|
|
@@ -161,13 +186,18 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
161
186
|
isListOpen,
|
|
162
187
|
activeDecendantId,
|
|
163
188
|
setIsMouseLastUsedInputDevice,
|
|
189
|
+
clearInput,
|
|
164
190
|
toggleIsListOpen,
|
|
191
|
+
onChange,
|
|
165
192
|
virtualFocus,
|
|
193
|
+
setValue,
|
|
194
|
+
searchTerm,
|
|
195
|
+
shouldShowSelectedOptions,
|
|
166
196
|
],
|
|
167
197
|
);
|
|
168
198
|
|
|
169
199
|
const onChangeHandler = useCallback(
|
|
170
|
-
(event: ChangeEvent<HTMLInputElement>) => {
|
|
200
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
171
201
|
const newValue = event.target.value;
|
|
172
202
|
if (newValue && newValue !== "") {
|
|
173
203
|
toggleIsListOpen(true);
|
|
@@ -175,7 +205,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
175
205
|
toggleIsListOpen(false);
|
|
176
206
|
}
|
|
177
207
|
virtualFocus.moveFocusToTop();
|
|
178
|
-
onChange(
|
|
208
|
+
onChange(newValue);
|
|
179
209
|
},
|
|
180
210
|
[filteredOptions.length, virtualFocus, onChange, toggleIsListOpen],
|
|
181
211
|
);
|
|
@@ -184,10 +214,11 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
184
214
|
<input
|
|
185
215
|
{...rest}
|
|
186
216
|
{...omit(inputProps, ["aria-invalid"])}
|
|
187
|
-
ref={
|
|
217
|
+
ref={mergedRefs}
|
|
188
218
|
value={value}
|
|
189
219
|
onBlur={() => virtualFocus.moveFocusToTop()}
|
|
190
|
-
|
|
220
|
+
onClick={() => value !== searchTerm && onChange(value)}
|
|
221
|
+
onInput={onChangeHandler}
|
|
191
222
|
type="text"
|
|
192
223
|
role="combobox"
|
|
193
224
|
onKeyUp={handleKeyUp}
|
|
@@ -24,6 +24,7 @@ export const InputController = forwardRef<
|
|
|
24
24
|
| "size"
|
|
25
25
|
| "onClear"
|
|
26
26
|
| "value"
|
|
27
|
+
| "disabled"
|
|
27
28
|
>
|
|
28
29
|
>((props, ref) => {
|
|
29
30
|
const {
|
|
@@ -72,6 +73,7 @@ export const InputController = forwardRef<
|
|
|
72
73
|
id={inputProps.id}
|
|
73
74
|
ref={mergedInputRef}
|
|
74
75
|
inputClassName={inputClassName}
|
|
76
|
+
shouldShowSelectedOptions={shouldShowSelectedOptions}
|
|
75
77
|
{...rest}
|
|
76
78
|
/>
|
|
77
79
|
</SelectedOptions>
|
|
@@ -8,6 +8,7 @@ import { UNSAFE_Combobox } from "../index";
|
|
|
8
8
|
const options = [
|
|
9
9
|
"banana",
|
|
10
10
|
"apple",
|
|
11
|
+
"apple pie",
|
|
11
12
|
"tangerine",
|
|
12
13
|
"pear",
|
|
13
14
|
"grape",
|
|
@@ -71,86 +72,193 @@ describe("Render combobox", () => {
|
|
|
71
72
|
});
|
|
72
73
|
});
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
describe("Combobox state-handling", () => {
|
|
76
|
+
test("Should show loading icon when loading (used for async search)", async () => {
|
|
77
|
+
render(<App options={[]} isListOpen isLoading />);
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
79
|
+
expect(await screen.findByText("Søker...")).toBeInTheDocument();
|
|
80
|
+
});
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
render(<App options={options} />);
|
|
82
|
+
test("Should not select previous focused element when closes", async () => {
|
|
83
|
+
render(<App options={options} />);
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
await act(async () => {
|
|
86
|
+
await userEvent.click(
|
|
87
|
+
screen.getByRole("combobox", {
|
|
88
|
+
name: "Hva er dine favorittfrukter?",
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
await act(async () => {
|
|
93
|
+
await userEvent.type(
|
|
94
|
+
screen.getByRole("combobox", {
|
|
95
|
+
name: "Hva er dine favorittfrukter?",
|
|
96
|
+
}),
|
|
97
|
+
"ban",
|
|
98
|
+
);
|
|
99
|
+
await userEvent.keyboard("{ArrowDown}");
|
|
100
|
+
await userEvent.keyboard("{ArrowUp}");
|
|
101
|
+
await userEvent.keyboard("{Enter}");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(screen.queryByRole("button", { name: "banana slett" })).toBeNull();
|
|
98
105
|
});
|
|
99
106
|
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
test("Should reset list when resetting input (ESC)", async () => {
|
|
108
|
+
render(<App options={options} />);
|
|
102
109
|
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
await act(async () => {
|
|
111
|
+
await userEvent.click(
|
|
112
|
+
screen.getByRole("combobox", {
|
|
113
|
+
name: "Hva er dine favorittfrukter?",
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
await act(async () => {
|
|
118
|
+
await userEvent.type(
|
|
119
|
+
screen.getByRole("combobox", {
|
|
120
|
+
name: "Hva er dine favorittfrukter?",
|
|
121
|
+
}),
|
|
122
|
+
"apple",
|
|
123
|
+
);
|
|
124
|
+
await userEvent.keyboard("{ArrowDown}");
|
|
125
|
+
await userEvent.keyboard("{Escape}");
|
|
126
|
+
await userEvent.keyboard("{ArrowDown}");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(
|
|
130
|
+
await screen.findByRole("option", { name: "banana" }),
|
|
131
|
+
).toBeInTheDocument();
|
|
132
|
+
});
|
|
105
133
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
134
|
+
test("Should handle complex options with label and value", async () => {
|
|
135
|
+
const onToggleSelected = vi.fn();
|
|
136
|
+
render(
|
|
137
|
+
<App
|
|
138
|
+
options={[
|
|
139
|
+
{ label: "Hjelpemidler [HJE]", value: "HJE" },
|
|
140
|
+
{ label: "Oppfølging [OPP]", value: "OPP" },
|
|
141
|
+
{ label: "Sykepenger [SYK]", value: "SYK" },
|
|
142
|
+
{ label: "Sykemelding [SYM]", value: "SYM" },
|
|
143
|
+
]}
|
|
144
|
+
onToggleSelected={onToggleSelected}
|
|
145
|
+
/>,
|
|
109
146
|
);
|
|
147
|
+
|
|
148
|
+
expect(screen.getByRole("combobox")).toBeInTheDocument();
|
|
149
|
+
const bananaOption = screen.getByRole("option", {
|
|
150
|
+
name: "Hjelpemidler [HJE]",
|
|
151
|
+
selected: false,
|
|
152
|
+
});
|
|
153
|
+
await act(async () => {
|
|
154
|
+
await userEvent.click(bananaOption);
|
|
155
|
+
});
|
|
156
|
+
expect(onToggleSelected).toHaveBeenCalledWith("HJE", true, false);
|
|
157
|
+
expect(
|
|
158
|
+
screen.getByRole("option", {
|
|
159
|
+
name: "Hjelpemidler [HJE]",
|
|
160
|
+
selected: true,
|
|
161
|
+
}),
|
|
162
|
+
).toBeInTheDocument();
|
|
110
163
|
});
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
164
|
+
|
|
165
|
+
test("should trigger onChange for every character typed or removed", async () => {
|
|
166
|
+
const onChange = vi.fn();
|
|
167
|
+
const onToggleSelected = vi.fn();
|
|
168
|
+
render(
|
|
169
|
+
<App
|
|
170
|
+
options={["Apple", "Orange", "Banana", "Lemon"]}
|
|
171
|
+
onChange={onChange}
|
|
172
|
+
onToggleSelected={onToggleSelected}
|
|
173
|
+
shouldAutocomplete
|
|
174
|
+
/>,
|
|
115
175
|
);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
176
|
+
const combobox = screen.getByRole("combobox");
|
|
177
|
+
expect(combobox).toBeInTheDocument();
|
|
178
|
+
|
|
179
|
+
await act(async () => {
|
|
180
|
+
await userEvent.click(combobox);
|
|
181
|
+
await userEvent.type(combobox, "Lemon");
|
|
182
|
+
});
|
|
183
|
+
expect(onChange).toHaveBeenNthCalledWith(1, "L");
|
|
184
|
+
expect(onChange).toHaveBeenNthCalledWith(2, "Le");
|
|
185
|
+
expect(onChange).toHaveBeenNthCalledWith(3, "Lem");
|
|
186
|
+
expect(onChange).toHaveBeenNthCalledWith(4, "Lemo");
|
|
187
|
+
expect(onChange).toHaveBeenNthCalledWith(5, "Lemon");
|
|
119
188
|
});
|
|
120
189
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
190
|
+
test("should trigger onChange while typing and on accepting autocomplete suggestions", async () => {
|
|
191
|
+
const onChange = vi.fn();
|
|
192
|
+
const onToggleSelected = vi.fn();
|
|
193
|
+
render(
|
|
194
|
+
<App
|
|
195
|
+
options={[
|
|
196
|
+
"Hjelpemidler [HJE]",
|
|
197
|
+
"Oppfølging [OPP]",
|
|
198
|
+
"Sykepenger [SYK]",
|
|
199
|
+
"Sykemelding [SYM]",
|
|
200
|
+
]}
|
|
201
|
+
onChange={onChange}
|
|
202
|
+
onToggleSelected={onToggleSelected}
|
|
203
|
+
shouldAutocomplete
|
|
204
|
+
/>,
|
|
205
|
+
);
|
|
206
|
+
const combobox = screen.getByRole("combobox");
|
|
207
|
+
expect(combobox).toBeInTheDocument();
|
|
125
208
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
209
|
+
await act(async () => {
|
|
210
|
+
await userEvent.click(combobox);
|
|
211
|
+
await userEvent.type(combobox, "Syke");
|
|
212
|
+
await userEvent.keyboard("{ArrowRight}");
|
|
213
|
+
await userEvent.keyboard("{ArrowDown}");
|
|
214
|
+
await userEvent.keyboard("{Enter}");
|
|
215
|
+
});
|
|
216
|
+
expect(onChange).toHaveBeenNthCalledWith(1, "S");
|
|
217
|
+
expect(onChange).toHaveBeenNthCalledWith(2, "Sy");
|
|
218
|
+
expect(onChange).toHaveBeenNthCalledWith(3, "Syk");
|
|
219
|
+
expect(onChange).toHaveBeenNthCalledWith(4, "Syke");
|
|
220
|
+
expect(onChange).toHaveBeenNthCalledWith(5, "Sykepenger [SYK]");
|
|
221
|
+
expect(onChange).toHaveBeenCalledWith("");
|
|
222
|
+
expect(onToggleSelected).toHaveBeenCalledOnce();
|
|
223
|
+
expect(onToggleSelected).toHaveBeenCalledWith(
|
|
224
|
+
"Sykepenger [SYK]",
|
|
225
|
+
true,
|
|
226
|
+
false,
|
|
227
|
+
);
|
|
144
228
|
});
|
|
145
|
-
|
|
146
|
-
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("search", () => {
|
|
232
|
+
test("should find matched anywhere in the label", async () => {
|
|
233
|
+
render(<App options={options} />);
|
|
234
|
+
|
|
235
|
+
const combobox = screen.getByRole("combobox", {
|
|
236
|
+
name: "Hva er dine favorittfrukter?",
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
await act(async () => {
|
|
240
|
+
await userEvent.click(combobox);
|
|
241
|
+
|
|
242
|
+
await userEvent.type(combobox, "p");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const searchHits = [
|
|
246
|
+
"apple",
|
|
247
|
+
"apple pie",
|
|
248
|
+
"pear",
|
|
249
|
+
"grape",
|
|
250
|
+
"passion fruit",
|
|
251
|
+
"pineapple",
|
|
252
|
+
"grape fruit",
|
|
253
|
+
];
|
|
254
|
+
searchHits.forEach((label) => {
|
|
255
|
+
expect(screen.getByRole("option", { name: label })).toBeInTheDocument();
|
|
256
|
+
});
|
|
257
|
+
screen.getAllByRole("option").forEach((option) => {
|
|
258
|
+
expect(
|
|
259
|
+
option.textContent && searchHits.includes(option.textContent),
|
|
260
|
+
).toBe(true);
|
|
261
|
+
});
|
|
147
262
|
});
|
|
148
|
-
expect(onToggleSelected).toHaveBeenCalledWith("HJE", true, false);
|
|
149
|
-
expect(
|
|
150
|
-
screen.getByRole("option", {
|
|
151
|
-
name: "Hjelpemidler [HJE]",
|
|
152
|
-
selected: true,
|
|
153
|
-
}),
|
|
154
|
-
).toBeInTheDocument();
|
|
155
263
|
});
|
|
156
264
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { InputHTMLAttributes } from "react";
|
|
2
2
|
import { FormFieldProps } from "../useFormField";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -29,7 +29,10 @@ export type MaxSelected = {
|
|
|
29
29
|
|
|
30
30
|
export interface ComboboxProps
|
|
31
31
|
extends FormFieldProps,
|
|
32
|
-
Omit<
|
|
32
|
+
Omit<
|
|
33
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
34
|
+
"size" | "onChange" | "value" | "defaultValue"
|
|
35
|
+
> {
|
|
33
36
|
/**
|
|
34
37
|
* Combobox label.
|
|
35
38
|
*/
|
|
@@ -89,12 +92,9 @@ export interface ComboboxProps
|
|
|
89
92
|
/**
|
|
90
93
|
* Callback function triggered whenever the value of the input field is triggered.
|
|
91
94
|
*
|
|
92
|
-
* @param
|
|
95
|
+
* @param value The value after change
|
|
93
96
|
*/
|
|
94
|
-
onChange?: (
|
|
95
|
-
event: ChangeEvent<HTMLInputElement> | null,
|
|
96
|
-
value?: string,
|
|
97
|
-
) => void;
|
|
97
|
+
onChange?: (value: string) => void;
|
|
98
98
|
/**
|
|
99
99
|
* Callback function triggered whenever the input field is cleared.
|
|
100
100
|
*
|
|
@@ -156,4 +156,8 @@ export interface ComboboxProps
|
|
|
156
156
|
* This converts the input to a controlled input, so you have to use onChange to update the value.
|
|
157
157
|
*/
|
|
158
158
|
value?: string;
|
|
159
|
+
/**
|
|
160
|
+
* Initial value of the input field. Only works when the input is uncontrolled.
|
|
161
|
+
*/
|
|
162
|
+
defaultValue?: string;
|
|
159
163
|
}
|