@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.
Files changed (341) hide show
  1. package/cjs/accordion/AccordionContext.d.ts +0 -1
  2. package/cjs/collapsible/Collapsible.context.d.ts +0 -1
  3. package/cjs/date/context/useDateInputContext.d.ts +0 -1
  4. package/cjs/date/datepicker/parts/HeadRow.d.ts +0 -1
  5. package/cjs/date/datepicker/parts/HeadRow.js +2 -3
  6. package/cjs/date/datepicker/parts/HeadRow.js.map +1 -1
  7. package/cjs/date/datepicker/parts/Row.d.ts +0 -1
  8. package/cjs/date/datepicker/parts/TableHead.d.ts +0 -1
  9. package/cjs/date/datepicker/parts/WeekNumber.d.ts +0 -1
  10. package/cjs/date/datepicker/types.d.ts +0 -1
  11. package/cjs/date/monthpicker/types.d.ts +0 -1
  12. package/cjs/date/utils/check-dates.js +2 -2
  13. package/cjs/date/utils/check-dates.js.map +1 -1
  14. package/cjs/date/utils/get-initial-year.js +1 -2
  15. package/cjs/date/utils/get-initial-year.js.map +1 -1
  16. package/cjs/date/utils/get-month-weeks.js +2 -3
  17. package/cjs/date/utils/get-month-weeks.js.map +1 -1
  18. package/cjs/date/utils/is-match.js +2 -3
  19. package/cjs/date/utils/is-match.js.map +1 -1
  20. package/cjs/dropdown/Menu/index.d.ts +1 -1
  21. package/cjs/dropdown/context.d.ts +0 -1
  22. package/cjs/expansion-card/context.d.ts +0 -1
  23. package/cjs/form/checkbox/useCheckbox.d.ts +3 -3
  24. package/cjs/form/combobox/Combobox.d.ts +1 -1
  25. package/cjs/form/combobox/Combobox.js.map +1 -1
  26. package/cjs/form/combobox/ComboboxProvider.js +3 -1
  27. package/cjs/form/combobox/ComboboxProvider.js.map +1 -1
  28. package/cjs/form/combobox/FilteredOptions/AddNewOption.d.ts +3 -0
  29. package/cjs/form/combobox/FilteredOptions/AddNewOption.js +41 -0
  30. package/cjs/form/combobox/FilteredOptions/AddNewOption.js.map +1 -0
  31. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +13 -57
  32. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  33. package/cjs/form/combobox/FilteredOptions/FilteredOptionsItem.d.ts +6 -0
  34. package/cjs/form/combobox/FilteredOptions/FilteredOptionsItem.js +43 -0
  35. package/cjs/form/combobox/FilteredOptions/FilteredOptionsItem.js.map +1 -0
  36. package/cjs/form/combobox/FilteredOptions/LoadingMessage.d.ts +3 -0
  37. package/cjs/form/combobox/FilteredOptions/LoadingMessage.js +16 -0
  38. package/cjs/form/combobox/FilteredOptions/LoadingMessage.js.map +1 -0
  39. package/cjs/form/combobox/FilteredOptions/MaxSelectedMessage.d.ts +3 -0
  40. package/cjs/form/combobox/FilteredOptions/MaxSelectedMessage.js +20 -0
  41. package/cjs/form/combobox/FilteredOptions/MaxSelectedMessage.js.map +1 -0
  42. package/cjs/form/combobox/FilteredOptions/NoSearchHitsMessage.d.ts +3 -0
  43. package/cjs/form/combobox/FilteredOptions/NoSearchHitsMessage.js +14 -0
  44. package/cjs/form/combobox/FilteredOptions/NoSearchHitsMessage.js.map +1 -0
  45. package/cjs/form/combobox/FilteredOptions/filtered-options-util.d.ts +1 -0
  46. package/cjs/form/combobox/FilteredOptions/filtered-options-util.js +6 -1
  47. package/cjs/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
  48. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +5 -5
  49. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  50. package/cjs/form/combobox/Input/Input.context.d.ts +20 -7
  51. package/cjs/form/combobox/Input/Input.context.js +6 -12
  52. package/cjs/form/combobox/Input/Input.context.js.map +1 -1
  53. package/cjs/form/combobox/Input/Input.d.ts +2 -1
  54. package/cjs/form/combobox/Input/Input.js +45 -20
  55. package/cjs/form/combobox/Input/Input.js.map +1 -1
  56. package/cjs/form/combobox/Input/InputController.d.ts +1 -1
  57. package/cjs/form/combobox/Input/InputController.js +1 -1
  58. package/cjs/form/combobox/Input/InputController.js.map +1 -1
  59. package/cjs/form/combobox/types.d.ts +8 -4
  60. package/cjs/form/fieldset/context.d.ts +0 -1
  61. package/cjs/form/fieldset/useFieldset.d.ts +1 -1
  62. package/cjs/form/file-upload/FileUpload.context.d.ts +0 -1
  63. package/cjs/form/file-upload/parts/dropzone/dropzone.types.d.ts +0 -1
  64. package/cjs/form/file-upload/parts/item/utils/format-file-size.js +1 -2
  65. package/cjs/form/file-upload/parts/item/utils/format-file-size.js.map +1 -1
  66. package/cjs/form/file-upload/useFileUpload.d.ts +1 -2
  67. package/cjs/form/file-upload/utils/is-accepted-file-type.js +1 -2
  68. package/cjs/form/file-upload/utils/is-accepted-file-type.js.map +1 -1
  69. package/cjs/form/file-upload/utils/is-accepted-size.js +1 -2
  70. package/cjs/form/file-upload/utils/is-accepted-size.js.map +1 -1
  71. package/cjs/form/radio/useRadio.d.ts +3 -3
  72. package/cjs/form/search/context.d.ts +0 -1
  73. package/cjs/layout/base/PrimitiveAsChildProps.d.ts +0 -1
  74. package/cjs/layout/grid/HGrid.js +4 -1
  75. package/cjs/layout/grid/HGrid.js.map +1 -1
  76. package/cjs/layout/stack/Stack.js +7 -2
  77. package/cjs/layout/stack/Stack.js.map +1 -1
  78. package/cjs/layout/utilities/css.js +2 -3
  79. package/cjs/layout/utilities/css.js.map +1 -1
  80. package/cjs/list/context.d.ts +0 -1
  81. package/cjs/list/types.d.ts +0 -1
  82. package/cjs/modal/ModalUtils.js +3 -3
  83. package/cjs/modal/ModalUtils.js.map +1 -1
  84. package/cjs/modal/types.d.ts +0 -1
  85. package/cjs/overlays/dismissablelayer/DismissableLayer.d.ts +1 -1
  86. package/cjs/overlays/dismissablelayer/util/dispatchCustomEvent.js +2 -2
  87. package/cjs/overlays/dismissablelayer/util/dispatchCustomEvent.js.map +1 -1
  88. package/cjs/overlays/dismissablelayer/util/useEscapeKeydown.js +1 -2
  89. package/cjs/overlays/dismissablelayer/util/useEscapeKeydown.js.map +1 -1
  90. package/cjs/overlays/dismissablelayer/util/useFocusOutside.js +1 -2
  91. package/cjs/overlays/dismissablelayer/util/useFocusOutside.js.map +1 -1
  92. package/cjs/overlays/dismissablelayer/util/usePointerDownOutside.js +1 -2
  93. package/cjs/overlays/dismissablelayer/util/usePointerDownOutside.js.map +1 -1
  94. package/cjs/overlays/floating/Floating.utils.js +2 -3
  95. package/cjs/overlays/floating/Floating.utils.js.map +1 -1
  96. package/cjs/overlays/floating-menu/Menu.d.ts +106 -0
  97. package/cjs/overlays/floating-menu/Menu.js +593 -0
  98. package/cjs/overlays/floating-menu/Menu.js.map +1 -0
  99. package/cjs/overlays/floating-menu/parts/FocusScope.d.ts +22 -0
  100. package/cjs/overlays/floating-menu/parts/FocusScope.js +89 -0
  101. package/cjs/overlays/floating-menu/parts/FocusScope.js.map +1 -0
  102. package/cjs/overlays/floating-menu/parts/RovingFocus.d.ts +9 -0
  103. package/cjs/overlays/floating-menu/parts/RovingFocus.js +112 -0
  104. package/cjs/overlays/floating-menu/parts/RovingFocus.js.map +1 -0
  105. package/cjs/overlays/floating-menu/parts/SlottedDivElement.d.ts +7 -0
  106. package/cjs/overlays/floating-menu/parts/SlottedDivElement.js +46 -0
  107. package/cjs/overlays/floating-menu/parts/SlottedDivElement.js.map +1 -0
  108. package/cjs/slot/merge-props.js +1 -2
  109. package/cjs/slot/merge-props.js.map +1 -1
  110. package/cjs/stepper/context.d.ts +0 -1
  111. package/cjs/table/context.d.ts +0 -1
  112. package/cjs/tabs/Tabs.context.d.ts +1 -2
  113. package/cjs/tabs/parts/tab/useTab.d.ts +1 -2
  114. package/cjs/tabs/parts/tab/useTab.js +1 -2
  115. package/cjs/tabs/parts/tab/useTab.js.map +1 -1
  116. package/cjs/tabs/parts/tablist/useScrollButtons.d.ts +0 -1
  117. package/cjs/tabs/parts/tablist/useScrollButtons.js +1 -2
  118. package/cjs/tabs/parts/tablist/useScrollButtons.js.map +1 -1
  119. package/cjs/tabs/parts/tablist/useTabList.js +3 -3
  120. package/cjs/tabs/parts/tablist/useTabList.js.map +1 -1
  121. package/cjs/tabs/parts/tabpanel/useTabPanel.js +1 -2
  122. package/cjs/tabs/parts/tabpanel/useTabPanel.js.map +1 -1
  123. package/cjs/tabs/useTabs.d.ts +0 -1
  124. package/cjs/tabs/useTabs.js +1 -2
  125. package/cjs/tabs/useTabs.js.map +1 -1
  126. package/cjs/timeline/hooks/usePeriodContext.d.ts +0 -1
  127. package/cjs/timeline/hooks/useRowContext.d.ts +0 -1
  128. package/cjs/timeline/hooks/useTimelineContext.d.ts +0 -1
  129. package/cjs/timeline/period/types.d.ts +0 -1
  130. package/cjs/timeline/zoom/index.d.ts +1 -1
  131. package/cjs/toggle-group/ToggleGroup.context.d.ts +1 -2
  132. package/cjs/toggle-group/parts/useToggleItem.d.ts +1 -2
  133. package/cjs/toggle-group/parts/useToggleItem.js +3 -3
  134. package/cjs/toggle-group/parts/useToggleItem.js.map +1 -1
  135. package/cjs/toggle-group/useToggleGroup.d.ts +0 -1
  136. package/cjs/toggle-group/useToggleGroup.js +1 -2
  137. package/cjs/toggle-group/useToggleGroup.js.map +1 -1
  138. package/cjs/util/composeEventHandlers.d.ts +1 -2
  139. package/cjs/util/composeEventHandlers.js +1 -2
  140. package/cjs/util/composeEventHandlers.js.map +1 -1
  141. package/cjs/util/copy.js +1 -1
  142. package/cjs/util/copy.js.map +1 -1
  143. package/cjs/util/create-context.js +1 -2
  144. package/cjs/util/create-context.js.map +1 -1
  145. package/cjs/util/debounce.js +1 -1
  146. package/cjs/util/debounce.js.map +1 -1
  147. package/cjs/util/hooks/descendants/useDescendant.d.ts +1 -1
  148. package/cjs/util/hooks/descendants/useDescendant.js +1 -2
  149. package/cjs/util/hooks/descendants/useDescendant.js.map +1 -1
  150. package/cjs/util/hooks/descendants/utils.js +4 -4
  151. package/cjs/util/hooks/descendants/utils.js.map +1 -1
  152. package/cjs/util/hooks/useCallbackRef.d.ts +0 -1
  153. package/cjs/util/hooks/useCallbackRef.js +1 -2
  154. package/cjs/util/hooks/useCallbackRef.js.map +1 -1
  155. package/cjs/util/hooks/useControllableState.js +1 -2
  156. package/cjs/util/hooks/useControllableState.js.map +1 -1
  157. package/cjs/util/hooks/useId.js +1 -2
  158. package/cjs/util/hooks/useId.js.map +1 -1
  159. package/cjs/util/hooks/useMergeRefs.d.ts +1 -1
  160. package/cjs/util/hooks/useMergeRefs.js +2 -3
  161. package/cjs/util/hooks/useMergeRefs.js.map +1 -1
  162. package/cjs/util/i18n/get.js +1 -2
  163. package/cjs/util/i18n/get.js.map +1 -1
  164. package/cjs/util/i18n/i18n.context.d.ts +0 -1
  165. package/cjs/util/i18n/i18n.context.js +2 -2
  166. package/cjs/util/i18n/i18n.context.js.map +1 -1
  167. package/cjs/util/i18n/merge.js +1 -2
  168. package/cjs/util/i18n/merge.js.map +1 -1
  169. package/cjs/util/omit.js +1 -2
  170. package/cjs/util/omit.js.map +1 -1
  171. package/cjs/util/types/AsChild.d.ts +0 -1
  172. package/cjs/util/types/AsChildProps.d.ts +0 -1
  173. package/cjs/util/virtualfocus/Context.d.ts +43 -0
  174. package/cjs/util/virtualfocus/Context.js +9 -0
  175. package/cjs/util/virtualfocus/Context.js.map +1 -0
  176. package/cjs/util/virtualfocus/SlottedDivElement.d.ts +7 -0
  177. package/cjs/util/virtualfocus/SlottedDivElement.js +46 -0
  178. package/cjs/util/virtualfocus/SlottedDivElement.js.map +1 -0
  179. package/cjs/util/virtualfocus/VirtualFocus.d.ts +62 -0
  180. package/cjs/util/virtualfocus/VirtualFocus.js +90 -0
  181. package/cjs/util/virtualfocus/VirtualFocus.js.map +1 -0
  182. package/cjs/util/virtualfocus/parts/VirtualFocusAnchor.d.ts +33 -0
  183. package/cjs/util/virtualfocus/parts/VirtualFocusAnchor.js +80 -0
  184. package/cjs/util/virtualfocus/parts/VirtualFocusAnchor.js.map +1 -0
  185. package/cjs/util/virtualfocus/parts/VirtualFocusContent.d.ts +4 -0
  186. package/cjs/util/virtualfocus/parts/VirtualFocusContent.js +45 -0
  187. package/cjs/util/virtualfocus/parts/VirtualFocusContent.js.map +1 -0
  188. package/cjs/util/virtualfocus/parts/VirtualFocusItem.d.ts +18 -0
  189. package/cjs/util/virtualfocus/parts/VirtualFocusItem.js +64 -0
  190. package/cjs/util/virtualfocus/parts/VirtualFocusItem.js.map +1 -0
  191. package/esm/accordion/AccordionContext.d.ts +0 -1
  192. package/esm/collapsible/Collapsible.context.d.ts +0 -1
  193. package/esm/date/context/useDateInputContext.d.ts +0 -1
  194. package/esm/date/datepicker/parts/HeadRow.d.ts +0 -1
  195. package/esm/date/datepicker/parts/Row.d.ts +0 -1
  196. package/esm/date/datepicker/parts/TableHead.d.ts +0 -1
  197. package/esm/date/datepicker/parts/WeekNumber.d.ts +0 -1
  198. package/esm/date/datepicker/types.d.ts +0 -1
  199. package/esm/date/monthpicker/types.d.ts +0 -1
  200. package/esm/dropdown/Menu/index.d.ts +1 -1
  201. package/esm/dropdown/context.d.ts +0 -1
  202. package/esm/expansion-card/context.d.ts +0 -1
  203. package/esm/form/checkbox/useCheckbox.d.ts +3 -3
  204. package/esm/form/combobox/Combobox.d.ts +1 -1
  205. package/esm/form/combobox/Combobox.js.map +1 -1
  206. package/esm/form/combobox/ComboboxProvider.js +3 -1
  207. package/esm/form/combobox/ComboboxProvider.js.map +1 -1
  208. package/esm/form/combobox/FilteredOptions/AddNewOption.d.ts +3 -0
  209. package/esm/form/combobox/FilteredOptions/AddNewOption.js +36 -0
  210. package/esm/form/combobox/FilteredOptions/AddNewOption.js.map +1 -0
  211. package/esm/form/combobox/FilteredOptions/FilteredOptions.js +13 -57
  212. package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  213. package/esm/form/combobox/FilteredOptions/FilteredOptionsItem.d.ts +6 -0
  214. package/esm/form/combobox/FilteredOptions/FilteredOptionsItem.js +38 -0
  215. package/esm/form/combobox/FilteredOptions/FilteredOptionsItem.js.map +1 -0
  216. package/esm/form/combobox/FilteredOptions/LoadingMessage.d.ts +3 -0
  217. package/esm/form/combobox/FilteredOptions/LoadingMessage.js +11 -0
  218. package/esm/form/combobox/FilteredOptions/LoadingMessage.js.map +1 -0
  219. package/esm/form/combobox/FilteredOptions/MaxSelectedMessage.d.ts +3 -0
  220. package/esm/form/combobox/FilteredOptions/MaxSelectedMessage.js +15 -0
  221. package/esm/form/combobox/FilteredOptions/MaxSelectedMessage.js.map +1 -0
  222. package/esm/form/combobox/FilteredOptions/NoSearchHitsMessage.d.ts +3 -0
  223. package/esm/form/combobox/FilteredOptions/NoSearchHitsMessage.js +9 -0
  224. package/esm/form/combobox/FilteredOptions/NoSearchHitsMessage.js.map +1 -0
  225. package/esm/form/combobox/FilteredOptions/filtered-options-util.d.ts +1 -0
  226. package/esm/form/combobox/FilteredOptions/filtered-options-util.js +6 -1
  227. package/esm/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
  228. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +5 -5
  229. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  230. package/esm/form/combobox/Input/Input.context.d.ts +20 -7
  231. package/esm/form/combobox/Input/Input.context.js +7 -13
  232. package/esm/form/combobox/Input/Input.context.js.map +1 -1
  233. package/esm/form/combobox/Input/Input.d.ts +2 -1
  234. package/esm/form/combobox/Input/Input.js +46 -21
  235. package/esm/form/combobox/Input/Input.js.map +1 -1
  236. package/esm/form/combobox/Input/InputController.d.ts +1 -1
  237. package/esm/form/combobox/Input/InputController.js +1 -1
  238. package/esm/form/combobox/Input/InputController.js.map +1 -1
  239. package/esm/form/combobox/types.d.ts +8 -4
  240. package/esm/form/fieldset/context.d.ts +0 -1
  241. package/esm/form/fieldset/useFieldset.d.ts +1 -1
  242. package/esm/form/file-upload/FileUpload.context.d.ts +0 -1
  243. package/esm/form/file-upload/parts/dropzone/dropzone.types.d.ts +0 -1
  244. package/esm/form/file-upload/useFileUpload.d.ts +1 -2
  245. package/esm/form/radio/useRadio.d.ts +3 -3
  246. package/esm/form/search/context.d.ts +0 -1
  247. package/esm/layout/base/PrimitiveAsChildProps.d.ts +0 -1
  248. package/esm/layout/grid/HGrid.js +4 -1
  249. package/esm/layout/grid/HGrid.js.map +1 -1
  250. package/esm/layout/stack/Stack.js +7 -2
  251. package/esm/layout/stack/Stack.js.map +1 -1
  252. package/esm/list/context.d.ts +0 -1
  253. package/esm/list/types.d.ts +0 -1
  254. package/esm/modal/types.d.ts +0 -1
  255. package/esm/overlays/dismissablelayer/DismissableLayer.d.ts +1 -1
  256. package/esm/overlays/floating-menu/Menu.d.ts +106 -0
  257. package/esm/overlays/floating-menu/Menu.js +551 -0
  258. package/esm/overlays/floating-menu/Menu.js.map +1 -0
  259. package/esm/overlays/floating-menu/parts/FocusScope.d.ts +22 -0
  260. package/esm/overlays/floating-menu/parts/FocusScope.js +63 -0
  261. package/esm/overlays/floating-menu/parts/FocusScope.js.map +1 -0
  262. package/esm/overlays/floating-menu/parts/RovingFocus.d.ts +9 -0
  263. package/esm/overlays/floating-menu/parts/RovingFocus.js +86 -0
  264. package/esm/overlays/floating-menu/parts/RovingFocus.js.map +1 -0
  265. package/esm/overlays/floating-menu/parts/SlottedDivElement.d.ts +7 -0
  266. package/esm/overlays/floating-menu/parts/SlottedDivElement.js +20 -0
  267. package/esm/overlays/floating-menu/parts/SlottedDivElement.js.map +1 -0
  268. package/esm/stepper/context.d.ts +0 -1
  269. package/esm/table/context.d.ts +0 -1
  270. package/esm/tabs/Tabs.context.d.ts +1 -2
  271. package/esm/tabs/parts/tab/useTab.d.ts +1 -2
  272. package/esm/tabs/parts/tablist/useScrollButtons.d.ts +0 -1
  273. package/esm/tabs/parts/tablist/useTabList.js +2 -1
  274. package/esm/tabs/parts/tablist/useTabList.js.map +1 -1
  275. package/esm/tabs/useTabs.d.ts +0 -1
  276. package/esm/timeline/hooks/usePeriodContext.d.ts +0 -1
  277. package/esm/timeline/hooks/useRowContext.d.ts +0 -1
  278. package/esm/timeline/hooks/useTimelineContext.d.ts +0 -1
  279. package/esm/timeline/period/types.d.ts +0 -1
  280. package/esm/timeline/zoom/index.d.ts +1 -1
  281. package/esm/toggle-group/ToggleGroup.context.d.ts +1 -2
  282. package/esm/toggle-group/parts/useToggleItem.d.ts +1 -2
  283. package/esm/toggle-group/parts/useToggleItem.js +2 -1
  284. package/esm/toggle-group/parts/useToggleItem.js.map +1 -1
  285. package/esm/toggle-group/useToggleGroup.d.ts +0 -1
  286. package/esm/util/composeEventHandlers.d.ts +1 -2
  287. package/esm/util/hooks/descendants/useDescendant.d.ts +1 -1
  288. package/esm/util/hooks/useCallbackRef.d.ts +0 -1
  289. package/esm/util/hooks/useMergeRefs.d.ts +1 -1
  290. package/esm/util/i18n/i18n.context.d.ts +0 -1
  291. package/esm/util/types/AsChild.d.ts +0 -1
  292. package/esm/util/types/AsChildProps.d.ts +0 -1
  293. package/esm/util/virtualfocus/Context.d.ts +43 -0
  294. package/esm/util/virtualfocus/Context.js +5 -0
  295. package/esm/util/virtualfocus/Context.js.map +1 -0
  296. package/esm/util/virtualfocus/SlottedDivElement.d.ts +7 -0
  297. package/esm/util/virtualfocus/SlottedDivElement.js +20 -0
  298. package/esm/util/virtualfocus/SlottedDivElement.js.map +1 -0
  299. package/esm/util/virtualfocus/VirtualFocus.d.ts +62 -0
  300. package/esm/util/virtualfocus/VirtualFocus.js +63 -0
  301. package/esm/util/virtualfocus/VirtualFocus.js.map +1 -0
  302. package/esm/util/virtualfocus/parts/VirtualFocusAnchor.d.ts +33 -0
  303. package/esm/util/virtualfocus/parts/VirtualFocusAnchor.js +54 -0
  304. package/esm/util/virtualfocus/parts/VirtualFocusAnchor.js.map +1 -0
  305. package/esm/util/virtualfocus/parts/VirtualFocusContent.d.ts +4 -0
  306. package/esm/util/virtualfocus/parts/VirtualFocusContent.js +19 -0
  307. package/esm/util/virtualfocus/parts/VirtualFocusContent.js.map +1 -0
  308. package/esm/util/virtualfocus/parts/VirtualFocusItem.d.ts +18 -0
  309. package/esm/util/virtualfocus/parts/VirtualFocusItem.js +38 -0
  310. package/esm/util/virtualfocus/parts/VirtualFocusItem.js.map +1 -0
  311. package/package.json +3 -3
  312. package/src/form/combobox/Combobox.tsx +4 -1
  313. package/src/form/combobox/ComboboxProvider.tsx +3 -0
  314. package/src/form/combobox/FilteredOptions/AddNewOption.tsx +63 -0
  315. package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +11 -121
  316. package/src/form/combobox/FilteredOptions/FilteredOptionsItem.tsx +73 -0
  317. package/src/form/combobox/FilteredOptions/LoadingMessage.tsx +20 -0
  318. package/src/form/combobox/FilteredOptions/MaxSelectedMessage.tsx +27 -0
  319. package/src/form/combobox/FilteredOptions/NoSearchHitsMessage.tsx +19 -0
  320. package/src/form/combobox/FilteredOptions/filtered-options-util.ts +9 -1
  321. package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +8 -5
  322. package/src/form/combobox/Input/Input.context.tsx +27 -25
  323. package/src/form/combobox/Input/Input.tsx +60 -29
  324. package/src/form/combobox/Input/InputController.tsx +2 -0
  325. package/src/form/combobox/__tests__/combobox.test.tsx +174 -66
  326. package/src/form/combobox/types.ts +11 -7
  327. package/src/layout/grid/HGrid.tsx +4 -1
  328. package/src/layout/stack/Stack.tsx +6 -2
  329. package/src/overlays/floating-menu/Menu.tsx +1177 -0
  330. package/src/overlays/floating-menu/parts/FocusScope.tsx +84 -0
  331. package/src/overlays/floating-menu/parts/RovingFocus.tsx +121 -0
  332. package/src/overlays/floating-menu/parts/SlottedDivElement.tsx +17 -0
  333. package/src/tabs/parts/tablist/useTabList.ts +4 -1
  334. package/src/toggle-group/parts/useToggleItem.ts +4 -1
  335. package/src/util/composeEventHandlers.ts +1 -1
  336. package/src/util/virtualfocus/Context.tsx +27 -0
  337. package/src/util/virtualfocus/SlottedDivElement.tsx +17 -0
  338. package/src/util/virtualfocus/VirtualFocus.tsx +89 -0
  339. package/src/util/virtualfocus/parts/VirtualFocusAnchor.tsx +102 -0
  340. package/src/util/virtualfocus/parts/VirtualFocusContent.tsx +17 -0
  341. 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).startsWith(normalizeText(value ?? ""));
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
- filteredOptionsUtils.normalizeText(searchTerm) !== "" &&
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}${filteredOptions[0].label.substring(searchTerm.length)}`,
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?: string;
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: ChangeEventHandler<HTMLInputElement>;
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
- const InputProvider = ({ children, value: props }) => {
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
- (event: ChangeEvent<HTMLInputElement>) => {
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?.(null, "");
91
- setValue("");
92
+ externalOnChange?.("");
93
+ setInternalValue("");
92
94
  setSearchTerm("");
93
95
  },
94
- [externalOnChange, onClear, setValue],
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 { clearInput, inputProps, onChange, size, value } = useInputContext();
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 (shouldAutocomplete && isTextInSelectedOptions(value)) {
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
- // Check that cursor position is at the end of the input field,
137
- // so we don't interfere with text editing
138
- if (e.target.selectionStart === value?.length) {
139
- e.preventDefault();
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(event);
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={ref}
217
+ ref={mergedRefs}
188
218
  value={value}
189
219
  onBlur={() => virtualFocus.moveFocusToTop()}
190
- onChange={onChangeHandler}
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
- test("Should show loading icon when loading (used for async search)", async () => {
75
- render(<App options={[]} isListOpen isLoading />);
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
- expect(await screen.findByText("Søker...")).toBeInTheDocument();
78
- });
79
- });
79
+ expect(await screen.findByText("Søker...")).toBeInTheDocument();
80
+ });
80
81
 
81
- describe("Combobox state-handling", () => {
82
- test("Should not select previous focused element when closes", async () => {
83
- render(<App options={options} />);
82
+ test("Should not select previous focused element when closes", async () => {
83
+ render(<App options={options} />);
84
84
 
85
- await act(async () => {
86
- await userEvent.click(
87
- screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?" }),
88
- );
89
- });
90
- await act(async () => {
91
- await userEvent.type(
92
- screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?" }),
93
- "ban",
94
- );
95
- await userEvent.keyboard("{ArrowDown}");
96
- await userEvent.keyboard("{ArrowUp}");
97
- await userEvent.keyboard("{Enter}");
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
- expect(screen.queryByRole("button", { name: "banana slett" })).toBeNull();
101
- });
107
+ test("Should reset list when resetting input (ESC)", async () => {
108
+ render(<App options={options} />);
102
109
 
103
- test("Should reset list when resetting input (ESC)", async () => {
104
- render(<App options={options} />);
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
- await act(async () => {
107
- await userEvent.click(
108
- screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?" }),
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
- await act(async () => {
112
- await userEvent.type(
113
- screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?" }),
114
- "apple",
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
- await userEvent.keyboard("{ArrowDown}");
117
- await userEvent.keyboard("{Escape}");
118
- await userEvent.keyboard("{ArrowDown}");
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
- expect(
122
- await screen.findByRole("option", { name: "banana" }),
123
- ).toBeInTheDocument();
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
- test("Should handle complex options with label and value", async () => {
127
- const onToggleSelected = vi.fn();
128
- render(
129
- <App
130
- options={[
131
- { label: "Hjelpemidler [HJE]", value: "HJE" },
132
- { label: "Oppfølging [OPP]", value: "OPP" },
133
- { label: "Sykepenger [SYK]", value: "SYK" },
134
- { label: "Sykemelding [SYM]", value: "SYM" },
135
- ]}
136
- onToggleSelected={onToggleSelected}
137
- />,
138
- );
139
-
140
- expect(screen.getByRole("combobox")).toBeInTheDocument();
141
- const bananaOption = screen.getByRole("option", {
142
- name: "Hjelpemidler [HJE]",
143
- selected: false,
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
- await act(async () => {
146
- await userEvent.click(bananaOption);
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, { ChangeEvent, InputHTMLAttributes } from "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<InputHTMLAttributes<HTMLInputElement>, "size" | "onChange" | "value"> {
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 event
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
  }