@navikt/ds-react 8.5.2 → 8.7.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 (258) hide show
  1. package/cjs/data/drag-and-drop/item/DataDragAndDropItem.d.ts +27 -0
  2. package/cjs/data/drag-and-drop/item/DataDragAndDropItem.js +91 -0
  3. package/cjs/data/drag-and-drop/item/DataDragAndDropItem.js.map +1 -0
  4. package/cjs/data/drag-and-drop/root/DataDragAndDrop.context.d.ts +5 -0
  5. package/cjs/data/drag-and-drop/root/DataDragAndDrop.context.js +6 -0
  6. package/cjs/data/drag-and-drop/root/DataDragAndDrop.context.js.map +1 -0
  7. package/cjs/data/drag-and-drop/root/DataDragAndDropRoot.d.ts +24 -0
  8. package/cjs/data/drag-and-drop/root/DataDragAndDropRoot.js +111 -0
  9. package/cjs/data/drag-and-drop/root/DataDragAndDropRoot.js.map +1 -0
  10. package/cjs/data/table/helpers/table-keyboard.d.ts +1 -0
  11. package/cjs/data/table/helpers/table-keyboard.js +5 -3
  12. package/cjs/data/table/helpers/table-keyboard.js.map +1 -1
  13. package/cjs/data/table/root/DataTableRoot.context.d.ts +8 -0
  14. package/cjs/data/table/root/DataTableRoot.context.js +11 -0
  15. package/cjs/data/table/root/DataTableRoot.context.js.map +1 -0
  16. package/cjs/data/table/root/DataTableRoot.js +5 -3
  17. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  18. package/cjs/data/table/th/DataTableTh.d.ts +18 -2
  19. package/cjs/data/table/th/DataTableTh.js +45 -20
  20. package/cjs/data/table/th/DataTableTh.js.map +1 -1
  21. package/cjs/data/table/tr/DataTableTr.js +9 -2
  22. package/cjs/data/table/tr/DataTableTr.js.map +1 -1
  23. package/cjs/data/token-filter/AutoSuggest.d.ts +6 -2
  24. package/cjs/data/token-filter/AutoSuggest.js +46 -14
  25. package/cjs/data/token-filter/AutoSuggest.js.map +1 -1
  26. package/cjs/data/token-filter/AutoSuggest.types.d.ts +0 -1
  27. package/cjs/data/token-filter/TokenFilter.d.ts +10 -5
  28. package/cjs/data/token-filter/TokenFilter.js +110 -48
  29. package/cjs/data/token-filter/TokenFilter.js.map +1 -1
  30. package/cjs/data/token-filter/TokenFilter.types.d.ts +51 -35
  31. package/cjs/data/token-filter/helpers/generate-autocomplete-options.d.ts +3 -6
  32. package/cjs/data/token-filter/helpers/generate-autocomplete-options.js +24 -37
  33. package/cjs/data/token-filter/helpers/generate-autocomplete-options.js.map +1 -1
  34. package/cjs/data/token-filter/helpers/operators.d.ts +6 -6
  35. package/cjs/data/token-filter/helpers/operators.js +3 -4
  36. package/cjs/data/token-filter/helpers/operators.js.map +1 -1
  37. package/cjs/data/token-filter/helpers/parse-query-text.d.ts +2 -20
  38. package/cjs/data/token-filter/helpers/parse-query-text.js +1 -1
  39. package/cjs/data/token-filter/helpers/parse-query-text.js.map +1 -1
  40. package/cjs/data/token-filter/helpers/query-builder.d.ts +2 -2
  41. package/cjs/data/token-filter/helpers/query-builder.js.map +1 -1
  42. package/cjs/date/Date.Dialog.d.ts +5 -1
  43. package/cjs/date/Date.Dialog.js +6 -2
  44. package/cjs/date/Date.Dialog.js.map +1 -1
  45. package/cjs/date/datepicker/DatePicker.js +3 -2
  46. package/cjs/date/datepicker/DatePicker.js.map +1 -1
  47. package/cjs/date/datepicker/hooks/useDatepicker.js +5 -2
  48. package/cjs/date/datepicker/hooks/useDatepicker.js.map +1 -1
  49. package/cjs/date/datepicker/hooks/useRangeDatepicker.js +3 -1
  50. package/cjs/date/datepicker/hooks/useRangeDatepicker.js.map +1 -1
  51. package/cjs/date/datepicker/parts/DatePicker.Months.d.ts +2 -1
  52. package/cjs/date/datepicker/parts/DatePicker.Months.js +3 -3
  53. package/cjs/date/datepicker/parts/DatePicker.Months.js.map +1 -1
  54. package/cjs/date/datepicker/parts/DatePicker.RDP.d.ts +5 -1
  55. package/cjs/date/datepicker/parts/DatePicker.RDP.js +2 -2
  56. package/cjs/date/datepicker/parts/DatePicker.RDP.js.map +1 -1
  57. package/cjs/date/monthpicker/MonthPicker.js +3 -2
  58. package/cjs/date/monthpicker/MonthPicker.js.map +1 -1
  59. package/cjs/date/monthpicker/hooks/useMonthPicker.js +3 -1
  60. package/cjs/date/monthpicker/hooks/useMonthPicker.js.map +1 -1
  61. package/cjs/date/monthpicker/parts/MonthPicker.Caption.d.ts +4 -1
  62. package/cjs/date/monthpicker/parts/MonthPicker.Caption.js +3 -2
  63. package/cjs/date/monthpicker/parts/MonthPicker.Caption.js.map +1 -1
  64. package/cjs/dropdown/Toggle.js +5 -12
  65. package/cjs/dropdown/Toggle.js.map +1 -1
  66. package/cjs/form/combobox/Input/Input.js +1 -1
  67. package/cjs/form/combobox/Input/Input.js.map +1 -1
  68. package/cjs/inline-message/root/InlineMessage.js +2 -2
  69. package/cjs/inline-message/root/InlineMessage.js.map +1 -1
  70. package/cjs/provider/Provider.d.ts +2 -2
  71. package/cjs/toggle-group/useToggleGroup.js +5 -3
  72. package/cjs/toggle-group/useToggleGroup.js.map +1 -1
  73. package/cjs/tooltip/Tooltip.js +1 -3
  74. package/cjs/tooltip/Tooltip.js.map +1 -1
  75. package/cjs/utils/components/HighlightText/HighlightText.d.ts +8 -0
  76. package/cjs/utils/components/HighlightText/HighlightText.js +27 -0
  77. package/cjs/utils/components/HighlightText/HighlightText.js.map +1 -0
  78. package/cjs/utils/components/Listbox/group/ListboxGroup.d.ts +7 -0
  79. package/cjs/utils/components/Listbox/group/ListboxGroup.js +15 -0
  80. package/cjs/utils/components/Listbox/group/ListboxGroup.js.map +1 -0
  81. package/cjs/utils/components/Listbox/input-slot/ListboxInputSlot.d.ts +7 -0
  82. package/cjs/utils/components/Listbox/input-slot/ListboxInputSlot.js +15 -0
  83. package/cjs/utils/components/Listbox/input-slot/ListboxInputSlot.js.map +1 -0
  84. package/cjs/utils/components/Listbox/item/ListboxItem.d.ts +24 -0
  85. package/cjs/utils/components/Listbox/item/ListboxItem.js +33 -0
  86. package/cjs/utils/components/Listbox/item/ListboxItem.js.map +1 -0
  87. package/cjs/utils/components/Listbox/list/ListboxList.d.ts +8 -0
  88. package/cjs/utils/components/Listbox/list/ListboxList.js +32 -0
  89. package/cjs/utils/components/Listbox/list/ListboxList.js.map +1 -0
  90. package/cjs/utils/components/Listbox/root/ListboxRoot.d.ts +20 -0
  91. package/cjs/utils/components/Listbox/root/ListboxRoot.js +84 -0
  92. package/cjs/utils/components/Listbox/root/ListboxRoot.js.map +1 -0
  93. package/cjs/utils/components/Listbox/root/domHelpers.d.ts +3 -0
  94. package/cjs/utils/components/Listbox/root/domHelpers.js +53 -0
  95. package/cjs/utils/components/Listbox/root/domHelpers.js.map +1 -0
  96. package/cjs/utils/components/focus-boundary/FocusBoundary.js +9 -64
  97. package/cjs/utils/components/focus-boundary/FocusBoundary.js.map +1 -1
  98. package/cjs/utils/helpers/focus.d.ts +14 -0
  99. package/cjs/utils/helpers/focus.js +63 -0
  100. package/cjs/utils/helpers/focus.js.map +1 -0
  101. package/cjs/utils/hooks/useDeferredValue.d.ts +1 -0
  102. package/cjs/utils/hooks/useDeferredValue.js +14 -0
  103. package/cjs/utils/hooks/useDeferredValue.js.map +1 -0
  104. package/esm/data/drag-and-drop/item/DataDragAndDropItem.d.ts +27 -0
  105. package/esm/data/drag-and-drop/item/DataDragAndDropItem.js +55 -0
  106. package/esm/data/drag-and-drop/item/DataDragAndDropItem.js.map +1 -0
  107. package/esm/data/drag-and-drop/root/DataDragAndDrop.context.d.ts +5 -0
  108. package/esm/data/drag-and-drop/root/DataDragAndDrop.context.js +3 -0
  109. package/esm/data/drag-and-drop/root/DataDragAndDrop.context.js.map +1 -0
  110. package/esm/data/drag-and-drop/root/DataDragAndDropRoot.d.ts +24 -0
  111. package/esm/data/drag-and-drop/root/DataDragAndDropRoot.js +71 -0
  112. package/esm/data/drag-and-drop/root/DataDragAndDropRoot.js.map +1 -0
  113. package/esm/data/table/helpers/table-keyboard.d.ts +1 -0
  114. package/esm/data/table/helpers/table-keyboard.js +5 -3
  115. package/esm/data/table/helpers/table-keyboard.js.map +1 -1
  116. package/esm/data/table/root/DataTableRoot.context.d.ts +8 -0
  117. package/esm/data/table/root/DataTableRoot.context.js +7 -0
  118. package/esm/data/table/root/DataTableRoot.context.js.map +1 -0
  119. package/esm/data/table/root/DataTableRoot.js +5 -3
  120. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  121. package/esm/data/table/th/DataTableTh.d.ts +18 -2
  122. package/esm/data/table/th/DataTableTh.js +46 -21
  123. package/esm/data/table/th/DataTableTh.js.map +1 -1
  124. package/esm/data/table/tr/DataTableTr.js +9 -2
  125. package/esm/data/table/tr/DataTableTr.js.map +1 -1
  126. package/esm/data/token-filter/AutoSuggest.d.ts +6 -2
  127. package/esm/data/token-filter/AutoSuggest.js +45 -16
  128. package/esm/data/token-filter/AutoSuggest.js.map +1 -1
  129. package/esm/data/token-filter/AutoSuggest.types.d.ts +0 -1
  130. package/esm/data/token-filter/TokenFilter.d.ts +10 -5
  131. package/esm/data/token-filter/TokenFilter.js +110 -48
  132. package/esm/data/token-filter/TokenFilter.js.map +1 -1
  133. package/esm/data/token-filter/TokenFilter.types.d.ts +51 -35
  134. package/esm/data/token-filter/helpers/generate-autocomplete-options.d.ts +3 -6
  135. package/esm/data/token-filter/helpers/generate-autocomplete-options.js +24 -37
  136. package/esm/data/token-filter/helpers/generate-autocomplete-options.js.map +1 -1
  137. package/esm/data/token-filter/helpers/operators.d.ts +6 -6
  138. package/esm/data/token-filter/helpers/operators.js +3 -4
  139. package/esm/data/token-filter/helpers/operators.js.map +1 -1
  140. package/esm/data/token-filter/helpers/parse-query-text.d.ts +2 -20
  141. package/esm/data/token-filter/helpers/parse-query-text.js +1 -1
  142. package/esm/data/token-filter/helpers/parse-query-text.js.map +1 -1
  143. package/esm/data/token-filter/helpers/query-builder.d.ts +2 -2
  144. package/esm/data/token-filter/helpers/query-builder.js.map +1 -1
  145. package/esm/date/Date.Dialog.d.ts +5 -1
  146. package/esm/date/Date.Dialog.js +6 -2
  147. package/esm/date/Date.Dialog.js.map +1 -1
  148. package/esm/date/datepicker/DatePicker.js +3 -2
  149. package/esm/date/datepicker/DatePicker.js.map +1 -1
  150. package/esm/date/datepicker/hooks/useDatepicker.js +5 -2
  151. package/esm/date/datepicker/hooks/useDatepicker.js.map +1 -1
  152. package/esm/date/datepicker/hooks/useRangeDatepicker.js +3 -1
  153. package/esm/date/datepicker/hooks/useRangeDatepicker.js.map +1 -1
  154. package/esm/date/datepicker/parts/DatePicker.Months.d.ts +2 -1
  155. package/esm/date/datepicker/parts/DatePicker.Months.js +3 -3
  156. package/esm/date/datepicker/parts/DatePicker.Months.js.map +1 -1
  157. package/esm/date/datepicker/parts/DatePicker.RDP.d.ts +5 -1
  158. package/esm/date/datepicker/parts/DatePicker.RDP.js +2 -2
  159. package/esm/date/datepicker/parts/DatePicker.RDP.js.map +1 -1
  160. package/esm/date/monthpicker/MonthPicker.js +3 -2
  161. package/esm/date/monthpicker/MonthPicker.js.map +1 -1
  162. package/esm/date/monthpicker/hooks/useMonthPicker.js +3 -1
  163. package/esm/date/monthpicker/hooks/useMonthPicker.js.map +1 -1
  164. package/esm/date/monthpicker/parts/MonthPicker.Caption.d.ts +4 -1
  165. package/esm/date/monthpicker/parts/MonthPicker.Caption.js +3 -2
  166. package/esm/date/monthpicker/parts/MonthPicker.Caption.js.map +1 -1
  167. package/esm/dropdown/Toggle.js +5 -12
  168. package/esm/dropdown/Toggle.js.map +1 -1
  169. package/esm/form/combobox/Input/Input.js +1 -1
  170. package/esm/form/combobox/Input/Input.js.map +1 -1
  171. package/esm/inline-message/root/InlineMessage.js +3 -3
  172. package/esm/inline-message/root/InlineMessage.js.map +1 -1
  173. package/esm/provider/Provider.d.ts +2 -2
  174. package/esm/toggle-group/useToggleGroup.js +6 -4
  175. package/esm/toggle-group/useToggleGroup.js.map +1 -1
  176. package/esm/tooltip/Tooltip.js +1 -3
  177. package/esm/tooltip/Tooltip.js.map +1 -1
  178. package/esm/utils/components/HighlightText/HighlightText.d.ts +8 -0
  179. package/esm/utils/components/HighlightText/HighlightText.js +21 -0
  180. package/esm/utils/components/HighlightText/HighlightText.js.map +1 -0
  181. package/esm/utils/components/Listbox/group/ListboxGroup.d.ts +7 -0
  182. package/esm/utils/components/Listbox/group/ListboxGroup.js +10 -0
  183. package/esm/utils/components/Listbox/group/ListboxGroup.js.map +1 -0
  184. package/esm/utils/components/Listbox/input-slot/ListboxInputSlot.d.ts +7 -0
  185. package/esm/utils/components/Listbox/input-slot/ListboxInputSlot.js +9 -0
  186. package/esm/utils/components/Listbox/input-slot/ListboxInputSlot.js.map +1 -0
  187. package/esm/utils/components/Listbox/item/ListboxItem.d.ts +24 -0
  188. package/esm/utils/components/Listbox/item/ListboxItem.js +27 -0
  189. package/esm/utils/components/Listbox/item/ListboxItem.js.map +1 -0
  190. package/esm/utils/components/Listbox/list/ListboxList.d.ts +8 -0
  191. package/esm/utils/components/Listbox/list/ListboxList.js +27 -0
  192. package/esm/utils/components/Listbox/list/ListboxList.js.map +1 -0
  193. package/esm/utils/components/Listbox/root/ListboxRoot.d.ts +20 -0
  194. package/esm/utils/components/Listbox/root/ListboxRoot.js +79 -0
  195. package/esm/utils/components/Listbox/root/ListboxRoot.js.map +1 -0
  196. package/esm/utils/components/Listbox/root/domHelpers.d.ts +3 -0
  197. package/esm/utils/components/Listbox/root/domHelpers.js +50 -0
  198. package/esm/utils/components/Listbox/root/domHelpers.js.map +1 -0
  199. package/esm/utils/components/focus-boundary/FocusBoundary.js +8 -63
  200. package/esm/utils/components/focus-boundary/FocusBoundary.js.map +1 -1
  201. package/esm/utils/helpers/focus.d.ts +14 -0
  202. package/esm/utils/helpers/focus.js +60 -0
  203. package/esm/utils/helpers/focus.js.map +1 -0
  204. package/esm/utils/hooks/useDeferredValue.d.ts +1 -0
  205. package/esm/utils/hooks/useDeferredValue.js +7 -0
  206. package/esm/utils/hooks/useDeferredValue.js.map +1 -0
  207. package/package.json +7 -7
  208. package/src/data/drag-and-drop/item/DataDragAndDropItem.tsx +101 -0
  209. package/src/data/drag-and-drop/root/DataDragAndDrop.context.tsx +9 -0
  210. package/src/data/drag-and-drop/root/DataDragAndDropRoot.tsx +98 -0
  211. package/src/data/table/helpers/table-keyboard.ts +7 -3
  212. package/src/data/table/root/DataTableRoot.context.ts +13 -0
  213. package/src/data/table/root/DataTableRoot.tsx +16 -13
  214. package/src/data/table/th/DataTableTh.tsx +110 -54
  215. package/src/data/table/tr/DataTableTr.tsx +13 -2
  216. package/src/data/token-filter/AutoSuggest.tsx +142 -35
  217. package/src/data/token-filter/AutoSuggest.types.ts +0 -1
  218. package/src/data/token-filter/TokenFilter.tsx +179 -81
  219. package/src/data/token-filter/TokenFilter.types.ts +70 -44
  220. package/src/data/token-filter/helpers/generate-autocomplete-options.test.ts +97 -157
  221. package/src/data/token-filter/helpers/generate-autocomplete-options.ts +56 -53
  222. package/src/data/token-filter/helpers/operators.test.ts +29 -29
  223. package/src/data/token-filter/helpers/operators.ts +16 -16
  224. package/src/data/token-filter/helpers/parse-query-text.test.ts +37 -35
  225. package/src/data/token-filter/helpers/parse-query-text.ts +7 -26
  226. package/src/data/token-filter/helpers/query-builder.ts +2 -2
  227. package/src/date/Date.Dialog.tsx +15 -0
  228. package/src/date/datepicker/DatePicker.tsx +3 -0
  229. package/src/date/datepicker/hooks/useDatepicker.tsx +7 -2
  230. package/src/date/datepicker/hooks/useRangeDatepicker.tsx +5 -1
  231. package/src/date/datepicker/parts/DatePicker.Months.tsx +9 -1
  232. package/src/date/datepicker/parts/DatePicker.RDP.tsx +7 -1
  233. package/src/date/monthpicker/MonthPicker.tsx +3 -1
  234. package/src/date/monthpicker/hooks/useMonthPicker.tsx +5 -1
  235. package/src/date/monthpicker/parts/MonthPicker.Caption.tsx +20 -2
  236. package/src/dropdown/Toggle.tsx +6 -12
  237. package/src/form/combobox/Input/Input.tsx +2 -2
  238. package/src/inline-message/root/InlineMessage.tsx +5 -5
  239. package/src/provider/Provider.tsx +2 -2
  240. package/src/toggle-group/useToggleGroup.ts +6 -5
  241. package/src/tooltip/Tooltip.tsx +1 -3
  242. package/src/utils/components/HighlightText/HighlightText.tsx +34 -0
  243. package/src/utils/components/Listbox/group/ListboxGroup.tsx +26 -0
  244. package/src/utils/components/Listbox/input-slot/ListboxInputSlot.tsx +22 -0
  245. package/src/utils/components/Listbox/item/ListboxItem.tsx +57 -0
  246. package/src/utils/components/Listbox/list/ListboxList.tsx +38 -0
  247. package/src/utils/components/Listbox/root/ListboxRoot.tsx +104 -0
  248. package/src/utils/components/Listbox/root/domHelpers.ts +59 -0
  249. package/src/utils/components/focus-boundary/FocusBoundary.tsx +8 -78
  250. package/src/utils/helpers/focus.ts +75 -0
  251. package/src/utils/hooks/useDeferredValue.ts +12 -0
  252. package/cjs/data/table/th/DataTableThSortHandle.d.ts +0 -6
  253. package/cjs/data/table/th/DataTableThSortHandle.js +0 -82
  254. package/cjs/data/table/th/DataTableThSortHandle.js.map +0 -1
  255. package/esm/data/table/th/DataTableThSortHandle.d.ts +0 -6
  256. package/esm/data/table/th/DataTableThSortHandle.js +0 -47
  257. package/esm/data/table/th/DataTableThSortHandle.js.map +0 -1
  258. package/src/data/table/th/DataTableThSortHandle.tsx +0 -67
@@ -1,11 +1,11 @@
1
- import type { QueryFilterOperator } from "../TokenFilter.types";
1
+ import type { OperatorT } from "../TokenFilter.types";
2
2
 
3
3
  /**
4
4
  * Human-readable labels for query filter operators.
5
5
  * Used for displaying operator descriptions in autocomplete suggestions.
6
6
  * TODO: Support i18n
7
7
  */
8
- const OPERATOR_LABELS: Record<QueryFilterOperator, string> = {
8
+ const OPERATOR_LABELS: Record<OperatorT, string> = {
9
9
  ":": "contains",
10
10
  "!:": "does not contain",
11
11
  "=": "is",
@@ -5,6 +5,7 @@ import { Modal } from "../modal";
5
5
  import { useModalContext } from "../modal/Modal.context";
6
6
  import { Popover } from "../popover";
7
7
  import { cl } from "../utils/helpers";
8
+ import { focusElement } from "../utils/helpers/focus";
8
9
  import { useMedia } from "../utils/hooks";
9
10
  import { useI18n } from "../utils/i18n/i18n.hooks";
10
11
  import type { TFunction } from "../utils/i18n/i18n.types";
@@ -30,6 +31,10 @@ type DateWrapperProps = {
30
31
  id?: string;
31
32
  strategy?: "absolute" | "fixed";
32
33
  };
34
+ /**
35
+ * Id for the label of the popup, used for aria-labelledby
36
+ */
37
+ popupLabelId?: string;
33
38
  };
34
39
 
35
40
  const DateDialog = ({
@@ -41,6 +46,7 @@ const DateDialog = ({
41
46
  translate,
42
47
  variant,
43
48
  popoverProps,
49
+ popupLabelId,
44
50
  }: DateWrapperProps) => {
45
51
  const translateGlobal = useI18n("global", getGlobalTranslations(locale));
46
52
 
@@ -68,6 +74,10 @@ const DateDialog = ({
68
74
  "aksel-date": variant === "month",
69
75
  })}
70
76
  {...popoverProps}
77
+ ref={focusPopoverOnOpen}
78
+ tabIndex={-1}
79
+ role="dialog"
80
+ aria-labelledby={popupLabelId}
71
81
  >
72
82
  {children}
73
83
  </Popover>
@@ -104,4 +114,9 @@ const DateDialog = ({
104
114
  </Modal>
105
115
  );
106
116
  };
117
+
118
+ function focusPopoverOnOpen(element: HTMLDivElement | null) {
119
+ focusElement(element, { preventScroll: true, sync: false });
120
+ }
121
+
107
122
  export { DateDialog };
@@ -95,6 +95,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
95
95
  );
96
96
 
97
97
  const ariaId = useId(id);
98
+ const popupLabelId = useId();
98
99
 
99
100
  const [open, setOpen] = useControllableState({
100
101
  defaultValue: false,
@@ -161,6 +162,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
161
162
  id: ariaId,
162
163
  strategy,
163
164
  }}
165
+ popupLabelId={popupLabelId}
164
166
  >
165
167
  <ReactDayPicker
166
168
  {...rest}
@@ -168,6 +170,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
168
170
  handleSelect={setValue}
169
171
  selected={value as any}
170
172
  mode={mode as any}
173
+ popupLabelId={popupLabelId}
171
174
  />
172
175
  </DateDialog>
173
176
  </div>
@@ -1,6 +1,7 @@
1
1
  import { differenceInCalendarDays, isWeekend } from "date-fns";
2
2
  import React, { useCallback, useState } from "react";
3
3
  import { DayEventHandler, dateMatchModifiers } from "react-day-picker";
4
+ import { focusElement } from "../../../utils/helpers/focus";
4
5
  import { useDateLocale } from "../../../utils/i18n/i18n.hooks";
5
6
  import { DateInputProps } from "../../Date.Input";
6
7
  import { getLocaleFromString } from "../../Date.locale";
@@ -160,8 +161,9 @@ export const useDatepicker = (
160
161
  const handleOpen = useCallback(
161
162
  (newOpen: boolean) => {
162
163
  setOpen(newOpen);
163
- newOpen &&
164
+ if (newOpen) {
164
165
  setMonth(selectedDay ?? defaultSelected ?? defaultMonth ?? today);
166
+ }
165
167
  },
166
168
  [defaultMonth, defaultSelected, selectedDay, today],
167
169
  );
@@ -313,7 +315,10 @@ export const useDatepicker = (
313
315
  open,
314
316
  onClose: () => {
315
317
  handleOpen(false);
316
- anchorRef?.focus();
318
+ /* Delay focus to allow "open"-button to update title before focus */
319
+ queueMicrotask(() =>
320
+ focusElement(anchorRef, { sync: false, preventScroll: true }),
321
+ );
317
322
  },
318
323
  onOpenToggle: () => handleOpen(!open),
319
324
  disabled,
@@ -1,6 +1,7 @@
1
1
  import { differenceInCalendarDays, isSameDay, isWeekend } from "date-fns";
2
2
  import React, { useState } from "react";
3
3
  import { dateMatchModifiers } from "react-day-picker";
4
+ import { focusElement } from "../../../utils/helpers/focus";
4
5
  import { useDateLocale } from "../../../utils/i18n/i18n.hooks";
5
6
  import { DateInputProps } from "../../Date.Input";
6
7
  import { getLocaleFromString } from "../../Date.locale";
@@ -539,7 +540,10 @@ export const useRangeDatepicker = (
539
540
  onOpenToggle: () => setOpen((x) => !x),
540
541
  onClose: () => {
541
542
  setOpen(false);
542
- anchorRef?.focus();
543
+ /* Delay focus to allow "open"-button to update title before focus */
544
+ queueMicrotask(() =>
545
+ focusElement(anchorRef, { sync: false, preventScroll: true }),
546
+ );
543
547
  },
544
548
  disabled,
545
549
  disableWeekends,
@@ -28,12 +28,14 @@ const DatePickerMonths = ({
28
28
  calendarMonth,
29
29
  locale,
30
30
  onWeekNumberClick,
31
+ popupLabelId,
31
32
  ...rest
32
33
  }: {
33
34
  calendarMonth: CalendarMonth;
34
35
  displayIndex: number;
35
36
  locale: Locale;
36
37
  onWeekNumberClick: MultipleMode["onWeekNumberClick"];
38
+ popupLabelId?: string;
37
39
  } & React.HTMLAttributes<HTMLDivElement>) => {
38
40
  const { dayPickerProps, goToMonth, previousMonth, nextMonth } =
39
41
  useDayPicker();
@@ -80,7 +82,12 @@ const DatePickerMonths = ({
80
82
  <div {...omit(rest, ["displayIndex"])}>
81
83
  <div className="aksel-date__caption">
82
84
  {captionLayout?.startsWith("dropdown") && (
83
- <span aria-live="polite" aria-atomic="true" className="aksel-sr-only">
85
+ <span
86
+ aria-live="polite"
87
+ aria-atomic="true"
88
+ className="aksel-sr-only"
89
+ id={popupLabelId}
90
+ >
84
91
  {format(calendarMonth.date, "LLLL y", { locale })}
85
92
  </span>
86
93
  )}
@@ -129,6 +136,7 @@ const DatePickerMonths = ({
129
136
  aria-live="polite"
130
137
  role="status"
131
138
  className="aksel-date__caption-label"
139
+ id={popupLabelId}
132
140
  >
133
141
  {format(calendarMonth.date, "LLLL y", { locale })}
134
142
  </BodyShort>
@@ -44,6 +44,10 @@ type ReactDayPickerProps = DatePickerDefaultProps &
44
44
  * Update selected date
45
45
  */
46
46
  handleSelect: (newSelected: any) => void;
47
+ /**
48
+ * Id for the label of the popup, used for aria-labelledby
49
+ */
50
+ popupLabelId?: string;
47
51
  };
48
52
 
49
53
  const ReactDayPicker = ({
@@ -61,6 +65,7 @@ const ReactDayPicker = ({
61
65
  mode: _mode,
62
66
  handleSelect,
63
67
  locale: _locale,
68
+ popupLabelId,
64
69
  ...rest
65
70
  }: ReactDayPickerProps) => {
66
71
  const langProviderLocale = useDateLocale();
@@ -130,9 +135,10 @@ const ReactDayPicker = ({
130
135
  onWeekNumberClick={
131
136
  mode === "multiple" ? onWeekNumberClick : undefined
132
137
  }
138
+ popupLabelId={popupLabelId}
133
139
  />
134
140
  ),
135
- [locale, mode, onWeekNumberClick],
141
+ [locale, mode, onWeekNumberClick, popupLabelId],
136
142
  ),
137
143
  Day: useCallback(
138
144
  (props) => (
@@ -97,6 +97,7 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
97
97
  );
98
98
  const langProviderLocale = useDateLocale();
99
99
  const ariaId = useId(id);
100
+ const popupLabelId = useId();
100
101
 
101
102
  const [open, setOpen] = useControllableState({
102
103
  defaultValue: false,
@@ -163,9 +164,10 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
163
164
  id: ariaId,
164
165
  strategy,
165
166
  }}
167
+ popupLabelId={popupLabelId}
166
168
  >
167
169
  <div className={cl("rdp-month", className)}>
168
- <MonthPickerCaption />
170
+ <MonthPickerCaption popupLabelId={popupLabelId} />
169
171
  <MonthPickerTable />
170
172
  </div>
171
173
  </DateDialog>
@@ -1,5 +1,6 @@
1
1
  import React, { useCallback, useMemo, useState } from "react";
2
2
  import { dateMatchModifiers } from "react-day-picker";
3
+ import { focusElement } from "../../../utils/helpers/focus";
3
4
  import { useDateLocale } from "../../../utils/i18n/i18n.hooks";
4
5
  import { DateInputProps } from "../../Date.Input";
5
6
  import { getLocaleFromString } from "../../Date.locale";
@@ -310,7 +311,10 @@ export const useMonthpicker = (
310
311
  onOpenToggle: () => handleOpen(!open),
311
312
  onClose: () => {
312
313
  handleOpen(false);
313
- anchorRef?.focus();
314
+ /* Delay focus to allow "open"-button to update title before focus */
315
+ queueMicrotask(() =>
316
+ focusElement(anchorRef, { sync: false, preventScroll: true }),
317
+ );
314
318
  },
315
319
  disabled,
316
320
  };
@@ -13,7 +13,11 @@ import { Select } from "../../../form/select";
13
13
  import { useDateTranslationContext } from "../../Date.locale";
14
14
  import { useMonthPickerContext } from "../MonthPicker.context";
15
15
 
16
- const MonthPickerCaption = () => {
16
+ type MonthPickerCaptionProps = {
17
+ popupLabelId?: string;
18
+ };
19
+
20
+ const MonthPickerCaption = ({ popupLabelId }: MonthPickerCaptionProps) => {
17
21
  const { fromDate, toDate, locale, year, onYearChange, caption } =
18
22
  useMonthPickerContext();
19
23
 
@@ -58,6 +62,16 @@ const MonthPickerCaption = () => {
58
62
 
59
63
  return (
60
64
  <div className="aksel-date__caption">
65
+ {caption === "dropdown" && (
66
+ <span
67
+ aria-live="polite"
68
+ aria-atomic="true"
69
+ className="aksel-sr-only"
70
+ id={popupLabelId}
71
+ >
72
+ {year.getFullYear()}
73
+ </span>
74
+ )}
61
75
  <Button
62
76
  className="aksel-date__caption-button"
63
77
  disabled={disablePreviousYear()}
@@ -82,7 +96,11 @@ const MonthPickerCaption = () => {
82
96
  ))}
83
97
  </Select>
84
98
  ) : (
85
- <span className="aksel-date__year-label" aria-live="polite">
99
+ <span
100
+ className="aksel-date__year-label"
101
+ aria-live="polite"
102
+ id={popupLabelId}
103
+ >
86
104
  {year.getFullYear()}
87
105
  </span>
88
106
  )}
@@ -1,5 +1,6 @@
1
1
  import React, { forwardRef, useContext } from "react";
2
2
  import { cl, composeEventHandlers } from "../utils/helpers";
3
+ import { useMergeRefs } from "../utils/hooks";
3
4
  import { DropdownContext } from "./context";
4
5
 
5
6
  export interface DropdownToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
@@ -15,30 +16,23 @@ export const DropdownToggle = forwardRef<
15
16
  >(({ className, onClick, ...rest }, ref) => {
16
17
  const context = useContext(DropdownContext);
17
18
 
19
+ const mergedRef = useMergeRefs(context?.setAnchorEl, ref);
20
+
18
21
  if (!context) {
19
22
  console.warn("Dropdown.Toggle has to be wrapped in <Dropdown />");
20
23
  return null;
21
24
  }
22
25
 
23
- const { setAnchorEl, handleToggle, isOpen } = context;
26
+ const { handleToggle, isOpen } = context;
24
27
 
25
- const handleClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
26
- setAnchorEl(e.currentTarget);
28
+ const handleClick = () => {
27
29
  handleToggle(!isOpen);
28
30
  };
29
31
 
30
32
  return (
31
33
  <button
32
34
  {...rest}
33
- ref={(el) => {
34
- setAnchorEl(el);
35
-
36
- if (typeof ref === "function") {
37
- ref(el);
38
- } else if (ref != null) {
39
- ref.current = el;
40
- }
41
- }}
35
+ ref={mergedRef}
42
36
  onClick={composeEventHandlers(onClick, handleClick)}
43
37
  aria-expanded={isOpen}
44
38
  className={cl("aksel-dropdown__toggle", className)}
@@ -255,8 +255,8 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
255
255
  );
256
256
 
257
257
  const onChangeHandler = useCallback(
258
- (event: React.ChangeEvent<HTMLInputElement>) => {
259
- const newValue = event.target.value;
258
+ (event: React.InputEvent<HTMLInputElement>) => {
259
+ const newValue = event.currentTarget.value;
260
260
  if (newValue && newValue !== "") {
261
261
  toggleIsListOpen(true);
262
262
  } else if (filteredOptions.length === 0) {
@@ -1,6 +1,6 @@
1
1
  import React, { forwardRef } from "react";
2
2
  import { useThemeInternal } from "../../theme/Theme";
3
- import { BodyShort } from "../../typography";
3
+ import { BodyLong } from "../../typography";
4
4
  import { type OverridableComponent, useId } from "../../utils-external";
5
5
  import { cl } from "../../utils/helpers";
6
6
  import { useI18n } from "../../utils/i18n/i18n.hooks";
@@ -64,7 +64,7 @@ export const InlineMessage: OverridableComponent<
64
64
  const contentId = useId();
65
65
 
66
66
  return (
67
- <BodyShort
67
+ <BodyLong
68
68
  ref={forwardedRef}
69
69
  className={cl("aksel-inline-message", className)}
70
70
  data-color={status === "error" ? "danger" : status}
@@ -75,9 +75,9 @@ export const InlineMessage: OverridableComponent<
75
75
  >
76
76
  <InlineMessageIcon status={status} />
77
77
  {status && (
78
- <BodyShort id={statusId} aria-hidden visuallyHidden>
78
+ <BodyLong id={statusId} aria-hidden visuallyHidden>
79
79
  {`${translate(status)}: `}
80
- </BodyShort>
80
+ </BodyLong>
81
81
  )}
82
82
  {/** biome-ignore lint/a11y/useAriaPropsSupportedByRole: Testing shows that this works. */}
83
83
  <span
@@ -87,7 +87,7 @@ export const InlineMessage: OverridableComponent<
87
87
  >
88
88
  {children}
89
89
  </span>
90
- </BodyShort>
90
+ </BodyLong>
91
91
  );
92
92
  },
93
93
  );
@@ -3,7 +3,7 @@ import { PartialTranslations, Translations } from "../utils/i18n/i18n.types";
3
3
  import nb from "../utils/i18n/locales/nb";
4
4
 
5
5
  type ProviderContextType = {
6
- rootElement?: HTMLElement;
6
+ rootElement?: HTMLElement | null;
7
7
  locale: Translations;
8
8
  translations?: PartialTranslations | PartialTranslations[];
9
9
  };
@@ -17,7 +17,7 @@ export type ProviderProps = {
17
17
  /**
18
18
  * Global root-element to attach portals to. Used by Tooltip, Modal (optionally) and ActionMenu.
19
19
  */
20
- rootElement?: HTMLElement;
20
+ rootElement?: HTMLElement | null;
21
21
  } & (
22
22
  | {
23
23
  /**
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import { useEffect, useState } from "react";
2
2
  import { useControllableState } from "../utils/hooks";
3
3
  import { ToggleGroupProps } from "./ToggleGroup.types";
4
4
 
@@ -8,7 +8,6 @@ export function useToggleGroup({
8
8
  defaultValue = "",
9
9
  }: Pick<ToggleGroupProps, "onChange" | "value" | "defaultValue">) {
10
10
  const [focusedValue, setFocusedValue] = useState(defaultValue);
11
-
12
11
  const [selectedValue, setSelectedValue] = useControllableState({
13
12
  defaultValue,
14
13
  value,
@@ -16,9 +15,11 @@ export function useToggleGroup({
16
15
  });
17
16
 
18
17
  /* Sync focused `value` with controlled `selectedValue` */
19
- if (value != null && value !== focusedValue) {
20
- setFocusedValue(value);
21
- }
18
+ useEffect(() => {
19
+ if (value != null) {
20
+ setFocusedValue(value);
21
+ }
22
+ }, [value]);
22
23
 
23
24
  return {
24
25
  selectedValue,
@@ -237,9 +237,7 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
237
237
  <TooltipShortcuts shortcuts={keys} />
238
238
  {_arrow && (
239
239
  <div
240
- ref={(node) => {
241
- arrowRef.current = node;
242
- }}
240
+ ref={arrowRef}
243
241
  className="aksel-tooltip__arrow"
244
242
  style={{
245
243
  left: arrowX != null ? `${arrowX}px` : "",
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+
3
+ // Før vi ev. eksponerer denne, vurder følgende:
4
+ // - Bør man kunne slå av aria-label? (Ev. kan vi kun sette på aria-label hvis process.env.NODE_ENV === "test")
5
+ // - Navngivning. Bør ikke bruke kun Highlight fordi det er allerede noe som heter det i JS.
6
+
7
+ interface HighlightTextProps {
8
+ /** Text to be highlighted */
9
+ text: string;
10
+ children: string;
11
+ }
12
+
13
+ const HighlightText = ({ text, children }: HighlightTextProps) => {
14
+ const textIndex = children
15
+ .toLocaleLowerCase()
16
+ .indexOf(text.toLocaleLowerCase());
17
+ if (textIndex === -1) {
18
+ return children;
19
+ }
20
+ const start = children.substring(0, textIndex);
21
+ const match = children.substring(textIndex, textIndex + text.length);
22
+ const end = children.substring(textIndex + text.length);
23
+ return (
24
+ // aria-label is used to fix testing-library wrongly evaluating the accessible name of the option when highlighting text
25
+ // biome-ignore lint/a11y/useAriaPropsSupportedByRole: Doesn't matter if it doesn't work in the browser
26
+ <span aria-label={children}>
27
+ {start}
28
+ {match && <mark>{match}</mark>}
29
+ {end}
30
+ </span>
31
+ );
32
+ };
33
+
34
+ export { HighlightText };
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import { useId } from "../../../../utils-external";
3
+
4
+ interface ListboxGroupProps {
5
+ label: React.ReactNode;
6
+ children: React.ReactNode;
7
+ }
8
+
9
+ function ListboxGroup({ label, children }: ListboxGroupProps) {
10
+ const labelId = useId();
11
+
12
+ return (
13
+ <div
14
+ role="group"
15
+ className="aksel-listbox__group"
16
+ aria-labelledby={labelId}
17
+ >
18
+ <div id={labelId} aria-hidden>
19
+ {label}
20
+ </div>
21
+ {children}
22
+ </div>
23
+ );
24
+ }
25
+
26
+ export { ListboxGroup };
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ import { Slot } from "../../slot/Slot";
3
+
4
+ interface ListboxInputSlotProps {
5
+ children: React.ReactElement;
6
+ }
7
+
8
+ const ListboxInputSlot = ({ children }: ListboxInputSlotProps) => {
9
+ return (
10
+ <Slot
11
+ aria-activedescendant="aksel-listbox__item-active"
12
+ // @ts-expect-error - You are meant to use an <input>, but Slot doesn't know that.
13
+ autoComplete="off"
14
+ role="combobox"
15
+ >
16
+ {children}
17
+ </Slot>
18
+ );
19
+ };
20
+
21
+ export { ListboxInputSlot };
22
+ export type { ListboxInputSlotProps };
@@ -0,0 +1,57 @@
1
+ import React from "react";
2
+ import { cl } from "../../../helpers";
3
+
4
+ export interface ListboxItemProps extends Omit<
5
+ React.HTMLAttributes<HTMLDivElement>,
6
+ "role" | "tabIndex"
7
+ > {
8
+ /**
9
+ * Unique ID used for tracking which item has virtual focus.
10
+ */
11
+ id: string;
12
+ hasVirtualFocus: boolean;
13
+ children: React.ReactNode;
14
+ /**
15
+ * Callback when item is selected.
16
+ * To improve performance when you have many items,
17
+ * memoize the prop with e.g. useEventCallback.
18
+ */
19
+ onClick: React.MouseEventHandler<HTMLDivElement>;
20
+ }
21
+
22
+ function ListboxItemComponent({
23
+ id,
24
+ hasVirtualFocus,
25
+ children,
26
+ className,
27
+ ...rest
28
+ }: ListboxItemProps) {
29
+ //console.log("Rendering item", id);
30
+
31
+ // TODO: Slot?
32
+
33
+ return (
34
+ <div
35
+ aria-selected={false}
36
+ {...rest}
37
+ className={cl("aksel-listbox__item", className)}
38
+ role="option"
39
+ tabIndex={-1}
40
+ data-virtual-focus={hasVirtualFocus}
41
+ data-id={id}
42
+ id={hasVirtualFocus ? "aksel-listbox__item-active" : undefined}
43
+ >
44
+ {children}
45
+ </div>
46
+ );
47
+ }
48
+
49
+ /**
50
+ * This component is memoized. To improve performance when you have many items,
51
+ * make sure all object props have stable references (i.e. memoize the event handlers with e.g. useEventCallback).
52
+ *
53
+ * NB: Remember to set `aria-selected` on selected items!
54
+ */
55
+ export const ListboxItem = React.memo(
56
+ ListboxItemComponent,
57
+ ) as typeof ListboxItemComponent;
@@ -0,0 +1,38 @@
1
+ /* eslint-disable jsx-a11y/mouse-events-have-key-events */
2
+ /** biome-ignore-all lint/a11y/useKeyWithMouseEvents: We know what we are doing */
3
+ import React from "react";
4
+ import { cl } from "../../../helpers";
5
+
6
+ export interface ListboxListProps extends Omit<
7
+ React.HTMLAttributes<HTMLDivElement>,
8
+ "role" | "tabIndex" | "onMouseOver"
9
+ > {
10
+ children: React.ReactNode;
11
+ setVirtuallyFocusedItemId: (value: string) => void;
12
+ }
13
+
14
+ function ListboxList({
15
+ children,
16
+ setVirtuallyFocusedItemId,
17
+ ...rest
18
+ }: ListboxListProps) {
19
+ return (
20
+ <div
21
+ {...rest}
22
+ className={cl(rest.className, "aksel-listbox__list")}
23
+ role="listbox"
24
+ tabIndex={-1}
25
+ onMouseOver={(event) => {
26
+ const target = event.target as HTMLElement;
27
+ const itemEl: HTMLElement | null = target.closest('[role="option"]');
28
+ if (itemEl) {
29
+ setVirtuallyFocusedItemId(itemEl?.dataset.id || "");
30
+ }
31
+ }}
32
+ >
33
+ {children}
34
+ </div>
35
+ );
36
+ }
37
+
38
+ export { ListboxList };