@navikt/ds-react 7.32.0 → 7.32.2

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 (214) hide show
  1. package/cjs/copybutton/CopyButton.js +4 -9
  2. package/cjs/copybutton/CopyButton.js.map +1 -1
  3. package/cjs/date/date-utils/dropdown-options.d.ts +11 -2
  4. package/cjs/date/date-utils/dropdown-options.js +5 -3
  5. package/cjs/date/date-utils/dropdown-options.js.map +1 -1
  6. package/cjs/date/datepicker/parts/DatePicker.Months.js +7 -2
  7. package/cjs/date/datepicker/parts/DatePicker.Months.js.map +1 -1
  8. package/cjs/form/combobox/Combobox.js +1 -3
  9. package/cjs/form/combobox/Combobox.js.map +1 -1
  10. package/cjs/form/combobox/ComboboxWrapper.d.ts +1 -2
  11. package/cjs/form/combobox/ComboboxWrapper.js +1 -2
  12. package/cjs/form/combobox/ComboboxWrapper.js.map +1 -1
  13. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +28 -19
  14. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  15. package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
  16. package/cjs/form/combobox/Input/Input.context.d.ts +2 -0
  17. package/cjs/form/combobox/Input/Input.context.js +4 -1
  18. package/cjs/form/combobox/Input/Input.context.js.map +1 -1
  19. package/cjs/form/combobox/Input/InputController.js +2 -2
  20. package/cjs/form/combobox/Input/InputController.js.map +1 -1
  21. package/cjs/help-text/HelpText.js +3 -3
  22. package/cjs/help-text/HelpText.js.map +1 -1
  23. package/cjs/help-text/HelpTextIcon.d.ts +1 -2
  24. package/cjs/help-text/HelpTextIcon.js +3 -7
  25. package/cjs/help-text/HelpTextIcon.js.map +1 -1
  26. package/cjs/layout/page/parts/PageBlock.d.ts +9 -2
  27. package/cjs/layout/page/parts/PageBlock.js.map +1 -1
  28. package/cjs/modal/ModalUtils.js +6 -4
  29. package/cjs/modal/ModalUtils.js.map +1 -1
  30. package/cjs/overlays/dismissablelayer/DismissableLayer.js +9 -19
  31. package/cjs/overlays/dismissablelayer/DismissableLayer.js.map +1 -1
  32. package/cjs/overlays/dismissablelayer/util/usePointerDownOutside.js +5 -4
  33. package/cjs/overlays/dismissablelayer/util/usePointerDownOutside.js.map +1 -1
  34. package/cjs/overlays/floating-menu/Menu.d.ts +4 -4
  35. package/cjs/overlays/floating-menu/Menu.js +7 -4
  36. package/cjs/overlays/floating-menu/Menu.js.map +1 -1
  37. package/cjs/overlays/floating-menu/parts/RovingFocus.js +3 -3
  38. package/cjs/overlays/floating-menu/parts/RovingFocus.js.map +1 -1
  39. package/cjs/overlays/overlay/hooks/useAnimationsFinished.js +1 -1
  40. package/cjs/overlays/overlay/hooks/useAnimationsFinished.js.map +1 -1
  41. package/cjs/overlays/overlay/hooks/useOpenChangeAnimationComplete.js +2 -2
  42. package/cjs/overlays/overlay/hooks/useOpenChangeAnimationComplete.js.map +1 -1
  43. package/cjs/progress-bar/ProgressBar.js +9 -6
  44. package/cjs/progress-bar/ProgressBar.js.map +1 -1
  45. package/cjs/table/AnimateHeight.js +12 -13
  46. package/cjs/table/AnimateHeight.js.map +1 -1
  47. package/cjs/tabs/parts/tablist/useScrollButtons.d.ts +1 -1
  48. package/cjs/tabs/parts/tablist/useScrollButtons.js +4 -4
  49. package/cjs/tabs/parts/tablist/useScrollButtons.js.map +1 -1
  50. package/cjs/util/TextareaAutoSize.js +3 -10
  51. package/cjs/util/TextareaAutoSize.js.map +1 -1
  52. package/cjs/util/create-context.d.ts +0 -1
  53. package/cjs/util/create-context.js.map +1 -1
  54. package/cjs/util/debounce.d.ts +1 -1
  55. package/cjs/util/debounce.js +5 -8
  56. package/cjs/util/debounce.js.map +1 -1
  57. package/cjs/util/detectBrowser.d.ts +2 -0
  58. package/cjs/util/detectBrowser.js +7 -0
  59. package/cjs/util/detectBrowser.js.map +1 -0
  60. package/cjs/util/focus-boundary/FocusBoundary.d.ts +44 -0
  61. package/cjs/util/focus-boundary/FocusBoundary.js +365 -0
  62. package/cjs/util/focus-boundary/FocusBoundary.js.map +1 -0
  63. package/cjs/util/focus-guards/FocusGuards.d.ts +8 -0
  64. package/cjs/util/focus-guards/FocusGuards.js +36 -0
  65. package/cjs/util/focus-guards/FocusGuards.js.map +1 -0
  66. package/cjs/util/hooks/useEventCallback.js.map +1 -0
  67. package/cjs/{overlays/overlay → util}/hooks/useLatestRef.js +2 -2
  68. package/cjs/util/hooks/useLatestRef.js.map +1 -0
  69. package/cjs/util/hooks/useRefWithInit.js.map +1 -0
  70. package/cjs/util/hooks/useTimeout.d.ts +16 -0
  71. package/cjs/util/hooks/useTimeout.js +49 -0
  72. package/cjs/util/hooks/useTimeout.js.map +1 -0
  73. package/cjs/util/link-anchor/LinkAnchor.js +6 -7
  74. package/cjs/util/link-anchor/LinkAnchor.js.map +1 -1
  75. package/cjs/util/owner.d.ts +29 -0
  76. package/cjs/util/owner.js +38 -0
  77. package/cjs/util/owner.js.map +1 -0
  78. package/esm/copybutton/CopyButton.js +5 -10
  79. package/esm/copybutton/CopyButton.js.map +1 -1
  80. package/esm/date/date-utils/dropdown-options.d.ts +11 -2
  81. package/esm/date/date-utils/dropdown-options.js +5 -3
  82. package/esm/date/date-utils/dropdown-options.js.map +1 -1
  83. package/esm/date/datepicker/parts/DatePicker.Months.js +7 -2
  84. package/esm/date/datepicker/parts/DatePicker.Months.js.map +1 -1
  85. package/esm/form/combobox/Combobox.js +1 -3
  86. package/esm/form/combobox/Combobox.js.map +1 -1
  87. package/esm/form/combobox/ComboboxWrapper.d.ts +1 -2
  88. package/esm/form/combobox/ComboboxWrapper.js +1 -2
  89. package/esm/form/combobox/ComboboxWrapper.js.map +1 -1
  90. package/esm/form/combobox/FilteredOptions/FilteredOptions.js +29 -20
  91. package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  92. package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
  93. package/esm/form/combobox/Input/Input.context.d.ts +2 -0
  94. package/esm/form/combobox/Input/Input.context.js +4 -1
  95. package/esm/form/combobox/Input/Input.context.js.map +1 -1
  96. package/esm/form/combobox/Input/InputController.js +2 -2
  97. package/esm/form/combobox/Input/InputController.js.map +1 -1
  98. package/esm/help-text/HelpText.js +3 -3
  99. package/esm/help-text/HelpText.js.map +1 -1
  100. package/esm/help-text/HelpTextIcon.d.ts +1 -2
  101. package/esm/help-text/HelpTextIcon.js +3 -7
  102. package/esm/help-text/HelpTextIcon.js.map +1 -1
  103. package/esm/layout/page/parts/PageBlock.d.ts +9 -2
  104. package/esm/layout/page/parts/PageBlock.js.map +1 -1
  105. package/esm/modal/ModalUtils.js +6 -4
  106. package/esm/modal/ModalUtils.js.map +1 -1
  107. package/esm/overlays/dismissablelayer/DismissableLayer.js +9 -19
  108. package/esm/overlays/dismissablelayer/DismissableLayer.js.map +1 -1
  109. package/esm/overlays/dismissablelayer/util/usePointerDownOutside.js +5 -4
  110. package/esm/overlays/dismissablelayer/util/usePointerDownOutside.js.map +1 -1
  111. package/esm/overlays/floating-menu/Menu.d.ts +4 -4
  112. package/esm/overlays/floating-menu/Menu.js +7 -4
  113. package/esm/overlays/floating-menu/Menu.js.map +1 -1
  114. package/esm/overlays/floating-menu/parts/RovingFocus.js +3 -3
  115. package/esm/overlays/floating-menu/parts/RovingFocus.js.map +1 -1
  116. package/esm/overlays/overlay/hooks/useAnimationsFinished.js +1 -1
  117. package/esm/overlays/overlay/hooks/useAnimationsFinished.js.map +1 -1
  118. package/esm/overlays/overlay/hooks/useOpenChangeAnimationComplete.js +2 -2
  119. package/esm/overlays/overlay/hooks/useOpenChangeAnimationComplete.js.map +1 -1
  120. package/esm/progress-bar/ProgressBar.js +10 -7
  121. package/esm/progress-bar/ProgressBar.js.map +1 -1
  122. package/esm/table/AnimateHeight.js +12 -13
  123. package/esm/table/AnimateHeight.js.map +1 -1
  124. package/esm/tabs/parts/tablist/useScrollButtons.d.ts +1 -1
  125. package/esm/tabs/parts/tablist/useScrollButtons.js +4 -4
  126. package/esm/tabs/parts/tablist/useScrollButtons.js.map +1 -1
  127. package/esm/util/TextareaAutoSize.js +1 -8
  128. package/esm/util/TextareaAutoSize.js.map +1 -1
  129. package/esm/util/create-context.d.ts +0 -1
  130. package/esm/util/create-context.js.map +1 -1
  131. package/esm/util/debounce.d.ts +1 -1
  132. package/esm/util/debounce.js +5 -8
  133. package/esm/util/debounce.js.map +1 -1
  134. package/esm/util/detectBrowser.d.ts +2 -0
  135. package/esm/util/detectBrowser.js +4 -0
  136. package/esm/util/detectBrowser.js.map +1 -0
  137. package/esm/util/focus-boundary/FocusBoundary.d.ts +44 -0
  138. package/esm/util/focus-boundary/FocusBoundary.js +329 -0
  139. package/esm/util/focus-boundary/FocusBoundary.js.map +1 -0
  140. package/esm/util/focus-guards/FocusGuards.d.ts +8 -0
  141. package/esm/util/focus-guards/FocusGuards.js +31 -0
  142. package/esm/util/focus-guards/FocusGuards.js.map +1 -0
  143. package/esm/util/hooks/useEventCallback.js.map +1 -0
  144. package/esm/{overlays/overlay → util}/hooks/useLatestRef.js +1 -1
  145. package/esm/util/hooks/useLatestRef.js.map +1 -0
  146. package/esm/util/hooks/useRefWithInit.js.map +1 -0
  147. package/esm/util/hooks/useTimeout.d.ts +16 -0
  148. package/esm/util/hooks/useTimeout.js +45 -0
  149. package/esm/util/hooks/useTimeout.js.map +1 -0
  150. package/esm/util/link-anchor/LinkAnchor.js +6 -7
  151. package/esm/util/link-anchor/LinkAnchor.js.map +1 -1
  152. package/esm/util/owner.d.ts +29 -0
  153. package/esm/util/owner.js +35 -0
  154. package/esm/util/owner.js.map +1 -0
  155. package/package.json +8 -8
  156. package/src/copybutton/CopyButton.tsx +5 -17
  157. package/src/date/date-utils/dropdown-options.test.ts +37 -9
  158. package/src/date/date-utils/dropdown-options.ts +23 -12
  159. package/src/date/datepicker/parts/DatePicker.Months.tsx +8 -2
  160. package/src/form/combobox/Combobox.tsx +0 -4
  161. package/src/form/combobox/ComboboxWrapper.tsx +0 -3
  162. package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +65 -45
  163. package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +1 -0
  164. package/src/form/combobox/Input/Input.context.tsx +5 -0
  165. package/src/form/combobox/Input/InputController.tsx +2 -1
  166. package/src/form/file-upload/parts/item/utils/format-file-size.test.ts +2 -2
  167. package/src/help-text/HelpText.tsx +3 -2
  168. package/src/help-text/HelpTextIcon.tsx +2 -12
  169. package/src/layout/page/parts/PageBlock.tsx +9 -2
  170. package/src/modal/ModalUtils.ts +7 -4
  171. package/src/overlays/dismissablelayer/DismissableLayer.tsx +9 -18
  172. package/src/overlays/dismissablelayer/util/usePointerDownOutside.ts +5 -4
  173. package/src/overlays/floating-menu/Menu.tsx +13 -9
  174. package/src/overlays/floating-menu/parts/RovingFocus.tsx +3 -3
  175. package/src/overlays/overlay/hooks/useAnimationsFinished.ts +1 -1
  176. package/src/overlays/overlay/hooks/useOpenChangeAnimationComplete.ts +2 -2
  177. package/src/progress-bar/ProgressBar.tsx +12 -10
  178. package/src/table/AnimateHeight.tsx +12 -15
  179. package/src/tabs/parts/tablist/useScrollButtons.ts +4 -3
  180. package/src/util/TextareaAutoSize.tsx +1 -9
  181. package/src/util/create-context.tsx +0 -1
  182. package/src/util/debounce.ts +7 -8
  183. package/src/util/detectBrowser.ts +5 -0
  184. package/src/util/focus-boundary/FocusBoundary.tsx +453 -0
  185. package/src/util/focus-guards/FocusGuards.tsx +56 -0
  186. package/src/{overlays/overlay → util}/hooks/useLatestRef.ts +1 -1
  187. package/src/util/hooks/useTimeout.ts +54 -0
  188. package/src/util/link-anchor/LinkAnchor.tsx +7 -6
  189. package/src/util/owner.ts +35 -0
  190. package/cjs/overlays/floating-menu/parts/FocusScope.d.ts +0 -22
  191. package/cjs/overlays/floating-menu/parts/FocusScope.js +0 -98
  192. package/cjs/overlays/floating-menu/parts/FocusScope.js.map +0 -1
  193. package/cjs/overlays/overlay/hooks/useEventCallback.js.map +0 -1
  194. package/cjs/overlays/overlay/hooks/useLatestRef.js.map +0 -1
  195. package/cjs/overlays/overlay/hooks/useRefWithInit.js.map +0 -1
  196. package/esm/overlays/floating-menu/parts/FocusScope.d.ts +0 -22
  197. package/esm/overlays/floating-menu/parts/FocusScope.js +0 -62
  198. package/esm/overlays/floating-menu/parts/FocusScope.js.map +0 -1
  199. package/esm/overlays/overlay/hooks/useEventCallback.js.map +0 -1
  200. package/esm/overlays/overlay/hooks/useLatestRef.js.map +0 -1
  201. package/esm/overlays/overlay/hooks/useRefWithInit.js.map +0 -1
  202. package/src/overlays/floating-menu/parts/FocusScope.tsx +0 -83
  203. /package/cjs/{overlays/overlay → util}/hooks/useEventCallback.d.ts +0 -0
  204. /package/cjs/{overlays/overlay → util}/hooks/useEventCallback.js +0 -0
  205. /package/cjs/{overlays/overlay → util}/hooks/useLatestRef.d.ts +0 -0
  206. /package/cjs/{overlays/overlay → util}/hooks/useRefWithInit.d.ts +0 -0
  207. /package/cjs/{overlays/overlay → util}/hooks/useRefWithInit.js +0 -0
  208. /package/esm/{overlays/overlay → util}/hooks/useEventCallback.d.ts +0 -0
  209. /package/esm/{overlays/overlay → util}/hooks/useEventCallback.js +0 -0
  210. /package/esm/{overlays/overlay → util}/hooks/useLatestRef.d.ts +0 -0
  211. /package/esm/{overlays/overlay → util}/hooks/useRefWithInit.d.ts +0 -0
  212. /package/esm/{overlays/overlay → util}/hooks/useRefWithInit.js +0 -0
  213. /package/src/{overlays/overlay → util}/hooks/useEventCallback.ts +0 -0
  214. /package/src/{overlays/overlay → util}/hooks/useRefWithInit.ts +0 -0
@@ -1,16 +1,11 @@
1
- import React, {
2
- ButtonHTMLAttributes,
3
- forwardRef,
4
- useEffect,
5
- useRef,
6
- useState,
7
- } from "react";
1
+ import React, { ButtonHTMLAttributes, forwardRef, useState } from "react";
8
2
  import { CheckmarkIcon, FilesIcon } from "@navikt/aksel-icons";
9
3
  import { Button, ButtonProps } from "../button";
10
4
  import { useRenameCSS, useThemeInternal } from "../theme/Theme";
11
5
  import { Label } from "../typography";
12
6
  import { composeEventHandlers } from "../util/composeEventHandlers";
13
7
  import copy from "../util/copy";
8
+ import { useTimeout } from "../util/hooks/useTimeout";
14
9
  import { useI18n } from "../util/i18n/i18n.hooks";
15
10
 
16
11
  export interface CopyButtonProps
@@ -94,29 +89,22 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
94
89
  ref,
95
90
  ) => {
96
91
  const [active, setActive] = useState(false);
97
- const timeoutRef = useRef<number>(undefined);
98
92
  const translate = useI18n("CopyButton");
93
+ const timeout = useTimeout();
99
94
 
100
95
  const { cn } = useRenameCSS();
101
96
 
102
97
  const themeContext = useThemeInternal(false);
103
98
 
104
- useEffect(() => {
105
- return () => {
106
- timeoutRef.current && clearTimeout(timeoutRef.current);
107
- };
108
- }, []);
109
-
110
99
  const handleClick = () => {
111
- timeoutRef.current && clearTimeout(timeoutRef.current);
112
100
  copy(copyText);
113
101
  setActive(true);
114
102
  onActiveChange?.(true);
115
103
 
116
- timeoutRef.current = window.setTimeout(() => {
104
+ timeout.start(activeDuration, () => {
117
105
  setActive(false);
118
106
  onActiveChange?.(false);
119
- }, activeDuration);
107
+ });
120
108
  };
121
109
 
122
110
  const activeString = activeText || translate("activeText");
@@ -4,19 +4,27 @@ import { getMonthOptions, getYearOptions } from "./dropdown-options";
4
4
 
5
5
  describe("getYearOptions", () => {
6
6
  test("should return undefined if navStart is undefined", () => {
7
- const result = getYearOptions(undefined, new Date(), nb);
7
+ const result = getYearOptions({
8
+ navStart: undefined,
9
+ navEnd: new Date(),
10
+ locale: nb,
11
+ });
8
12
  expect(result).toBeUndefined();
9
13
  });
10
14
 
11
15
  test("should return undefined if navEnd is undefined", () => {
12
- const result = getYearOptions(new Date(), undefined, nb);
16
+ const result = getYearOptions({
17
+ navStart: new Date(),
18
+ navEnd: undefined,
19
+ locale: nb,
20
+ });
13
21
  expect(result).toBeUndefined();
14
22
  });
15
23
 
16
24
  test("should return the correct year options within the interval", () => {
17
25
  const navStart = new Date(2020, 0, 1); // Januar 1, 2020
18
26
  const navEnd = new Date(2022, 11, 31); // Desember 31, 2022
19
- const result = getYearOptions(navStart, navEnd, nb);
27
+ const result = getYearOptions({ navStart, navEnd, locale: nb });
20
28
 
21
29
  const expected = [
22
30
  { value: 2020, label: "2020", disabled: false },
@@ -30,7 +38,7 @@ describe("getYearOptions", () => {
30
38
  test("should return the correct year options for a single year", () => {
31
39
  const navStart = new Date(2021, 0, 1); // Januar 1, 2021
32
40
  const navEnd = new Date(2021, 11, 31); // Desember 31, 2021
33
- const result = getYearOptions(navStart, navEnd, nb);
41
+ const result = getYearOptions({ navStart, navEnd, locale: nb });
34
42
 
35
43
  const expected = [{ value: 2021, label: "2021", disabled: false }];
36
44
 
@@ -40,7 +48,7 @@ describe("getYearOptions", () => {
40
48
  test("should return the correct year options when navStart and navEnd are the same date", () => {
41
49
  const navStart = new Date(2021, 0, 1); // Januar 1, 2021
42
50
  const navEnd = new Date(2021, 0, 1); // Januar 1, 2021
43
- const result = getYearOptions(navStart, navEnd, nb);
51
+ const result = getYearOptions({ navStart, navEnd, locale: nb });
44
52
 
45
53
  const expected = [{ value: 2021, label: "2021", disabled: false }];
46
54
 
@@ -51,7 +59,12 @@ describe("getYearOptions", () => {
51
59
  describe("getMonthOptions", () => {
52
60
  test("should return the correct month options for the given year", () => {
53
61
  const displayMonth = new Date(2021, 0, 1); // Januar 1, 2021
54
- const result = getMonthOptions(displayMonth, undefined, undefined, nb);
62
+ const result = getMonthOptions({
63
+ displayMonth,
64
+ navStart: undefined,
65
+ navEnd: undefined,
66
+ locale: nb,
67
+ });
55
68
 
56
69
  const expected = [
57
70
  { value: 0, label: "januar", disabled: false },
@@ -74,7 +87,12 @@ describe("getMonthOptions", () => {
74
87
  test("should disable months before navStart", () => {
75
88
  const displayMonth = new Date(2021, 0, 1); // Januar 1, 2021
76
89
  const navStart = new Date(2021, 5, 1); // Juni 1, 2021
77
- const result = getMonthOptions(displayMonth, navStart, undefined, nb);
90
+ const result = getMonthOptions({
91
+ displayMonth,
92
+ navStart,
93
+ navEnd: undefined,
94
+ locale: nb,
95
+ });
78
96
 
79
97
  const expected = [
80
98
  { value: 0, label: "januar", disabled: true },
@@ -97,7 +115,12 @@ describe("getMonthOptions", () => {
97
115
  test("should disable months after navEnd", () => {
98
116
  const displayMonth = new Date(2021, 0, 1); // Januar 1, 2021
99
117
  const navEnd = new Date(2021, 5, 1); // Juni 1, 2021
100
- const result = getMonthOptions(displayMonth, undefined, navEnd, nb);
118
+ const result = getMonthOptions({
119
+ displayMonth,
120
+ navStart: undefined,
121
+ navEnd,
122
+ locale: nb,
123
+ });
101
124
 
102
125
  const expected = [
103
126
  { value: 0, label: "januar", disabled: false },
@@ -121,7 +144,12 @@ describe("getMonthOptions", () => {
121
144
  const displayMonth = new Date(2021, 0, 1); // Januar 1, 2021
122
145
  const navStart = new Date(2021, 3, 1); // April 1, 2021
123
146
  const navEnd = new Date(2021, 8, 1); // September 1, 2021
124
- const result = getMonthOptions(displayMonth, navStart, navEnd, nb);
147
+ const result = getMonthOptions({
148
+ displayMonth,
149
+ navStart,
150
+ navEnd,
151
+ locale: nb,
152
+ });
125
153
 
126
154
  const expected = [
127
155
  { value: 0, label: "januar", disabled: true },
@@ -13,12 +13,17 @@ import {
13
13
  } from "date-fns";
14
14
 
15
15
  /** Return the months to show in the dropdown. */
16
- export function getMonthOptions(
17
- displayMonth: Date,
18
- navStart: Date | undefined,
19
- navEnd: Date | undefined,
20
- locale: Locale,
21
- ):
16
+ export function getMonthOptions({
17
+ displayMonth,
18
+ navStart,
19
+ navEnd,
20
+ locale,
21
+ }: {
22
+ displayMonth: Date;
23
+ navStart: Date | undefined;
24
+ navEnd: Date | undefined;
25
+ locale: Locale;
26
+ }):
22
27
  | {
23
28
  value: number;
24
29
  label: string;
@@ -31,7 +36,9 @@ export function getMonthOptions(
31
36
  });
32
37
 
33
38
  const options = months.map((month) => {
34
- const label = format(month, "LLLL", { locale });
39
+ const label = format(month, "LLLL", {
40
+ locale,
41
+ }).replace(".", "");
35
42
  const value = getMonth(month);
36
43
  const disabled =
37
44
  (navStart && month < startOfMonth(navStart)) ||
@@ -44,11 +51,15 @@ export function getMonthOptions(
44
51
  }
45
52
 
46
53
  /** Return the years to show in the dropdown. */
47
- export function getYearOptions(
48
- navStart: Date | undefined,
49
- navEnd: Date | undefined,
50
- locale: Locale,
51
- ):
54
+ export function getYearOptions({
55
+ navStart,
56
+ navEnd,
57
+ locale,
58
+ }: {
59
+ navStart: Date | undefined;
60
+ navEnd: Date | undefined;
61
+ locale: Locale;
62
+ }):
52
63
  | {
53
64
  value: number;
54
65
  label: string;
@@ -69,8 +69,14 @@ const DatePickerMonths = ({
69
69
  today: dayPickerProps.today,
70
70
  });
71
71
 
72
- const months = getMonthOptions(calendarMonth.date, navStart, navEnd, locale);
73
- const dropdownYears = getYearOptions(navStart, navEnd, locale);
72
+ const months = getMonthOptions({
73
+ displayMonth: calendarMonth.date,
74
+ navStart,
75
+ navEnd,
76
+ locale,
77
+ });
78
+
79
+ const dropdownYears = getYearOptions({ navStart, navEnd, locale });
74
80
 
75
81
  return (
76
82
  <div {...omit(rest, ["displayIndex"])}>
@@ -5,7 +5,6 @@ import { BodyShort, ErrorMessage, Label } from "../../typography";
5
5
  import { ReadOnlyIconWithTitle } from "../ReadOnlyIcon";
6
6
  import ComboboxWrapper from "./ComboboxWrapper";
7
7
  import FilteredOptions from "./FilteredOptions/FilteredOptions";
8
- import { useFilteredOptionsContext } from "./FilteredOptions/filteredOptionsContext";
9
8
  import { useInputContext } from "./Input/Input.context";
10
9
  import { InputController } from "./Input/InputController";
11
10
  import { ComboboxProps } from "./types";
@@ -21,8 +20,6 @@ export const Combobox = forwardRef<
21
20
 
22
21
  const { cn } = useRenameCSS();
23
22
 
24
- const { toggleIsListOpen } = useFilteredOptionsContext();
25
-
26
23
  const {
27
24
  error,
28
25
  errorId,
@@ -41,7 +38,6 @@ export const Combobox = forwardRef<
41
38
  hasError={hasError}
42
39
  inputProps={inputProps}
43
40
  inputSize={size}
44
- toggleIsListOpen={toggleIsListOpen}
45
41
  >
46
42
  <Label
47
43
  htmlFor={inputProps.id}
@@ -11,7 +11,6 @@ type ComboboxWrapperProps = {
11
11
  };
12
12
  readOnly?: boolean;
13
13
  inputSize: string;
14
- toggleIsListOpen: (isListOpen: boolean) => void;
15
14
  };
16
15
 
17
16
  const ComboboxWrapper = ({
@@ -20,7 +19,6 @@ const ComboboxWrapper = ({
20
19
  hasError,
21
20
  inputProps,
22
21
  inputSize,
23
- toggleIsListOpen,
24
22
  }: ComboboxWrapperProps) => {
25
23
  const { cn } = useRenameCSS();
26
24
  const { toggleOpenButtonRef, clearInput, readOnly } = useInputContext();
@@ -39,7 +37,6 @@ const ComboboxWrapper = ({
39
37
 
40
38
  function onBlurWrapper(event: React.FocusEvent<HTMLDivElement>) {
41
39
  if (!wrapperRef.current?.contains(event.relatedTarget)) {
42
- toggleIsListOpen(false);
43
40
  setHasFocusWithin(false);
44
41
  clearInput(event);
45
42
  }
@@ -1,4 +1,5 @@
1
- import React, { useState } from "react";
1
+ import React, { useRef, useState } from "react";
2
+ import { DismissableLayer } from "../../../overlays/dismissablelayer/DismissableLayer";
2
3
  import { Floating } from "../../../overlays/floating/Floating";
3
4
  import { useRenameCSS, useThemeInternal } from "../../../theme/Theme";
4
5
  import { useClientLayoutEffect } from "../../../util";
@@ -17,6 +18,7 @@ const FilteredOptions = () => {
17
18
  const themeContext = useThemeInternal(false);
18
19
  const {
19
20
  inputProps: { id },
21
+ anchorRef,
20
22
  } = useInputContext();
21
23
 
22
24
  const {
@@ -27,9 +29,12 @@ const FilteredOptions = () => {
27
29
  setFilteredOptionsRef,
28
30
  isMouseLastUsedInputDevice,
29
31
  isValueNew,
32
+ toggleIsListOpen,
30
33
  } = useFilteredOptionsContext();
31
34
  const [localOpen, setLocalOpen] = useState(isListOpen);
32
35
 
36
+ const floatingRef = useRef<HTMLDivElement | null>(null);
37
+
33
38
  /**
34
39
  * This is a dirty hack to make the positioning-logic in Floating base the "flip" on the static 290px max-height,
35
40
  * instead of the dynamic one based on available space. Without this, the list won't flip to top when there's
@@ -53,53 +58,68 @@ const FilteredOptions = () => {
53
58
  const height = themeContext?.isDarkside ? "316px" : "290px";
54
59
 
55
60
  return (
56
- <Floating.Content
57
- className={cn("navds-combobox__list", {
58
- "navds-combobox__list--closed": !isListOpen,
59
- "navds-combobox__list--with-hover": isMouseLastUsedInputDevice,
60
- })}
61
- id={filteredOptionsUtil.getFilteredOptionsId(id)}
62
- tabIndex={-1}
63
- sideOffset={8}
64
- side="bottom"
65
- fallbackPlacements={["top"]}
66
- enabled={isListOpen}
67
- style={{
68
- maxHeight: localOpen
69
- ? `min(${height}, var(--ac-floating-available-height))`
70
- : `${height}`,
61
+ <DismissableLayer
62
+ asChild
63
+ safeZone={{
64
+ anchor: anchorRef,
65
+ dismissable: floatingRef.current,
66
+ }}
67
+ onDismiss={() => localOpen && toggleIsListOpen(false)}
68
+ onEscapeKeyDown={(event) => {
69
+ /* We handle this manually in Input */
70
+ event.preventDefault();
71
71
  }}
72
- autoUpdateWhileMounted={false}
72
+ enabled={localOpen}
73
73
  >
74
- {shouldRenderNonSelectables && (
75
- <div
76
- className={cn("navds-combobox__list_non-selectables")}
77
- role="status"
78
- >
79
- {isMultiSelect && maxSelected.limit && <MaxSelectedMessage />}
80
- {isLoading && <LoadingMessage />}
81
- {!isLoading && filteredOptions.length === 0 && !allowNewValues && (
82
- <NoSearchHitsMessage />
83
- )}
84
- </div>
85
- )}
74
+ <Floating.Content
75
+ ref={floatingRef}
76
+ className={cn("navds-combobox__list", {
77
+ "navds-combobox__list--closed": !isListOpen,
78
+ "navds-combobox__list--with-hover": isMouseLastUsedInputDevice,
79
+ })}
80
+ id={filteredOptionsUtil.getFilteredOptionsId(id)}
81
+ tabIndex={-1}
82
+ sideOffset={8}
83
+ side="bottom"
84
+ fallbackPlacements={["top"]}
85
+ enabled={isListOpen}
86
+ style={{
87
+ maxHeight: localOpen
88
+ ? `min(${height}, var(--ac-floating-available-height))`
89
+ : `${height}`,
90
+ }}
91
+ autoUpdateWhileMounted={false}
92
+ >
93
+ {shouldRenderNonSelectables && (
94
+ <div
95
+ className={cn("navds-combobox__list_non-selectables")}
96
+ role="status"
97
+ >
98
+ {isMultiSelect && maxSelected.limit && <MaxSelectedMessage />}
99
+ {isLoading && <LoadingMessage />}
100
+ {!isLoading && filteredOptions.length === 0 && !allowNewValues && (
101
+ <NoSearchHitsMessage />
102
+ )}
103
+ </div>
104
+ )}
86
105
 
87
- {shouldRenderFilteredOptionsList && (
88
- /* biome-ignore lint/a11y/useFocusableInteractive: Interaction is not handeled by listbox itself. */
89
- <ul
90
- ref={setFilteredOptionsRef}
91
- role="listbox"
92
- className={cn("navds-combobox__list-options")}
93
- >
94
- {isValueNew && !maxSelected.isLimitReached && allowNewValues && (
95
- <AddNewOption />
96
- )}
97
- {filteredOptions.map((option) => (
98
- <FilteredOptionsItem key={option.value} option={option} />
99
- ))}
100
- </ul>
101
- )}
102
- </Floating.Content>
106
+ {shouldRenderFilteredOptionsList && (
107
+ /* biome-ignore lint/a11y/useFocusableInteractive: Interaction is not handeled by listbox itself. */
108
+ <ul
109
+ ref={setFilteredOptionsRef}
110
+ role="listbox"
111
+ className={cn("navds-combobox__list-options")}
112
+ >
113
+ {isValueNew && !maxSelected.isLimitReached && allowNewValues && (
114
+ <AddNewOption />
115
+ )}
116
+ {filteredOptions.map((option) => (
117
+ <FilteredOptionsItem key={option.value} option={option} />
118
+ ))}
119
+ </ul>
120
+ )}
121
+ </Floating.Content>
122
+ </DismissableLayer>
103
123
  );
104
124
  };
105
125
 
@@ -67,6 +67,7 @@ const useVirtualFocus = (
67
67
 
68
68
  if (!activeElement) {
69
69
  setActiveAndScrollToElement(elementsAbleToReceiveFocus[0]);
70
+
70
71
  return;
71
72
  }
72
73
  const _currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
@@ -18,6 +18,8 @@ interface InputContextValue extends FormFieldType {
18
18
  toggleOpenButtonRef: React.MutableRefObject<HTMLDivElement | null>;
19
19
  hideCaret: boolean;
20
20
  setHideCaret: React.Dispatch<React.SetStateAction<boolean>>;
21
+ anchorRef: HTMLDivElement | null;
22
+ setAnchorRef: React.Dispatch<React.SetStateAction<HTMLDivElement | null>>;
21
23
  }
22
24
 
23
25
  const [InputContextProvider, useInputContext] =
@@ -75,6 +77,7 @@ const InputProvider = ({ children, value: props }: Props) => {
75
77
  const toggleOpenButtonRef = useRef<HTMLDivElement>(null);
76
78
  const [internalValue, setInternalValue] = useState<string>(defaultValue);
77
79
  const [hideCaret, setHideCaret] = useState(false);
80
+ const [anchorRef, setAnchorRef] = useState<HTMLDivElement | null>(null);
78
81
 
79
82
  const value = useMemo(
80
83
  () => String(externalValue ?? internalValue),
@@ -127,6 +130,8 @@ const InputProvider = ({ children, value: props }: Props) => {
127
130
  toggleOpenButtonRef,
128
131
  hideCaret,
129
132
  setHideCaret,
133
+ anchorRef,
134
+ setAnchorRef,
130
135
  };
131
136
 
132
137
  return (
@@ -49,6 +49,7 @@ export const InputController = forwardRef<
49
49
  inputRef,
50
50
  toggleOpenButtonRef,
51
51
  readOnly,
52
+ setAnchorRef,
52
53
  } = useInputContext();
53
54
 
54
55
  const { activeDecendantId, toggleIsListOpen } = useFilteredOptionsContext();
@@ -57,7 +58,7 @@ export const InputController = forwardRef<
57
58
  const mergedInputRef = useMergeRefs(inputRef, ref);
58
59
 
59
60
  return (
60
- <Floating.Anchor asChild>
61
+ <Floating.Anchor asChild ref={setAnchorRef}>
61
62
  <div
62
63
  className={cn("navds-combobox__wrapper-inner navds-text-field__input", {
63
64
  "navds-combobox__wrapper-inner--virtually-unfocused":
@@ -60,13 +60,13 @@ describe("format-file-size", () => {
60
60
 
61
61
  function createLargeMockFile(sizeInBytes: number): File {
62
62
  const chunkSize = 1024 * 1024; // 1MB chunk size
63
- const chunks: Uint8Array[] = [];
63
+ const chunks: ArrayBuffer[] = [];
64
64
 
65
65
  for (let i = 0; i < sizeInBytes; i += chunkSize) {
66
66
  const size = Math.min(chunkSize, sizeInBytes - i);
67
67
  const chunk = new Uint8Array(size);
68
68
  chunk.fill("a".charCodeAt(0));
69
- chunks.push(chunk);
69
+ chunks.push(chunk.buffer);
70
70
  }
71
71
 
72
72
  const blob = new Blob(chunks, { type: "text/plain" });
@@ -70,9 +70,10 @@ export const HelpText = forwardRef<HTMLButtonElement, HelpTextProps>(
70
70
  className={cn(className, "navds-help-text__button")}
71
71
  type="button"
72
72
  aria-expanded={open}
73
+ aria-label={titleWithFallback}
73
74
  >
74
- <HelpTextIcon title={titleWithFallback} />
75
- <HelpTextIcon filled title={titleWithFallback} />
75
+ <HelpTextIcon />
76
+ <HelpTextIcon filled />
76
77
  </button>
77
78
  <Popover
78
79
  onClose={() => setOpen(false)}
@@ -1,18 +1,9 @@
1
1
  import React from "react";
2
2
  import { useRenameCSS } from "../theme/Theme";
3
- import { useId } from "../util/hooks";
4
3
 
5
- export const HelpTextIcon = ({
6
- title,
7
- filled = false,
8
- }: {
9
- title: string;
10
- filled?: boolean;
11
- }) => {
4
+ export const HelpTextIcon = ({ filled = false }: { filled?: boolean }) => {
12
5
  const { cn } = useRenameCSS();
13
6
 
14
- let titleId: string | undefined = useId();
15
- titleId = title ? `title-${titleId}` : undefined;
16
7
  return (
17
8
  <svg
18
9
  width="1em"
@@ -22,12 +13,11 @@ export const HelpTextIcon = ({
22
13
  xmlns="http://www.w3.org/2000/svg"
23
14
  focusable={false}
24
15
  role="img"
25
- aria-labelledby={titleId}
26
16
  className={cn("navds-help-text__icon", {
27
17
  "navds-help-text__icon--filled": filled,
28
18
  })}
19
+ aria-hidden
29
20
  >
30
- {title ? <title id={titleId}>{title}</title> : null}
31
21
  <circle
32
22
  cx="12"
33
23
  cy="12"
@@ -7,20 +7,27 @@ export const widths = ["text", "md", "lg", "xl", "2xl"] as const;
7
7
  export interface PageBlockProps extends React.HTMLAttributes<HTMLDivElement> {
8
8
  /**
9
9
  * Predefined max-width
10
- * @example
10
+ *
11
11
  * text: 576px + dynamic gutters
12
+ *
12
13
  * md: 768px
14
+ *
13
15
  * lg: 1024px
16
+ *
14
17
  * xl: 1280px
18
+ *
15
19
  * 2xl: 1440px
20
+ *
16
21
  * @default max-width: 100%;
17
22
  */
18
23
  width?: (typeof widths)[number];
19
24
  /**
20
25
  * Adds a standardised responsive padding-inline
21
- * @example
26
+ *
22
27
  * 3rem on > md
28
+ *
23
29
  * 1rem on < md
30
+ *
24
31
  * @default false
25
32
  */
26
33
  gutters?: boolean;
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { ownerDocument } from "../util/owner";
2
3
  import type { ModalProps } from "./types";
3
4
 
4
5
  export interface MouseCoordinates {
@@ -45,16 +46,18 @@ export function useBodyScrollLock(
45
46
  return;
46
47
  }
47
48
 
49
+ const ownerDoc = ownerDocument(modalRef.current);
50
+
48
51
  // In case `open` is true initially
49
52
  if (modalRef.current.open) {
50
- document.body.classList.add(BODY_CLASS, BODY_CLASS_LEGACY);
53
+ ownerDoc.body.classList.add(BODY_CLASS, BODY_CLASS_LEGACY);
51
54
  }
52
55
 
53
56
  const observer = new MutationObserver(() => {
54
57
  if (modalRef.current?.open) {
55
- document.body.classList.add(BODY_CLASS, BODY_CLASS_LEGACY);
58
+ ownerDoc.body.classList.add(BODY_CLASS, BODY_CLASS_LEGACY);
56
59
  } else {
57
- document.body.classList.remove(BODY_CLASS, BODY_CLASS_LEGACY);
60
+ ownerDoc.body.classList.remove(BODY_CLASS, BODY_CLASS_LEGACY);
58
61
  }
59
62
  });
60
63
 
@@ -66,7 +69,7 @@ export function useBodyScrollLock(
66
69
  return () => {
67
70
  observer.disconnect();
68
71
  // In case modal is unmounted before it's closed
69
- document.body.classList.remove(BODY_CLASS, BODY_CLASS_LEGACY);
72
+ ownerDoc.body.classList.remove(BODY_CLASS, BODY_CLASS_LEGACY);
70
73
  };
71
74
  }, [modalRef, portalNode, isNested]);
72
75
  }