@navikt/ds-react 6.12.0 → 6.14.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 (279) hide show
  1. package/cjs/accordion/AccordionContext.d.ts +0 -1
  2. package/cjs/alert/Alert.d.ts +4 -4
  3. package/cjs/collapsible/Collapsible.context.d.ts +0 -1
  4. package/cjs/date/context/useDateInputContext.d.ts +0 -1
  5. package/cjs/date/datepicker/parts/HeadRow.d.ts +0 -1
  6. package/cjs/date/datepicker/parts/HeadRow.js +2 -3
  7. package/cjs/date/datepicker/parts/HeadRow.js.map +1 -1
  8. package/cjs/date/datepicker/parts/Row.d.ts +0 -1
  9. package/cjs/date/datepicker/parts/TableHead.d.ts +0 -1
  10. package/cjs/date/datepicker/parts/WeekNumber.d.ts +0 -1
  11. package/cjs/date/datepicker/types.d.ts +0 -1
  12. package/cjs/date/monthpicker/types.d.ts +0 -1
  13. package/cjs/date/utils/check-dates.js +2 -2
  14. package/cjs/date/utils/check-dates.js.map +1 -1
  15. package/cjs/date/utils/get-initial-year.js +1 -2
  16. package/cjs/date/utils/get-initial-year.js.map +1 -1
  17. package/cjs/date/utils/get-month-weeks.js +2 -3
  18. package/cjs/date/utils/get-month-weeks.js.map +1 -1
  19. package/cjs/date/utils/is-match.js +2 -3
  20. package/cjs/date/utils/is-match.js.map +1 -1
  21. package/cjs/dropdown/Menu/index.d.ts +1 -1
  22. package/cjs/dropdown/context.d.ts +0 -1
  23. package/cjs/expansion-card/context.d.ts +0 -1
  24. package/cjs/form/checkbox/useCheckbox.d.ts +3 -3
  25. package/cjs/form/combobox/Combobox.d.ts +1 -1
  26. package/cjs/form/combobox/Combobox.js.map +1 -1
  27. package/cjs/form/combobox/ComboboxProvider.js +3 -1
  28. package/cjs/form/combobox/ComboboxProvider.js.map +1 -1
  29. package/cjs/form/combobox/FilteredOptions/filtered-options-util.d.ts +1 -0
  30. package/cjs/form/combobox/FilteredOptions/filtered-options-util.js +6 -1
  31. package/cjs/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
  32. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +5 -5
  33. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  34. package/cjs/form/combobox/Input/Input.context.d.ts +20 -7
  35. package/cjs/form/combobox/Input/Input.context.js +6 -12
  36. package/cjs/form/combobox/Input/Input.context.js.map +1 -1
  37. package/cjs/form/combobox/Input/Input.d.ts +1 -1
  38. package/cjs/form/combobox/Input/Input.js +42 -18
  39. package/cjs/form/combobox/Input/Input.js.map +1 -1
  40. package/cjs/form/combobox/Input/InputController.d.ts +1 -1
  41. package/cjs/form/combobox/Input/InputController.js.map +1 -1
  42. package/cjs/form/combobox/types.d.ts +8 -4
  43. package/cjs/form/fieldset/context.d.ts +0 -1
  44. package/cjs/form/fieldset/useFieldset.d.ts +1 -1
  45. package/cjs/form/file-upload/FileUpload.context.d.ts +0 -1
  46. package/cjs/form/file-upload/parts/dropzone/dropzone.types.d.ts +0 -1
  47. package/cjs/form/file-upload/parts/item/utils/format-file-size.js +1 -2
  48. package/cjs/form/file-upload/parts/item/utils/format-file-size.js.map +1 -1
  49. package/cjs/form/file-upload/useFileUpload.d.ts +1 -2
  50. package/cjs/form/file-upload/utils/is-accepted-file-type.js +1 -2
  51. package/cjs/form/file-upload/utils/is-accepted-file-type.js.map +1 -1
  52. package/cjs/form/file-upload/utils/is-accepted-size.js +1 -2
  53. package/cjs/form/file-upload/utils/is-accepted-size.js.map +1 -1
  54. package/cjs/form/radio/useRadio.d.ts +3 -3
  55. package/cjs/form/search/context.d.ts +0 -1
  56. package/cjs/layout/base/PrimitiveAsChildProps.d.ts +0 -1
  57. package/cjs/layout/grid/HGrid.js +4 -1
  58. package/cjs/layout/grid/HGrid.js.map +1 -1
  59. package/cjs/layout/stack/Stack.js +7 -2
  60. package/cjs/layout/stack/Stack.js.map +1 -1
  61. package/cjs/layout/utilities/css.js +2 -3
  62. package/cjs/layout/utilities/css.js.map +1 -1
  63. package/cjs/list/context.d.ts +0 -1
  64. package/cjs/list/types.d.ts +0 -1
  65. package/cjs/modal/Modal.js +3 -2
  66. package/cjs/modal/Modal.js.map +1 -1
  67. package/cjs/modal/ModalUtils.js +3 -3
  68. package/cjs/modal/ModalUtils.js.map +1 -1
  69. package/cjs/modal/types.d.ts +5 -1
  70. package/cjs/overlays/dismissablelayer/DismissableLayer.d.ts +1 -1
  71. package/cjs/overlays/dismissablelayer/util/dispatchCustomEvent.js +2 -2
  72. package/cjs/overlays/dismissablelayer/util/dispatchCustomEvent.js.map +1 -1
  73. package/cjs/overlays/dismissablelayer/util/useEscapeKeydown.js +1 -2
  74. package/cjs/overlays/dismissablelayer/util/useEscapeKeydown.js.map +1 -1
  75. package/cjs/overlays/dismissablelayer/util/useFocusOutside.js +1 -2
  76. package/cjs/overlays/dismissablelayer/util/useFocusOutside.js.map +1 -1
  77. package/cjs/overlays/dismissablelayer/util/usePointerDownOutside.js +1 -2
  78. package/cjs/overlays/dismissablelayer/util/usePointerDownOutside.js.map +1 -1
  79. package/cjs/overlays/floating/Floating.utils.js +2 -3
  80. package/cjs/overlays/floating/Floating.utils.js.map +1 -1
  81. package/cjs/slot/merge-props.js +1 -2
  82. package/cjs/slot/merge-props.js.map +1 -1
  83. package/cjs/stepper/context.d.ts +0 -1
  84. package/cjs/table/context.d.ts +0 -1
  85. package/cjs/tabs/Tabs.context.d.ts +1 -2
  86. package/cjs/tabs/parts/tab/useTab.d.ts +1 -2
  87. package/cjs/tabs/parts/tab/useTab.js +1 -2
  88. package/cjs/tabs/parts/tab/useTab.js.map +1 -1
  89. package/cjs/tabs/parts/tablist/useScrollButtons.d.ts +0 -1
  90. package/cjs/tabs/parts/tablist/useScrollButtons.js +1 -2
  91. package/cjs/tabs/parts/tablist/useScrollButtons.js.map +1 -1
  92. package/cjs/tabs/parts/tablist/useTabList.js +3 -3
  93. package/cjs/tabs/parts/tablist/useTabList.js.map +1 -1
  94. package/cjs/tabs/parts/tabpanel/useTabPanel.js +1 -2
  95. package/cjs/tabs/parts/tabpanel/useTabPanel.js.map +1 -1
  96. package/cjs/tabs/useTabs.d.ts +0 -1
  97. package/cjs/tabs/useTabs.js +1 -2
  98. package/cjs/tabs/useTabs.js.map +1 -1
  99. package/cjs/timeline/hooks/usePeriodContext.d.ts +0 -1
  100. package/cjs/timeline/hooks/useRowContext.d.ts +0 -1
  101. package/cjs/timeline/hooks/useTimelineContext.d.ts +0 -1
  102. package/cjs/timeline/period/types.d.ts +0 -1
  103. package/cjs/timeline/zoom/index.d.ts +1 -1
  104. package/cjs/toggle-group/ToggleGroup.context.d.ts +1 -2
  105. package/cjs/toggle-group/parts/useToggleItem.d.ts +1 -2
  106. package/cjs/toggle-group/parts/useToggleItem.js +3 -3
  107. package/cjs/toggle-group/parts/useToggleItem.js.map +1 -1
  108. package/cjs/toggle-group/useToggleGroup.d.ts +0 -1
  109. package/cjs/toggle-group/useToggleGroup.js +1 -2
  110. package/cjs/toggle-group/useToggleGroup.js.map +1 -1
  111. package/cjs/util/composeEventHandlers.d.ts +0 -1
  112. package/cjs/util/composeEventHandlers.js +1 -2
  113. package/cjs/util/composeEventHandlers.js.map +1 -1
  114. package/cjs/util/copy.js +1 -1
  115. package/cjs/util/copy.js.map +1 -1
  116. package/cjs/util/create-context.js +1 -2
  117. package/cjs/util/create-context.js.map +1 -1
  118. package/cjs/util/debounce.js +1 -1
  119. package/cjs/util/debounce.js.map +1 -1
  120. package/cjs/util/hooks/descendants/useDescendant.d.ts +1 -1
  121. package/cjs/util/hooks/descendants/useDescendant.js +1 -2
  122. package/cjs/util/hooks/descendants/useDescendant.js.map +1 -1
  123. package/cjs/util/hooks/descendants/utils.js +4 -4
  124. package/cjs/util/hooks/descendants/utils.js.map +1 -1
  125. package/cjs/util/hooks/useCallbackRef.d.ts +0 -1
  126. package/cjs/util/hooks/useCallbackRef.js +1 -2
  127. package/cjs/util/hooks/useCallbackRef.js.map +1 -1
  128. package/cjs/util/hooks/useControllableState.js +1 -2
  129. package/cjs/util/hooks/useControllableState.js.map +1 -1
  130. package/cjs/util/hooks/useId.js +1 -2
  131. package/cjs/util/hooks/useId.js.map +1 -1
  132. package/cjs/util/hooks/useMergeRefs.d.ts +1 -1
  133. package/cjs/util/hooks/useMergeRefs.js +2 -3
  134. package/cjs/util/hooks/useMergeRefs.js.map +1 -1
  135. package/cjs/util/i18n/get.js +1 -2
  136. package/cjs/util/i18n/get.js.map +1 -1
  137. package/cjs/util/i18n/i18n.context.d.ts +0 -1
  138. package/cjs/util/i18n/i18n.context.js +2 -2
  139. package/cjs/util/i18n/i18n.context.js.map +1 -1
  140. package/cjs/util/i18n/merge.js +1 -2
  141. package/cjs/util/i18n/merge.js.map +1 -1
  142. package/cjs/util/omit.js +1 -2
  143. package/cjs/util/omit.js.map +1 -1
  144. package/cjs/util/types/AsChild.d.ts +0 -1
  145. package/cjs/util/types/AsChildProps.d.ts +0 -1
  146. package/cjs/util/virtualfocus/Context.d.ts +43 -0
  147. package/cjs/util/virtualfocus/Context.js +9 -0
  148. package/cjs/util/virtualfocus/Context.js.map +1 -0
  149. package/cjs/util/virtualfocus/SlottedDivElement.d.ts +7 -0
  150. package/cjs/util/virtualfocus/SlottedDivElement.js +46 -0
  151. package/cjs/util/virtualfocus/SlottedDivElement.js.map +1 -0
  152. package/cjs/util/virtualfocus/VirtualFocus.d.ts +62 -0
  153. package/cjs/util/virtualfocus/VirtualFocus.js +90 -0
  154. package/cjs/util/virtualfocus/VirtualFocus.js.map +1 -0
  155. package/cjs/util/virtualfocus/parts/VirtualFocusAnchor.d.ts +33 -0
  156. package/cjs/util/virtualfocus/parts/VirtualFocusAnchor.js +80 -0
  157. package/cjs/util/virtualfocus/parts/VirtualFocusAnchor.js.map +1 -0
  158. package/cjs/util/virtualfocus/parts/VirtualFocusContent.d.ts +4 -0
  159. package/cjs/util/virtualfocus/parts/VirtualFocusContent.js +45 -0
  160. package/cjs/util/virtualfocus/parts/VirtualFocusContent.js.map +1 -0
  161. package/cjs/util/virtualfocus/parts/VirtualFocusItem.d.ts +18 -0
  162. package/cjs/util/virtualfocus/parts/VirtualFocusItem.js +64 -0
  163. package/cjs/util/virtualfocus/parts/VirtualFocusItem.js.map +1 -0
  164. package/esm/accordion/AccordionContext.d.ts +0 -1
  165. package/esm/alert/Alert.d.ts +4 -4
  166. package/esm/collapsible/Collapsible.context.d.ts +0 -1
  167. package/esm/date/context/useDateInputContext.d.ts +0 -1
  168. package/esm/date/datepicker/parts/HeadRow.d.ts +0 -1
  169. package/esm/date/datepicker/parts/Row.d.ts +0 -1
  170. package/esm/date/datepicker/parts/TableHead.d.ts +0 -1
  171. package/esm/date/datepicker/parts/WeekNumber.d.ts +0 -1
  172. package/esm/date/datepicker/types.d.ts +0 -1
  173. package/esm/date/monthpicker/types.d.ts +0 -1
  174. package/esm/dropdown/Menu/index.d.ts +1 -1
  175. package/esm/dropdown/context.d.ts +0 -1
  176. package/esm/expansion-card/context.d.ts +0 -1
  177. package/esm/form/checkbox/useCheckbox.d.ts +3 -3
  178. package/esm/form/combobox/Combobox.d.ts +1 -1
  179. package/esm/form/combobox/Combobox.js.map +1 -1
  180. package/esm/form/combobox/ComboboxProvider.js +3 -1
  181. package/esm/form/combobox/ComboboxProvider.js.map +1 -1
  182. package/esm/form/combobox/FilteredOptions/filtered-options-util.d.ts +1 -0
  183. package/esm/form/combobox/FilteredOptions/filtered-options-util.js +6 -1
  184. package/esm/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
  185. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +5 -5
  186. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  187. package/esm/form/combobox/Input/Input.context.d.ts +20 -7
  188. package/esm/form/combobox/Input/Input.context.js +7 -13
  189. package/esm/form/combobox/Input/Input.context.js.map +1 -1
  190. package/esm/form/combobox/Input/Input.d.ts +1 -1
  191. package/esm/form/combobox/Input/Input.js +43 -19
  192. package/esm/form/combobox/Input/Input.js.map +1 -1
  193. package/esm/form/combobox/Input/InputController.d.ts +1 -1
  194. package/esm/form/combobox/Input/InputController.js.map +1 -1
  195. package/esm/form/combobox/types.d.ts +8 -4
  196. package/esm/form/fieldset/context.d.ts +0 -1
  197. package/esm/form/fieldset/useFieldset.d.ts +1 -1
  198. package/esm/form/file-upload/FileUpload.context.d.ts +0 -1
  199. package/esm/form/file-upload/parts/dropzone/dropzone.types.d.ts +0 -1
  200. package/esm/form/file-upload/useFileUpload.d.ts +1 -2
  201. package/esm/form/radio/useRadio.d.ts +3 -3
  202. package/esm/form/search/context.d.ts +0 -1
  203. package/esm/layout/base/PrimitiveAsChildProps.d.ts +0 -1
  204. package/esm/layout/grid/HGrid.js +4 -1
  205. package/esm/layout/grid/HGrid.js.map +1 -1
  206. package/esm/layout/stack/Stack.js +7 -2
  207. package/esm/layout/stack/Stack.js.map +1 -1
  208. package/esm/list/context.d.ts +0 -1
  209. package/esm/list/types.d.ts +0 -1
  210. package/esm/modal/Modal.js +3 -2
  211. package/esm/modal/Modal.js.map +1 -1
  212. package/esm/modal/types.d.ts +5 -1
  213. package/esm/overlays/dismissablelayer/DismissableLayer.d.ts +1 -1
  214. package/esm/stepper/context.d.ts +0 -1
  215. package/esm/table/context.d.ts +0 -1
  216. package/esm/tabs/Tabs.context.d.ts +1 -2
  217. package/esm/tabs/parts/tab/useTab.d.ts +1 -2
  218. package/esm/tabs/parts/tablist/useScrollButtons.d.ts +0 -1
  219. package/esm/tabs/parts/tablist/useTabList.js +2 -1
  220. package/esm/tabs/parts/tablist/useTabList.js.map +1 -1
  221. package/esm/tabs/useTabs.d.ts +0 -1
  222. package/esm/timeline/hooks/usePeriodContext.d.ts +0 -1
  223. package/esm/timeline/hooks/useRowContext.d.ts +0 -1
  224. package/esm/timeline/hooks/useTimelineContext.d.ts +0 -1
  225. package/esm/timeline/period/types.d.ts +0 -1
  226. package/esm/timeline/zoom/index.d.ts +1 -1
  227. package/esm/toggle-group/ToggleGroup.context.d.ts +1 -2
  228. package/esm/toggle-group/parts/useToggleItem.d.ts +1 -2
  229. package/esm/toggle-group/parts/useToggleItem.js +2 -1
  230. package/esm/toggle-group/parts/useToggleItem.js.map +1 -1
  231. package/esm/toggle-group/useToggleGroup.d.ts +0 -1
  232. package/esm/util/composeEventHandlers.d.ts +0 -1
  233. package/esm/util/hooks/descendants/useDescendant.d.ts +1 -1
  234. package/esm/util/hooks/useCallbackRef.d.ts +0 -1
  235. package/esm/util/hooks/useMergeRefs.d.ts +1 -1
  236. package/esm/util/i18n/i18n.context.d.ts +0 -1
  237. package/esm/util/types/AsChild.d.ts +0 -1
  238. package/esm/util/types/AsChildProps.d.ts +0 -1
  239. package/esm/util/virtualfocus/Context.d.ts +43 -0
  240. package/esm/util/virtualfocus/Context.js +5 -0
  241. package/esm/util/virtualfocus/Context.js.map +1 -0
  242. package/esm/util/virtualfocus/SlottedDivElement.d.ts +7 -0
  243. package/esm/util/virtualfocus/SlottedDivElement.js +20 -0
  244. package/esm/util/virtualfocus/SlottedDivElement.js.map +1 -0
  245. package/esm/util/virtualfocus/VirtualFocus.d.ts +62 -0
  246. package/esm/util/virtualfocus/VirtualFocus.js +63 -0
  247. package/esm/util/virtualfocus/VirtualFocus.js.map +1 -0
  248. package/esm/util/virtualfocus/parts/VirtualFocusAnchor.d.ts +33 -0
  249. package/esm/util/virtualfocus/parts/VirtualFocusAnchor.js +54 -0
  250. package/esm/util/virtualfocus/parts/VirtualFocusAnchor.js.map +1 -0
  251. package/esm/util/virtualfocus/parts/VirtualFocusContent.d.ts +4 -0
  252. package/esm/util/virtualfocus/parts/VirtualFocusContent.js +19 -0
  253. package/esm/util/virtualfocus/parts/VirtualFocusContent.js.map +1 -0
  254. package/esm/util/virtualfocus/parts/VirtualFocusItem.d.ts +18 -0
  255. package/esm/util/virtualfocus/parts/VirtualFocusItem.js +38 -0
  256. package/esm/util/virtualfocus/parts/VirtualFocusItem.js.map +1 -0
  257. package/package.json +3 -3
  258. package/src/alert/Alert.tsx +4 -4
  259. package/src/form/combobox/Combobox.tsx +4 -1
  260. package/src/form/combobox/ComboboxProvider.tsx +3 -0
  261. package/src/form/combobox/FilteredOptions/filtered-options-util.ts +9 -1
  262. package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +8 -5
  263. package/src/form/combobox/Input/Input.context.tsx +27 -25
  264. package/src/form/combobox/Input/Input.tsx +56 -27
  265. package/src/form/combobox/Input/InputController.tsx +1 -0
  266. package/src/form/combobox/__tests__/combobox.test.tsx +174 -66
  267. package/src/form/combobox/types.ts +11 -7
  268. package/src/layout/grid/HGrid.tsx +4 -1
  269. package/src/layout/stack/Stack.tsx +6 -2
  270. package/src/modal/Modal.tsx +3 -1
  271. package/src/modal/types.ts +5 -0
  272. package/src/tabs/parts/tablist/useTabList.ts +4 -1
  273. package/src/toggle-group/parts/useToggleItem.ts +4 -1
  274. package/src/util/virtualfocus/Context.tsx +27 -0
  275. package/src/util/virtualfocus/SlottedDivElement.tsx +17 -0
  276. package/src/util/virtualfocus/VirtualFocus.tsx +89 -0
  277. package/src/util/virtualfocus/parts/VirtualFocusAnchor.tsx +102 -0
  278. package/src/util/virtualfocus/parts/VirtualFocusContent.tsx +17 -0
  279. package/src/util/virtualfocus/parts/VirtualFocusItem.tsx +60 -0
@@ -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,18 +1,19 @@
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;
18
19
  value?: string;
@@ -20,7 +21,17 @@ interface InputProps
20
21
 
21
22
  const Input = forwardRef<HTMLInputElement, InputProps>(
22
23
  ({ inputClassName, ...rest }, ref) => {
23
- const { clearInput, inputProps, onChange, size, value } = useInputContext();
24
+ const internalRef = useRef<HTMLInputElement>(null);
25
+ const mergedRefs = useMergeRefs(ref, internalRef);
26
+ const {
27
+ clearInput,
28
+ inputProps,
29
+ onChange,
30
+ size,
31
+ value,
32
+ searchTerm,
33
+ setValue,
34
+ } = useInputContext();
24
35
  const {
25
36
  selectedOptions,
26
37
  removeSelectedOption,
@@ -56,7 +67,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
56
67
  if (!isMultiSelect && !isTextInSelectedOptions(currentOption.label)) {
57
68
  toggleIsListOpen(false);
58
69
  }
59
- } else if (shouldAutocomplete && isTextInSelectedOptions(value)) {
70
+ } else if (isTextInSelectedOptions(value)) {
60
71
  event.preventDefault();
61
72
  // Trying to set the same value that is already set, so just clearing the input
62
73
  clearInput(event);
@@ -67,13 +78,13 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
67
78
  allowNewValues && isValueNew
68
79
  ? { label: value, value }
69
80
  : filteredOptions[0];
81
+
82
+ if (!selectedValue) {
83
+ return;
84
+ }
85
+
70
86
  toggleOption(selectedValue, event);
71
- if (
72
- !isMultiSelect &&
73
- !isTextInSelectedOptions(
74
- filteredOptions[0].label || selectedValue.label,
75
- )
76
- ) {
87
+ if (!isMultiSelect && !isTextInSelectedOptions(selectedValue.label)) {
77
88
  toggleIsListOpen(false);
78
89
  }
79
90
  }
@@ -96,10 +107,6 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
96
107
  const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
97
108
  e.preventDefault();
98
109
  switch (e.key) {
99
- case "Escape":
100
- clearInput(e);
101
- toggleIsListOpen(false);
102
- break;
103
110
  case "Enter":
104
111
  case "Accept":
105
112
  onEnter(e);
@@ -118,7 +125,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
118
125
  };
119
126
 
120
127
  const handleKeyDown = useCallback(
121
- (e) => {
128
+ (e: React.KeyboardEvent<HTMLInputElement>) => {
122
129
  setIsMouseLastUsedInputDevice(false);
123
130
  if (e.key === "Backspace") {
124
131
  if (value === "") {
@@ -132,17 +139,34 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
132
139
  if (activeDecendantId || value) {
133
140
  e.preventDefault();
134
141
  }
142
+ } else if (e.key === "Escape") {
143
+ if (isListOpen || value) {
144
+ e.preventDefault(); // Prevents closing an encasing Modal, as Combobox reacts on keyup.
145
+ clearInput(e);
146
+ toggleIsListOpen(false);
147
+ }
148
+ } else if (["ArrowLeft", "ArrowRight"].includes(e.key)) {
149
+ /**
150
+ * In case user has an active selection and 'completes' the selection with ArrowLeft or ArrowRight
151
+ * we need to make sure to update the filter.
152
+ */
153
+ if (value !== "" && value !== searchTerm) {
154
+ onChange(value);
155
+ }
135
156
  } 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();
157
+ // Reset the value to the search term to cancel autocomplete
158
+ // if the user moves focus down to the FilteredOptions
159
+ if (value !== searchTerm) {
160
+ setValue(searchTerm);
144
161
  }
162
+ if (virtualFocus.activeElement === null || !isListOpen) {
163
+ toggleIsListOpen(true);
164
+ }
165
+ virtualFocus.moveFocusDown();
145
166
  } else if (e.key === "ArrowUp") {
167
+ if (value !== "" && value !== searchTerm) {
168
+ onChange(value);
169
+ }
146
170
  // Check that the FilteredOptions list is open and has virtual focus.
147
171
  // Otherwise ignore keystrokes, so it doesn't interfere with text editing
148
172
  if (isListOpen && activeDecendantId) {
@@ -161,13 +185,17 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
161
185
  isListOpen,
162
186
  activeDecendantId,
163
187
  setIsMouseLastUsedInputDevice,
188
+ clearInput,
164
189
  toggleIsListOpen,
190
+ onChange,
165
191
  virtualFocus,
192
+ setValue,
193
+ searchTerm,
166
194
  ],
167
195
  );
168
196
 
169
197
  const onChangeHandler = useCallback(
170
- (event: ChangeEvent<HTMLInputElement>) => {
198
+ (event: React.ChangeEvent<HTMLInputElement>) => {
171
199
  const newValue = event.target.value;
172
200
  if (newValue && newValue !== "") {
173
201
  toggleIsListOpen(true);
@@ -175,7 +203,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
175
203
  toggleIsListOpen(false);
176
204
  }
177
205
  virtualFocus.moveFocusToTop();
178
- onChange(event);
206
+ onChange(newValue);
179
207
  },
180
208
  [filteredOptions.length, virtualFocus, onChange, toggleIsListOpen],
181
209
  );
@@ -184,10 +212,11 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
184
212
  <input
185
213
  {...rest}
186
214
  {...omit(inputProps, ["aria-invalid"])}
187
- ref={ref}
215
+ ref={mergedRefs}
188
216
  value={value}
189
217
  onBlur={() => virtualFocus.moveFocusToTop()}
190
- onChange={onChangeHandler}
218
+ onClick={() => value !== searchTerm && onChange(value)}
219
+ onInput={onChangeHandler}
191
220
  type="text"
192
221
  role="combobox"
193
222
  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 {
@@ -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
  }
@@ -92,7 +92,10 @@ export const HGrid: OverridableComponent<HGridProps, HTMLDivElement> =
92
92
  <Comp
93
93
  {...omit(rest, PRIMITIVE_PROPS)}
94
94
  ref={ref}
95
- className={cl("navds-hgrid", className)}
95
+ className={cl("navds-hgrid", className, {
96
+ "navds-hgrid-gap": gap,
97
+ "navds-hgrid-align": align,
98
+ })}
96
99
  style={styles}
97
100
  >
98
101
  {children}
@@ -73,7 +73,7 @@ export const Stack: OverridableComponent<StackProps, HTMLDivElement> =
73
73
  children,
74
74
  className,
75
75
  as: Component = "div",
76
- align = "stretch",
76
+ align,
77
77
  justify,
78
78
  wrap = true,
79
79
  gap,
@@ -86,7 +86,6 @@ export const Stack: OverridableComponent<StackProps, HTMLDivElement> =
86
86
  ) => {
87
87
  const style: React.CSSProperties = {
88
88
  ..._style,
89
- "--__ac-stack-wrap": wrap ? "wrap" : "nowrap",
90
89
  ...getResponsiveProps(`stack`, "gap", "spacing", gap),
91
90
  ...getResponsiveValue(`stack`, "direction", direction),
92
91
  ...getResponsiveValue(`stack`, "align", align),
@@ -104,6 +103,11 @@ export const Stack: OverridableComponent<StackProps, HTMLDivElement> =
104
103
  className={cl("navds-stack", className, {
105
104
  "navds-vstack": direction === "column",
106
105
  "navds-hstack": direction === "row",
106
+ "navds-stack-gap": gap,
107
+ "navds-stack-align": align,
108
+ "navds-stack-justify": justify,
109
+ "navds-stack-direction": direction,
110
+ "navds-stack-wrap": wrap,
107
111
  })}
108
112
  >
109
113
  {children}
@@ -88,6 +88,7 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
88
88
  onCancel,
89
89
  closeOnBackdropClick,
90
90
  width,
91
+ placement,
91
92
  portal,
92
93
  className,
93
94
  "aria-labelledby": ariaLabelledby,
@@ -147,9 +148,10 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
147
148
  typeof width === "string" && ["small", "medium"].includes(width);
148
149
 
149
150
  const mergedClassName = cl("navds-modal", className, {
150
- polyfillClassName: needPolyfill,
151
+ [polyfillClassName]: needPolyfill,
151
152
  "navds-modal--autowidth": !width,
152
153
  [`navds-modal--${width}`]: isWidthPreset,
154
+ "navds-modal--top": placement === "top" && !needPolyfill,
153
155
  });
154
156
 
155
157
  const mergedStyle = {
@@ -59,6 +59,11 @@ interface ModalPropsBase extends React.DialogHTMLAttributes<HTMLDialogElement> {
59
59
  * @default fit-content (up to 700px)
60
60
  * */
61
61
  width?: "medium" | "small" | number | `${number}${string}`;
62
+ /**
63
+ * Where to place the modal in the viewport. (Will always be centered on mobile and old browsers.)
64
+ * @default "center"
65
+ */
66
+ placement?: "top" | "center";
62
67
  /**
63
68
  * Lets you render the modal into a different part of the DOM.
64
69
  * Will use `rootElement` from `Provider` if defined, otherwise `document.body`.