@navikt/ds-react 7.2.0 → 7.3.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 (294) hide show
  1. package/cjs/accordion/AccordionHeader.js +1 -1
  2. package/cjs/accordion/AccordionHeader.js.map +1 -1
  3. package/cjs/alert/Alert.d.ts +0 -3
  4. package/cjs/alert/Alert.js +11 -17
  5. package/cjs/alert/Alert.js.map +1 -1
  6. package/cjs/chips/Removable.d.ts +5 -5
  7. package/cjs/chips/Removable.js +4 -2
  8. package/cjs/chips/Removable.js.map +1 -1
  9. package/cjs/collapsible/Collapsible.context.d.ts +1 -1
  10. package/cjs/date/datepicker/DatePicker.d.ts +2 -2
  11. package/cjs/date/monthpicker/MonthPicker.d.ts +2 -2
  12. package/cjs/dropdown/Menu/index.js +1 -1
  13. package/cjs/dropdown/Menu/index.js.map +1 -1
  14. package/cjs/form/checkbox/useCheckbox.js +3 -2
  15. package/cjs/form/checkbox/useCheckbox.js.map +1 -1
  16. package/cjs/form/combobox/ComboboxProvider.js +4 -1
  17. package/cjs/form/combobox/ComboboxProvider.js.map +1 -1
  18. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +1 -1
  19. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  20. package/cjs/form/combobox/FilteredOptions/useVirtualFocus.d.ts +3 -0
  21. package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js +33 -10
  22. package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
  23. package/cjs/form/combobox/Input/Input.js +23 -10
  24. package/cjs/form/combobox/Input/Input.js.map +1 -1
  25. package/cjs/form/file-upload/FileUpload.context.d.ts +1 -1
  26. package/cjs/form/radio/useRadio.js +3 -2
  27. package/cjs/form/radio/useRadio.js.map +1 -1
  28. package/cjs/form/search/Search.js +1 -1
  29. package/cjs/form/search/Search.js.map +1 -1
  30. package/cjs/form/switch/Switch.js +2 -1
  31. package/cjs/form/switch/Switch.js.map +1 -1
  32. package/cjs/index.d.ts +1 -0
  33. package/cjs/index.js +4 -2
  34. package/cjs/index.js.map +1 -1
  35. package/cjs/layout/base/BasePrimitive.d.ts +3 -0
  36. package/cjs/layout/base/BasePrimitive.js.map +1 -1
  37. package/cjs/layout/box/Box.d.ts +2 -2
  38. package/cjs/layout/box/Box.js.map +1 -1
  39. package/cjs/layout/grid/HGrid.d.ts +2 -2
  40. package/cjs/layout/grid/HGrid.js.map +1 -1
  41. package/cjs/layout/stack/Stack.d.ts +2 -2
  42. package/cjs/layout/stack/Stack.js.map +1 -1
  43. package/cjs/modal/ModalHeader.js +6 -1
  44. package/cjs/modal/ModalHeader.js.map +1 -1
  45. package/cjs/modal/dialog-polyfill.js +2 -2
  46. package/cjs/modal/dialog-polyfill.js.map +1 -1
  47. package/cjs/overlays/action-menu/ActionMenu.d.ts +310 -0
  48. package/cjs/overlays/action-menu/ActionMenu.js +227 -0
  49. package/cjs/overlays/action-menu/ActionMenu.js.map +1 -0
  50. package/cjs/overlays/action-menu/index.d.ts +1 -0
  51. package/cjs/overlays/action-menu/index.js +19 -0
  52. package/cjs/overlays/action-menu/index.js.map +1 -0
  53. package/cjs/overlays/floating/Floating.js +9 -10
  54. package/cjs/overlays/floating/Floating.js.map +1 -1
  55. package/cjs/overlays/floating/Floating.utils.d.ts +3 -5
  56. package/cjs/overlays/floating/Floating.utils.js +0 -2
  57. package/cjs/overlays/floating/Floating.utils.js.map +1 -1
  58. package/cjs/overlays/floating-menu/Menu.d.ts +15 -21
  59. package/cjs/overlays/floating-menu/Menu.js +119 -230
  60. package/cjs/overlays/floating-menu/Menu.js.map +1 -1
  61. package/cjs/overlays/floating-menu/parts/RovingFocus.d.ts +1 -1
  62. package/cjs/overlays/floating-menu/parts/RovingFocus.js +4 -4
  63. package/cjs/overlays/floating-menu/parts/RovingFocus.js.map +1 -1
  64. package/cjs/pagination/Pagination.d.ts +1 -6
  65. package/cjs/pagination/Pagination.js.map +1 -1
  66. package/cjs/provider/i18n/LanguageProvider.d.ts +3 -3
  67. package/cjs/stepper/context.d.ts +1 -1
  68. package/cjs/table/Body.d.ts +2 -4
  69. package/cjs/table/Body.js.map +1 -1
  70. package/cjs/table/ColumnHeader.d.ts +1 -2
  71. package/cjs/table/ColumnHeader.js.map +1 -1
  72. package/cjs/table/ExpandableRow.d.ts +1 -2
  73. package/cjs/table/ExpandableRow.js.map +1 -1
  74. package/cjs/table/Header.d.ts +2 -4
  75. package/cjs/table/Header.js.map +1 -1
  76. package/cjs/table/HeaderCell.d.ts +1 -2
  77. package/cjs/table/HeaderCell.js.map +1 -1
  78. package/cjs/table/Row.d.ts +1 -2
  79. package/cjs/table/Row.js.map +1 -1
  80. package/cjs/tabs/Tabs.context.d.ts +1 -1
  81. package/cjs/tabs/parts/tablist/useScrollButtons.js +1 -1
  82. package/cjs/tabs/parts/tablist/useScrollButtons.js.map +1 -1
  83. package/cjs/tabs/parts/tablist/useTabList.js +4 -4
  84. package/cjs/tabs/parts/tablist/useTabList.js.map +1 -1
  85. package/cjs/timeline/TimelineRow.js +9 -10
  86. package/cjs/timeline/TimelineRow.js.map +1 -1
  87. package/cjs/toggle-group/ToggleGroup.context.d.ts +1 -1
  88. package/cjs/toggle-group/parts/useToggleItem.js +4 -4
  89. package/cjs/toggle-group/parts/useToggleItem.js.map +1 -1
  90. package/cjs/util/TextareaAutoSize.js +2 -2
  91. package/cjs/util/TextareaAutoSize.js.map +1 -1
  92. package/cjs/util/hooks/descendants/descendant.js +1 -1
  93. package/cjs/util/hooks/descendants/descendant.js.map +1 -1
  94. package/cjs/util/hooks/descendants/useDescendant.js +1 -1
  95. package/cjs/util/hooks/descendants/useDescendant.js.map +1 -1
  96. package/cjs/util/i18n/get.d.ts +2 -2
  97. package/cjs/util/i18n/get.js.map +1 -1
  98. package/cjs/util/i18n/i18n.context.d.ts +2 -3
  99. package/cjs/util/i18n/i18n.context.js.map +1 -1
  100. package/cjs/util/i18n/i18n.types.d.ts +5 -9
  101. package/cjs/util/i18n/locales/en.d.ts +39 -0
  102. package/cjs/util/i18n/locales/en.js +41 -0
  103. package/cjs/util/i18n/locales/en.js.map +1 -0
  104. package/cjs/util/i18n/locales/nb.d.ts +14 -0
  105. package/cjs/util/i18n/locales/nb.js +14 -0
  106. package/cjs/util/i18n/locales/nb.js.map +1 -1
  107. package/cjs/util/i18n/locales/nn.d.ts +39 -0
  108. package/cjs/util/i18n/locales/nn.js +41 -0
  109. package/cjs/util/i18n/locales/nn.js.map +1 -0
  110. package/cjs/util/requireReactElement.d.ts +2 -0
  111. package/cjs/util/requireReactElement.js +22 -0
  112. package/cjs/util/requireReactElement.js.map +1 -0
  113. package/cjs/util/virtualfocus/Context.d.ts +1 -1
  114. package/cjs/util/virtualfocus/parts/VirtualFocusContent.d.ts +1 -2
  115. package/cjs/util/virtualfocus/parts/VirtualFocusContent.js.map +1 -1
  116. package/esm/accordion/AccordionHeader.js +1 -1
  117. package/esm/accordion/AccordionHeader.js.map +1 -1
  118. package/esm/alert/Alert.d.ts +0 -3
  119. package/esm/alert/Alert.js +11 -17
  120. package/esm/alert/Alert.js.map +1 -1
  121. package/esm/chips/Removable.d.ts +5 -5
  122. package/esm/chips/Removable.js +4 -2
  123. package/esm/chips/Removable.js.map +1 -1
  124. package/esm/collapsible/Collapsible.context.d.ts +1 -1
  125. package/esm/date/datepicker/DatePicker.d.ts +2 -2
  126. package/esm/date/monthpicker/MonthPicker.d.ts +2 -2
  127. package/esm/dropdown/Menu/index.js +1 -1
  128. package/esm/dropdown/Menu/index.js.map +1 -1
  129. package/esm/form/checkbox/useCheckbox.js +3 -2
  130. package/esm/form/checkbox/useCheckbox.js.map +1 -1
  131. package/esm/form/combobox/ComboboxProvider.js +4 -1
  132. package/esm/form/combobox/ComboboxProvider.js.map +1 -1
  133. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +1 -1
  134. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  135. package/esm/form/combobox/FilteredOptions/useVirtualFocus.d.ts +3 -0
  136. package/esm/form/combobox/FilteredOptions/useVirtualFocus.js +34 -11
  137. package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
  138. package/esm/form/combobox/Input/Input.js +23 -10
  139. package/esm/form/combobox/Input/Input.js.map +1 -1
  140. package/esm/form/file-upload/FileUpload.context.d.ts +1 -1
  141. package/esm/form/radio/useRadio.js +3 -2
  142. package/esm/form/radio/useRadio.js.map +1 -1
  143. package/esm/form/search/Search.js +1 -1
  144. package/esm/form/search/Search.js.map +1 -1
  145. package/esm/form/switch/Switch.js +2 -1
  146. package/esm/form/switch/Switch.js.map +1 -1
  147. package/esm/index.d.ts +1 -0
  148. package/esm/index.js +1 -0
  149. package/esm/index.js.map +1 -1
  150. package/esm/layout/base/BasePrimitive.d.ts +3 -0
  151. package/esm/layout/base/BasePrimitive.js.map +1 -1
  152. package/esm/layout/box/Box.d.ts +2 -2
  153. package/esm/layout/box/Box.js.map +1 -1
  154. package/esm/layout/grid/HGrid.d.ts +2 -2
  155. package/esm/layout/grid/HGrid.js.map +1 -1
  156. package/esm/layout/stack/Stack.d.ts +2 -2
  157. package/esm/layout/stack/Stack.js.map +1 -1
  158. package/esm/modal/ModalHeader.js +6 -1
  159. package/esm/modal/ModalHeader.js.map +1 -1
  160. package/esm/modal/dialog-polyfill.js +2 -2
  161. package/esm/modal/dialog-polyfill.js.map +1 -1
  162. package/esm/overlays/action-menu/ActionMenu.d.ts +310 -0
  163. package/esm/overlays/action-menu/ActionMenu.js +197 -0
  164. package/esm/overlays/action-menu/ActionMenu.js.map +1 -0
  165. package/esm/overlays/action-menu/index.d.ts +1 -0
  166. package/esm/overlays/action-menu/index.js +3 -0
  167. package/esm/overlays/action-menu/index.js.map +1 -0
  168. package/esm/overlays/floating/Floating.js +9 -10
  169. package/esm/overlays/floating/Floating.js.map +1 -1
  170. package/esm/overlays/floating/Floating.utils.d.ts +3 -5
  171. package/esm/overlays/floating/Floating.utils.js +0 -2
  172. package/esm/overlays/floating/Floating.utils.js.map +1 -1
  173. package/esm/overlays/floating-menu/Menu.d.ts +15 -21
  174. package/esm/overlays/floating-menu/Menu.js +119 -230
  175. package/esm/overlays/floating-menu/Menu.js.map +1 -1
  176. package/esm/overlays/floating-menu/parts/RovingFocus.d.ts +1 -1
  177. package/esm/overlays/floating-menu/parts/RovingFocus.js +4 -4
  178. package/esm/overlays/floating-menu/parts/RovingFocus.js.map +1 -1
  179. package/esm/pagination/Pagination.d.ts +1 -6
  180. package/esm/pagination/Pagination.js.map +1 -1
  181. package/esm/provider/i18n/LanguageProvider.d.ts +3 -3
  182. package/esm/stepper/context.d.ts +1 -1
  183. package/esm/table/Body.d.ts +2 -4
  184. package/esm/table/Body.js.map +1 -1
  185. package/esm/table/ColumnHeader.d.ts +1 -2
  186. package/esm/table/ColumnHeader.js.map +1 -1
  187. package/esm/table/ExpandableRow.d.ts +1 -2
  188. package/esm/table/ExpandableRow.js.map +1 -1
  189. package/esm/table/Header.d.ts +2 -4
  190. package/esm/table/Header.js.map +1 -1
  191. package/esm/table/HeaderCell.d.ts +1 -2
  192. package/esm/table/HeaderCell.js.map +1 -1
  193. package/esm/table/Row.d.ts +1 -2
  194. package/esm/table/Row.js.map +1 -1
  195. package/esm/tabs/Tabs.context.d.ts +1 -1
  196. package/esm/tabs/parts/tablist/useScrollButtons.js +1 -1
  197. package/esm/tabs/parts/tablist/useScrollButtons.js.map +1 -1
  198. package/esm/tabs/parts/tablist/useTabList.js +4 -4
  199. package/esm/tabs/parts/tablist/useTabList.js.map +1 -1
  200. package/esm/timeline/TimelineRow.js +9 -10
  201. package/esm/timeline/TimelineRow.js.map +1 -1
  202. package/esm/toggle-group/ToggleGroup.context.d.ts +1 -1
  203. package/esm/toggle-group/parts/useToggleItem.js +4 -4
  204. package/esm/toggle-group/parts/useToggleItem.js.map +1 -1
  205. package/esm/util/TextareaAutoSize.js +2 -2
  206. package/esm/util/TextareaAutoSize.js.map +1 -1
  207. package/esm/util/hooks/descendants/descendant.js +1 -1
  208. package/esm/util/hooks/descendants/descendant.js.map +1 -1
  209. package/esm/util/hooks/descendants/useDescendant.js +1 -1
  210. package/esm/util/hooks/descendants/useDescendant.js.map +1 -1
  211. package/esm/util/i18n/get.d.ts +2 -2
  212. package/esm/util/i18n/get.js.map +1 -1
  213. package/esm/util/i18n/i18n.context.d.ts +2 -3
  214. package/esm/util/i18n/i18n.context.js.map +1 -1
  215. package/esm/util/i18n/i18n.types.d.ts +5 -9
  216. package/esm/util/i18n/locales/en.d.ts +39 -0
  217. package/esm/util/i18n/locales/en.js +39 -0
  218. package/esm/util/i18n/locales/en.js.map +1 -0
  219. package/esm/util/i18n/locales/nb.d.ts +14 -0
  220. package/esm/util/i18n/locales/nb.js +14 -0
  221. package/esm/util/i18n/locales/nb.js.map +1 -1
  222. package/esm/util/i18n/locales/nn.d.ts +39 -0
  223. package/esm/util/i18n/locales/nn.js +39 -0
  224. package/esm/util/i18n/locales/nn.js.map +1 -0
  225. package/esm/util/requireReactElement.d.ts +2 -0
  226. package/esm/util/requireReactElement.js +15 -0
  227. package/esm/util/requireReactElement.js.map +1 -0
  228. package/esm/util/virtualfocus/Context.d.ts +1 -1
  229. package/esm/util/virtualfocus/parts/VirtualFocusContent.d.ts +1 -2
  230. package/esm/util/virtualfocus/parts/VirtualFocusContent.js.map +1 -1
  231. package/package.json +15 -7
  232. package/src/accordion/AccordionHeader.tsx +0 -1
  233. package/src/alert/Alert.tsx +11 -20
  234. package/src/chips/Removable.tsx +13 -9
  235. package/src/date/datepicker/DatePicker.tsx +2 -2
  236. package/src/date/monthpicker/MonthPicker.tsx +2 -2
  237. package/src/dropdown/Menu/index.tsx +1 -1
  238. package/src/form/checkbox/Checkbox.test.tsx +2 -3
  239. package/src/form/checkbox/useCheckbox.ts +2 -2
  240. package/src/form/combobox/ComboboxProvider.tsx +9 -1
  241. package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +1 -1
  242. package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +42 -11
  243. package/src/form/combobox/Input/Input.tsx +19 -10
  244. package/src/form/combobox/__tests__/combobox.test.tsx +36 -0
  245. package/src/form/confirmation-panel/ConfirmationPanel.test.tsx +1 -2
  246. package/src/form/radio/Radio.test.tsx +4 -5
  247. package/src/form/radio/useRadio.ts +2 -2
  248. package/src/form/search/Search.tsx +1 -1
  249. package/src/form/switch/Switch.tsx +1 -1
  250. package/src/index.ts +1 -0
  251. package/src/layout/base/BasePrimitive.tsx +3 -0
  252. package/src/layout/box/Box.tsx +35 -36
  253. package/src/layout/grid/HGrid.tsx +26 -27
  254. package/src/layout/stack/Stack.tsx +53 -54
  255. package/src/modal/ModalHeader.tsx +6 -0
  256. package/src/modal/dialog-polyfill.ts +2 -2
  257. package/src/overlays/action-menu/ActionMenu.tsx +971 -0
  258. package/src/overlays/action-menu/index.ts +29 -0
  259. package/src/overlays/floating/Floating.tsx +6 -12
  260. package/src/overlays/floating/Floating.utils.ts +2 -5
  261. package/src/overlays/floating-menu/Menu.tsx +183 -332
  262. package/src/overlays/floating-menu/parts/RovingFocus.tsx +7 -7
  263. package/src/pagination/Pagination.tsx +4 -1
  264. package/src/pagination/steps.test.ts +15 -16
  265. package/src/provider/i18n/LanguageProvider.tsx +3 -3
  266. package/src/table/Body.tsx +4 -6
  267. package/src/table/ColumnHeader.tsx +3 -4
  268. package/src/table/ExpandableRow.tsx +3 -4
  269. package/src/table/Header.tsx +4 -6
  270. package/src/table/HeaderCell.tsx +3 -4
  271. package/src/table/Row.tsx +3 -4
  272. package/src/tabs/parts/tablist/useScrollButtons.ts +1 -1
  273. package/src/tabs/parts/tablist/useTabList.ts +4 -4
  274. package/src/timeline/TimelineRow.tsx +20 -21
  275. package/src/toggle-group/parts/useToggleItem.ts +4 -4
  276. package/src/util/TextareaAutoSize.tsx +2 -2
  277. package/src/util/hooks/descendants/descendant.ts +1 -1
  278. package/src/util/hooks/descendants/useDescendant.tsx +1 -1
  279. package/src/util/i18n/get.ts +3 -3
  280. package/src/util/i18n/i18n.context.ts +2 -3
  281. package/src/util/i18n/i18n.types.ts +7 -11
  282. package/src/util/i18n/locales/en.ts +40 -0
  283. package/src/util/i18n/locales/nb.ts +23 -1
  284. package/src/util/i18n/locales/nn.ts +40 -0
  285. package/src/util/i18n/locales.test.tsx +23 -0
  286. package/src/util/requireReactElement.ts +25 -0
  287. package/src/util/virtualfocus/parts/VirtualFocusContent.tsx +4 -2
  288. package/cjs/util/i18n/merge.d.ts +0 -2
  289. package/cjs/util/i18n/merge.js +0 -28
  290. package/cjs/util/i18n/merge.js.map +0 -1
  291. package/esm/util/i18n/merge.d.ts +0 -2
  292. package/esm/util/i18n/merge.js +0 -25
  293. package/esm/util/i18n/merge.js.map +0 -1
  294. package/src/util/i18n/merge.ts +0 -35
@@ -51,13 +51,21 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
51
51
  value,
52
52
  onChange,
53
53
  onClear,
54
- shouldAutocomplete,
54
+ shouldAutocomplete: externalShouldAutocomplete,
55
55
  size,
56
56
  ...rest
57
57
  } = props;
58
58
  const options = mapToComboboxOptionArray(externalOptions) || [];
59
59
  const filteredOptions = mapToComboboxOptionArray(externalFilteredOptions);
60
60
  const selectedOptions = mapToComboboxOptionArray(externalSelectedOptions);
61
+
62
+ const userAgent =
63
+ typeof navigator === "undefined" ? "" : navigator.userAgent;
64
+ const isFirefoxOnAndroid =
65
+ userAgent.includes("Android") && userAgent.includes("Firefox/");
66
+ const shouldAutocomplete =
67
+ !isFirefoxOnAndroid && externalShouldAutocomplete;
68
+
61
69
  return (
62
70
  <InputContextProvider
63
71
  value={{
@@ -140,7 +140,7 @@ const FilteredOptionsProvider = ({
140
140
  if (disabled || readOnly) {
141
141
  return;
142
142
  }
143
- virtualFocus.moveFocusToTop();
143
+ virtualFocus.resetFocus();
144
144
  if (newState ?? !isInternalListOpen) {
145
145
  setHideCaret(!!maxSelected?.isLimitReached);
146
146
  }
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import { useEffect, useState } from "react";
2
2
 
3
3
  export type VirtualFocusType = {
4
4
  activeElement: HTMLElement | undefined;
@@ -10,6 +10,9 @@ export type VirtualFocusType = {
10
10
  moveFocusToElement: (id: string) => void;
11
11
  moveFocusToTop: () => void;
12
12
  moveFocusToBottom: () => void;
13
+ moveFocusUpBy: (numberOfElements: number) => void;
14
+ moveFocusDownBy: (numberOfElements: number) => void;
15
+ resetFocus: () => void;
13
16
  };
14
17
 
15
18
  const useVirtualFocus = (
@@ -40,11 +43,6 @@ const useVirtualFocus = (
40
43
  : false;
41
44
  };
42
45
 
43
- const _moveFocusAndScrollTo = (_element?: HTMLElement) => {
44
- setActiveElement(_element);
45
- _element?.scrollIntoView?.({ block: "nearest" });
46
- };
47
-
48
46
  const moveFocusUp = () => {
49
47
  if (!activeElement) {
50
48
  return;
@@ -55,14 +53,14 @@ const useVirtualFocus = (
55
53
  if (_currentIndex === 0) {
56
54
  setActiveElement(undefined);
57
55
  } else {
58
- _moveFocusAndScrollTo(elementAbove);
56
+ setActiveElement(elementAbove);
59
57
  }
60
58
  };
61
59
 
62
60
  const moveFocusDown = () => {
63
61
  const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
64
62
  if (!activeElement) {
65
- _moveFocusAndScrollTo(elementsAbleToReceiveFocus[0]);
63
+ setActiveElement(elementsAbleToReceiveFocus[0]);
66
64
  return;
67
65
  }
68
66
  const _currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
@@ -70,13 +68,17 @@ const useVirtualFocus = (
70
68
  return;
71
69
  }
72
70
 
73
- _moveFocusAndScrollTo(elementsAbleToReceiveFocus[_currentIndex + 1]);
71
+ setActiveElement(elementsAbleToReceiveFocus[_currentIndex + 1]);
74
72
  };
75
73
 
76
- const moveFocusToTop = () => _moveFocusAndScrollTo(undefined);
74
+ const resetFocus = () => setActiveElement(undefined);
75
+ const moveFocusToTop = () => {
76
+ const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
77
+ setActiveElement(elementsAbleToReceiveFocus[0]);
78
+ };
77
79
  const moveFocusToBottom = () => {
78
80
  const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
79
- return _moveFocusAndScrollTo(
81
+ setActiveElement(
80
82
  elementsAbleToReceiveFocus[elementsAbleToReceiveFocus.length - 1],
81
83
  );
82
84
  };
@@ -89,6 +91,32 @@ const useVirtualFocus = (
89
91
  }
90
92
  };
91
93
 
94
+ const moveFocusUpBy = (numberOfElements: number) => {
95
+ if (!activeElement) {
96
+ return;
97
+ }
98
+ const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
99
+ const currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
100
+ const newIndex = Math.max(currentIndex - numberOfElements, 0);
101
+ setActiveElement(elementsAbleToReceiveFocus[newIndex]);
102
+ };
103
+
104
+ const moveFocusDownBy = (numberOfElements: number) => {
105
+ const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
106
+ const currentIndex = activeElement
107
+ ? elementsAbleToReceiveFocus.indexOf(activeElement)
108
+ : -1;
109
+ const newIndex = Math.min(
110
+ currentIndex + numberOfElements,
111
+ elementsAbleToReceiveFocus.length - 1,
112
+ );
113
+ setActiveElement(elementsAbleToReceiveFocus[newIndex]);
114
+ };
115
+
116
+ useEffect(() => {
117
+ activeElement?.scrollIntoView?.({ block: "nearest" });
118
+ }, [activeElement]);
119
+
92
120
  return {
93
121
  activeElement,
94
122
  getElementById,
@@ -99,6 +127,9 @@ const useVirtualFocus = (
99
127
  moveFocusToElement,
100
128
  moveFocusToTop,
101
129
  moveFocusToBottom,
130
+ moveFocusUpBy,
131
+ moveFocusDownBy,
132
+ resetFocus,
102
133
  };
103
134
  };
104
135
 
@@ -134,14 +134,6 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
134
134
  case "Accept":
135
135
  onEnter(e);
136
136
  break;
137
- case "Home":
138
- toggleIsListOpen(false);
139
- virtualFocus.moveFocusToTop();
140
- break;
141
- case "End":
142
- toggleIsListOpen(true);
143
- virtualFocus.moveFocusToBottom();
144
- break;
145
137
  default:
146
138
  break;
147
139
  }
@@ -202,6 +194,24 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
202
194
  }
203
195
  virtualFocus.moveFocusUp();
204
196
  }
197
+ } else if (e.key === "Home") {
198
+ e.preventDefault();
199
+ virtualFocus.moveFocusToTop();
200
+ } else if (e.key === "End") {
201
+ e.preventDefault();
202
+ if (virtualFocus.activeElement === null || !isListOpen) {
203
+ toggleIsListOpen(true);
204
+ }
205
+ virtualFocus.moveFocusToBottom();
206
+ } else if (e.key === "PageUp") {
207
+ e.preventDefault();
208
+ virtualFocus.moveFocusUpBy(6);
209
+ } else if (e.key === "PageDown") {
210
+ e.preventDefault();
211
+ if (virtualFocus.activeElement === null || !isListOpen) {
212
+ toggleIsListOpen(true);
213
+ }
214
+ virtualFocus.moveFocusDownBy(6);
205
215
  }
206
216
  },
207
217
  [
@@ -230,10 +240,9 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
230
240
  } else if (filteredOptions.length === 0) {
231
241
  toggleIsListOpen(false);
232
242
  }
233
- virtualFocus.moveFocusToTop();
234
243
  onChange(newValue);
235
244
  },
236
- [filteredOptions.length, virtualFocus, onChange, toggleIsListOpen],
245
+ [filteredOptions.length, onChange, toggleIsListOpen],
237
246
  );
238
247
 
239
248
  return (
@@ -291,4 +291,40 @@ describe("Render combobox", () => {
291
291
  );
292
292
  });
293
293
  });
294
+
295
+ describe("has keyboard navigation", () => {
296
+ test("for PageDown and PageUp", async () => {
297
+ render(<App options={options} />);
298
+
299
+ const combobox = screen.getByRole("combobox", {
300
+ name: "Hva er dine favorittfrukter?",
301
+ });
302
+
303
+ const pressKey = async (key: string) => {
304
+ await act(async () => {
305
+ await userEvent.keyboard(`{${key}}`);
306
+ });
307
+ };
308
+
309
+ const hasVirtualFocus = (option: string) =>
310
+ expect(combobox.getAttribute("aria-activedescendant")).toBe(
311
+ screen.getByRole("option", { name: option }).id,
312
+ );
313
+
314
+ await act(async () => {
315
+ await userEvent.click(combobox);
316
+ });
317
+
318
+ await pressKey("ArrowDown");
319
+ hasVirtualFocus("apple");
320
+ await pressKey("PageDown");
321
+ hasVirtualFocus("mango");
322
+ await pressKey("PageDown");
323
+ hasVirtualFocus("watermelon");
324
+ await pressKey("PageUp");
325
+ hasVirtualFocus("mango");
326
+ await pressKey("PageUp");
327
+ hasVirtualFocus("apple");
328
+ });
329
+ });
294
330
  });
@@ -1,11 +1,10 @@
1
1
  import { render, screen } from "@testing-library/react";
2
- import faker from "faker";
3
2
  import React from "react";
4
3
  import { expect, test } from "vitest";
5
4
  import { ConfirmationPanel } from ".";
6
5
 
7
6
  test("omits error id from input", async () => {
8
- const label = faker.datatype.string();
7
+ const label = "My label";
9
8
 
10
9
  render(<ConfirmationPanel label={label} errorId="wat"></ConfirmationPanel>);
11
10
 
@@ -1,14 +1,13 @@
1
1
  import { render, screen } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
- import faker from "faker";
4
3
  import React from "react";
5
4
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
6
5
  import { Radio, RadioGroup } from ".";
7
6
 
8
- const value1 = faker.datatype.string();
9
- const label1 = faker.datatype.string();
10
- const value2 = faker.datatype.string();
11
- const label2 = faker.datatype.string();
7
+ const value1 = "My first value";
8
+ const label1 = "World's best radio label";
9
+ const value2 = "Life changing value";
10
+ const label2 = "Radio label of the year";
12
11
 
13
12
  const Group = (props) => (
14
13
  <RadioGroup {...props} legend="legend">
@@ -41,8 +41,8 @@ export const useRadio = (props: RadioProps) => {
41
41
  if (readOnly) {
42
42
  return;
43
43
  }
44
- props.onChange && props.onChange(e);
45
- radioGroup?.onChange && radioGroup.onChange(props.value);
44
+ props.onChange?.(e);
45
+ radioGroup?.onChange?.(props.value);
46
46
  },
47
47
  onClick: (e) => {
48
48
  if (readOnly) {
@@ -144,7 +144,7 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
144
144
  (event: SearchClearEvent) => {
145
145
  onClear?.(event);
146
146
  handleChange("");
147
- searchRef.current && searchRef.current?.focus?.();
147
+ searchRef.current?.focus?.();
148
148
  },
149
149
  [handleChange, onClear],
150
150
  );
@@ -122,7 +122,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
122
122
  return;
123
123
  }
124
124
  setChecked(e.target.checked);
125
- props.onChange && props.onChange(e);
125
+ props.onChange?.(e);
126
126
  }}
127
127
  onClick={(e) => {
128
128
  if (readOnly) {
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  export { Accordion, type AccordionProps } from "./accordion";
3
+ export { ActionMenu, type ActionMenuProps } from "./overlays/action-menu";
3
4
  export { Alert, type AlertProps } from "./alert";
4
5
  export { Button, type ButtonProps } from "./button";
5
6
  export { Chat, type ChatProps } from "./chat";
@@ -5,6 +5,9 @@ import { getResponsiveProps, getResponsiveValue } from "../utilities/css";
5
5
  import { ResponsiveProp, SpacingScale } from "../utilities/types";
6
6
 
7
7
  export type PrimitiveProps = {
8
+ /**
9
+ * @private Hides prop from documentation
10
+ */
8
11
  className?: string;
9
12
  /**
10
13
  * Padding around children.
@@ -19,42 +19,41 @@ import {
19
19
  SurfaceColorToken,
20
20
  } from "../utilities/types";
21
21
 
22
- export type BoxProps = PrimitiveProps &
23
- PrimitiveAsChildProps &
24
- React.HTMLAttributes<HTMLDivElement> & {
25
- /**
26
- * CSS `background-color` property.
27
- * Accepts a [background/surface color token](https://aksel.nav.no/grunnleggende/styling/design-tokens#afff774dad80).
28
- */
29
- background?: BackgroundColorToken | SurfaceColorToken;
30
- /**
31
- * CSS `border-color` property.
32
- * Accepts a [border color token](https://aksel.nav.no/grunnleggende/styling/design-tokens#adb1767e2f87).
33
- */
34
- borderColor?: BorderColorToken;
35
- /**
36
- * CSS `border-radius` property.
37
- * Accepts a [radius token](https://aksel.nav.no/grunnleggende/styling/design-tokens#6d79c5605d31)
38
- * or an object of radius tokens for different breakpoints.
39
- * @example
40
- * borderRadius='full'
41
- * borderRadius='0 full large small'
42
- * borderRadius={{xs: 'small large', sm: '0', md: 'large', lg: 'full'}}
43
- */
44
- borderRadius?: ResponsiveProp<SpaceDelimitedAttribute<BorderRadiiToken>>;
45
- /**
46
- * CSS `border-width` property. If this is not set there will be no border.
47
- * @example
48
- * borderWidth='2'
49
- * borderWidth='1 2 3 4'
50
- */
51
- borderWidth?: SpaceDelimitedAttribute<"0" | "1" | "2" | "3" | "4" | "5">;
52
- /** Shadow on box. Accepts a shadow token.
53
- * @example
54
- * shadow='small'
55
- */
56
- shadow?: ShadowToken;
57
- };
22
+ export type BoxProps = React.HTMLAttributes<HTMLDivElement> & {
23
+ /**
24
+ * CSS `background-color` property.
25
+ * Accepts a [background/surface color token](https://aksel.nav.no/grunnleggende/styling/design-tokens#afff774dad80).
26
+ */
27
+ background?: BackgroundColorToken | SurfaceColorToken;
28
+ /**
29
+ * CSS `border-color` property.
30
+ * Accepts a [border color token](https://aksel.nav.no/grunnleggende/styling/design-tokens#adb1767e2f87).
31
+ */
32
+ borderColor?: BorderColorToken;
33
+ /**
34
+ * CSS `border-radius` property.
35
+ * Accepts a [radius token](https://aksel.nav.no/grunnleggende/styling/design-tokens#6d79c5605d31)
36
+ * or an object of radius tokens for different breakpoints.
37
+ * @example
38
+ * borderRadius='full'
39
+ * borderRadius='0 full large small'
40
+ * borderRadius={{xs: 'small large', sm: '0', md: 'large', lg: 'full'}}
41
+ */
42
+ borderRadius?: ResponsiveProp<SpaceDelimitedAttribute<BorderRadiiToken>>;
43
+ /**
44
+ * CSS `border-width` property. If this is not set there will be no border.
45
+ * @example
46
+ * borderWidth='2'
47
+ * borderWidth='1 2 3 4'
48
+ */
49
+ borderWidth?: SpaceDelimitedAttribute<"0" | "1" | "2" | "3" | "4" | "5">;
50
+ /** Shadow on box. Accepts a shadow token.
51
+ * @example
52
+ * shadow='small'
53
+ */
54
+ shadow?: ShadowToken;
55
+ } & PrimitiveProps &
56
+ PrimitiveAsChildProps;
58
57
 
59
58
  /**
60
59
  * Foundational Layout-primitive for generic encapsulation & styling.
@@ -10,33 +10,32 @@ import { PrimitiveAsChildProps } from "../base/PrimitiveAsChildProps";
10
10
  import { getResponsiveProps, getResponsiveValue } from "../utilities/css";
11
11
  import { ResponsiveProp, SpacingScale } from "../utilities/types";
12
12
 
13
- export type HGridProps = PrimitiveProps &
14
- PrimitiveAsChildProps &
15
- React.HTMLAttributes<HTMLDivElement> & {
16
- /**
17
- * Number of columns to display. Can be a number, a string, or an object with values for specific breakpoints.
18
- * Sets `grid-template-columns`, so `fr`, `minmax` etc. works.
19
- * @example
20
- * columns={{ sm: 1, md: 1, lg: "1fr auto", xl: "1fr auto"}}
21
- * columns={3}
22
- * columns="repeat(3, minmax(0, 1fr))"
23
- */
24
- columns?: ResponsiveProp<number | string>;
25
- /**
26
- * Spacing between columns.
27
- * Accepts a [spacing token](https://aksel.nav.no/grunnleggende/styling/design-tokens#0cc9fb32f213)
28
- * or an object of spacing tokens for different breakpoints.
29
- * @example
30
- * gap="6"
31
- * gap="8 4"
32
- * gap={{ sm: "2", md: "2", lg: "6", xl: "6"}}
33
- */
34
- gap?: ResponsiveProp<SpacingScale | `${SpacingScale} ${SpacingScale}`>;
35
- /**
36
- * Vertical alignment of children. Elements will by default stretch to the height of parent-element.
37
- */
38
- align?: "start" | "center" | "end";
39
- };
13
+ export type HGridProps = React.HTMLAttributes<HTMLDivElement> & {
14
+ /**
15
+ * Number of columns to display. Can be a number, a string, or an object with values for specific breakpoints.
16
+ * Sets `grid-template-columns`, so `fr`, `minmax` etc. works.
17
+ * @example
18
+ * columns={{ sm: 1, md: 1, lg: "1fr auto", xl: "1fr auto"}}
19
+ * columns={3}
20
+ * columns="repeat(3, minmax(0, 1fr))"
21
+ */
22
+ columns?: ResponsiveProp<number | string>;
23
+ /**
24
+ * Spacing between columns.
25
+ * Accepts a [spacing token](https://aksel.nav.no/grunnleggende/styling/design-tokens#0cc9fb32f213)
26
+ * or an object of spacing tokens for different breakpoints.
27
+ * @example
28
+ * gap="6"
29
+ * gap="8 4"
30
+ * gap={{ sm: "2", md: "2", lg: "6", xl: "6"}}
31
+ */
32
+ gap?: ResponsiveProp<SpacingScale | `${SpacingScale} ${SpacingScale}`>;
33
+ /**
34
+ * Vertical alignment of children. Elements will by default stretch to the height of parent-element.
35
+ */
36
+ align?: "start" | "center" | "end";
37
+ } & PrimitiveProps &
38
+ PrimitiveAsChildProps;
40
39
  /**
41
40
  * Horizontal Grid Primitive with dynamic columns and gap based on breakpoints.
42
41
  *
@@ -11,60 +11,59 @@ import { PrimitiveAsChildProps } from "../base/PrimitiveAsChildProps";
11
11
  import { getResponsiveProps, getResponsiveValue } from "../utilities/css";
12
12
  import { ResponsiveProp, SpacingScale } from "../utilities/types";
13
13
 
14
- export type StackProps = PrimitiveProps &
15
- PrimitiveAsChildProps &
16
- HTMLAttributes<HTMLDivElement> & {
17
- /**
18
- * CSS `justify-content` property.
19
- *
20
- * @example
21
- * justify='center'
22
- * justify={{xs: 'start', sm: 'center', md: 'end', lg: 'space-around', xl: 'space-between'}}
23
- */
24
- justify?: ResponsiveProp<
25
- | "start"
26
- | "center"
27
- | "end"
28
- | "space-around"
29
- | "space-between"
30
- | "space-evenly"
31
- >;
32
- /**
33
- * CSS `align-items` property.
34
- * @default "stretch"
35
- *
36
- * @example
37
- * align='center'
38
- * align={{xs: 'start', sm: 'center', md: 'end', lg: 'baseline', xl: 'stretch'}}
39
- */
40
- align?: ResponsiveProp<"start" | "center" | "end" | "baseline" | "stretch">;
41
- /**
42
- * Sets the CSS `flex-wrap` property.
43
- */
44
- wrap?: boolean;
45
- /**
46
- * CSS `gap` property.
47
- * Accepts a [spacing token](https://aksel.nav.no/grunnleggende/styling/design-tokens#0cc9fb32f213)
48
- * or an object of spacing tokens for different breakpoints.
49
- *
50
- * @example
51
- * gap='4'
52
- * gap='8 4'
53
- * gap={{xs: '2', sm: '3', md: '4', lg: '5', xl: '6'}}
54
- */
55
- gap?: ResponsiveProp<SpacingScale | `${SpacingScale} ${SpacingScale}`>;
56
- /**
57
- * CSS `flex-direction` property.
58
- * @default "row"
59
- *
60
- * @example
61
- * direction='row'
62
- * direction={{xs: 'row', sm: 'column'}}
63
- */
64
- direction?: ResponsiveProp<
65
- "row" | "column" | "row-reverse" | "column-reverse"
66
- >;
67
- };
14
+ export type StackProps = HTMLAttributes<HTMLDivElement> & {
15
+ /**
16
+ * CSS `justify-content` property.
17
+ *
18
+ * @example
19
+ * justify='center'
20
+ * justify={{xs: 'start', sm: 'center', md: 'end', lg: 'space-around', xl: 'space-between'}}
21
+ */
22
+ justify?: ResponsiveProp<
23
+ | "start"
24
+ | "center"
25
+ | "end"
26
+ | "space-around"
27
+ | "space-between"
28
+ | "space-evenly"
29
+ >;
30
+ /**
31
+ * CSS `align-items` property.
32
+ * @default "stretch"
33
+ *
34
+ * @example
35
+ * align='center'
36
+ * align={{xs: 'start', sm: 'center', md: 'end', lg: 'baseline', xl: 'stretch'}}
37
+ */
38
+ align?: ResponsiveProp<"start" | "center" | "end" | "baseline" | "stretch">;
39
+ /**
40
+ * Sets the CSS `flex-wrap` property.
41
+ */
42
+ wrap?: boolean;
43
+ /**
44
+ * CSS `gap` property.
45
+ * Accepts a [spacing token](https://aksel.nav.no/grunnleggende/styling/design-tokens#0cc9fb32f213)
46
+ * or an object of spacing tokens for different breakpoints.
47
+ *
48
+ * @example
49
+ * gap='4'
50
+ * gap='8 4'
51
+ * gap={{xs: '2', sm: '3', md: '4', lg: '5', xl: '6'}}
52
+ */
53
+ gap?: ResponsiveProp<SpacingScale | `${SpacingScale} ${SpacingScale}`>;
54
+ /**
55
+ * CSS `flex-direction` property.
56
+ * @default "row"
57
+ *
58
+ * @example
59
+ * direction='row'
60
+ * direction={{xs: 'row', sm: 'column'}}
61
+ */
62
+ direction?: ResponsiveProp<
63
+ "row" | "column" | "row-reverse" | "column-reverse"
64
+ >;
65
+ } & PrimitiveProps &
66
+ PrimitiveAsChildProps;
68
67
 
69
68
  export const Stack: OverridableComponent<StackProps, HTMLDivElement> =
70
69
  forwardRef(
@@ -25,6 +25,12 @@ const ModalHeader = forwardRef<HTMLDivElement, ModalHeaderProps>(
25
25
  className="navds-modal__button"
26
26
  size="small"
27
27
  variant="tertiary-neutral"
28
+ onKeyDown={(event) => {
29
+ /* Prevents autofocus used in combination with holding down keys from closing modal */
30
+ if (["Enter", " "].includes(event.key) && event.repeat) {
31
+ event.preventDefault();
32
+ }
33
+ }}
28
34
  onClick={context.closeHandler}
29
35
  icon={<XMarkIcon title="Lukk" />}
30
36
  />
@@ -518,7 +518,7 @@ dialogPolyfill.isInlinePositionSetByStylesheet = function (element) {
518
518
  // Some browsers throw on cssRules.
519
519
  try {
520
520
  cssRules = styleSheet.cssRules;
521
- } catch (e) {
521
+ } catch {
522
522
  /* empty */
523
523
  }
524
524
  if (!cssRules) {
@@ -530,7 +530,7 @@ dialogPolyfill.isInlinePositionSetByStylesheet = function (element) {
530
530
  // Ignore errors on invalid selector texts.
531
531
  try {
532
532
  selectedNodes = document.querySelectorAll(rule.selectorText);
533
- } catch (e) {
533
+ } catch {
534
534
  /* empty */
535
535
  }
536
536
  if (!selectedNodes || !inNodeList(selectedNodes, element)) {