@navikt/ds-react 7.32.1 → 7.32.3

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 (221) hide show
  1. package/cjs/copybutton/CopyButton.js +4 -9
  2. package/cjs/copybutton/CopyButton.js.map +1 -1
  3. package/cjs/form/combobox/Combobox.js +1 -3
  4. package/cjs/form/combobox/Combobox.js.map +1 -1
  5. package/cjs/form/combobox/ComboboxWrapper.d.ts +1 -2
  6. package/cjs/form/combobox/ComboboxWrapper.js +1 -2
  7. package/cjs/form/combobox/ComboboxWrapper.js.map +1 -1
  8. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +28 -19
  9. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  10. package/cjs/form/combobox/FilteredOptions/FilteredOptionsItem.js +4 -0
  11. package/cjs/form/combobox/FilteredOptions/FilteredOptionsItem.js.map +1 -1
  12. package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
  13. package/cjs/form/combobox/Input/Input.context.d.ts +2 -0
  14. package/cjs/form/combobox/Input/Input.context.js +4 -1
  15. package/cjs/form/combobox/Input/Input.context.js.map +1 -1
  16. package/cjs/form/combobox/Input/InputController.js +2 -2
  17. package/cjs/form/combobox/Input/InputController.js.map +1 -1
  18. package/cjs/form/switch/Switch.js +3 -3
  19. package/cjs/form/switch/Switch.js.map +1 -1
  20. package/cjs/help-text/HelpText.js +3 -3
  21. package/cjs/help-text/HelpText.js.map +1 -1
  22. package/cjs/help-text/HelpTextIcon.d.ts +1 -2
  23. package/cjs/help-text/HelpTextIcon.js +3 -7
  24. package/cjs/help-text/HelpTextIcon.js.map +1 -1
  25. package/cjs/layout/page/parts/PageBlock.d.ts +9 -6
  26. package/cjs/layout/page/parts/PageBlock.js.map +1 -1
  27. package/cjs/modal/ModalUtils.js +6 -4
  28. package/cjs/modal/ModalUtils.js.map +1 -1
  29. package/cjs/overlays/dismissablelayer/DismissableLayer.js +9 -19
  30. package/cjs/overlays/dismissablelayer/DismissableLayer.js.map +1 -1
  31. package/cjs/overlays/dismissablelayer/util/usePointerDownOutside.js +5 -4
  32. package/cjs/overlays/dismissablelayer/util/usePointerDownOutside.js.map +1 -1
  33. package/cjs/overlays/floating-menu/Menu.d.ts +4 -4
  34. package/cjs/overlays/floating-menu/Menu.js +7 -4
  35. package/cjs/overlays/floating-menu/Menu.js.map +1 -1
  36. package/cjs/overlays/floating-menu/parts/RovingFocus.js +3 -3
  37. package/cjs/overlays/floating-menu/parts/RovingFocus.js.map +1 -1
  38. package/cjs/overlays/overlay/hooks/useAnimationsFinished.js +1 -1
  39. package/cjs/overlays/overlay/hooks/useAnimationsFinished.js.map +1 -1
  40. package/cjs/overlays/overlay/hooks/useOpenChangeAnimationComplete.js +2 -2
  41. package/cjs/overlays/overlay/hooks/useOpenChangeAnimationComplete.js.map +1 -1
  42. package/cjs/popover/Popover.js +1 -1
  43. package/cjs/popover/Popover.js.map +1 -1
  44. package/cjs/progress-bar/ProgressBar.js +9 -6
  45. package/cjs/progress-bar/ProgressBar.js.map +1 -1
  46. package/cjs/table/AnimateHeight.js +12 -13
  47. package/cjs/table/AnimateHeight.js.map +1 -1
  48. package/cjs/tabs/parts/tablist/useScrollButtons.d.ts +1 -1
  49. package/cjs/tabs/parts/tablist/useScrollButtons.js +4 -4
  50. package/cjs/tabs/parts/tablist/useScrollButtons.js.map +1 -1
  51. package/cjs/util/TextareaAutoSize.js +3 -10
  52. package/cjs/util/TextareaAutoSize.js.map +1 -1
  53. package/cjs/util/create-context.d.ts +0 -1
  54. package/cjs/util/create-context.js.map +1 -1
  55. package/cjs/util/debounce.d.ts +1 -1
  56. package/cjs/util/debounce.js +5 -8
  57. package/cjs/util/debounce.js.map +1 -1
  58. package/cjs/util/detectBrowser.d.ts +2 -0
  59. package/cjs/util/detectBrowser.js +7 -0
  60. package/cjs/util/detectBrowser.js.map +1 -0
  61. package/cjs/util/focus-boundary/FocusBoundary.d.ts +44 -0
  62. package/cjs/util/focus-boundary/FocusBoundary.js +365 -0
  63. package/cjs/util/focus-boundary/FocusBoundary.js.map +1 -0
  64. package/cjs/util/focus-guards/FocusGuards.d.ts +8 -0
  65. package/cjs/util/focus-guards/FocusGuards.js +36 -0
  66. package/cjs/util/focus-guards/FocusGuards.js.map +1 -0
  67. package/cjs/util/hooks/descendants/useDescendant.js +3 -0
  68. package/cjs/util/hooks/descendants/useDescendant.js.map +1 -1
  69. package/cjs/util/hooks/useEventCallback.js.map +1 -0
  70. package/cjs/{overlays/overlay → util}/hooks/useLatestRef.js +3 -2
  71. package/cjs/util/hooks/useLatestRef.js.map +1 -0
  72. package/cjs/util/hooks/useRefWithInit.js.map +1 -0
  73. package/cjs/util/hooks/useTimeout.d.ts +16 -0
  74. package/cjs/util/hooks/useTimeout.js +49 -0
  75. package/cjs/util/hooks/useTimeout.js.map +1 -0
  76. package/cjs/util/link-anchor/LinkAnchor.js +6 -7
  77. package/cjs/util/link-anchor/LinkAnchor.js.map +1 -1
  78. package/cjs/util/owner.d.ts +29 -0
  79. package/cjs/util/owner.js +38 -0
  80. package/cjs/util/owner.js.map +1 -0
  81. package/esm/copybutton/CopyButton.js +5 -10
  82. package/esm/copybutton/CopyButton.js.map +1 -1
  83. package/esm/form/combobox/Combobox.js +1 -3
  84. package/esm/form/combobox/Combobox.js.map +1 -1
  85. package/esm/form/combobox/ComboboxWrapper.d.ts +1 -2
  86. package/esm/form/combobox/ComboboxWrapper.js +1 -2
  87. package/esm/form/combobox/ComboboxWrapper.js.map +1 -1
  88. package/esm/form/combobox/FilteredOptions/FilteredOptions.js +29 -20
  89. package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  90. package/esm/form/combobox/FilteredOptions/FilteredOptionsItem.js +4 -0
  91. package/esm/form/combobox/FilteredOptions/FilteredOptionsItem.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/form/switch/Switch.js +3 -3
  99. package/esm/form/switch/Switch.js.map +1 -1
  100. package/esm/help-text/HelpText.js +3 -3
  101. package/esm/help-text/HelpText.js.map +1 -1
  102. package/esm/help-text/HelpTextIcon.d.ts +1 -2
  103. package/esm/help-text/HelpTextIcon.js +3 -7
  104. package/esm/help-text/HelpTextIcon.js.map +1 -1
  105. package/esm/layout/page/parts/PageBlock.d.ts +9 -6
  106. package/esm/layout/page/parts/PageBlock.js.map +1 -1
  107. package/esm/modal/ModalUtils.js +6 -4
  108. package/esm/modal/ModalUtils.js.map +1 -1
  109. package/esm/overlays/dismissablelayer/DismissableLayer.js +9 -19
  110. package/esm/overlays/dismissablelayer/DismissableLayer.js.map +1 -1
  111. package/esm/overlays/dismissablelayer/util/usePointerDownOutside.js +5 -4
  112. package/esm/overlays/dismissablelayer/util/usePointerDownOutside.js.map +1 -1
  113. package/esm/overlays/floating-menu/Menu.d.ts +4 -4
  114. package/esm/overlays/floating-menu/Menu.js +7 -4
  115. package/esm/overlays/floating-menu/Menu.js.map +1 -1
  116. package/esm/overlays/floating-menu/parts/RovingFocus.js +3 -3
  117. package/esm/overlays/floating-menu/parts/RovingFocus.js.map +1 -1
  118. package/esm/overlays/overlay/hooks/useAnimationsFinished.js +1 -1
  119. package/esm/overlays/overlay/hooks/useAnimationsFinished.js.map +1 -1
  120. package/esm/overlays/overlay/hooks/useOpenChangeAnimationComplete.js +2 -2
  121. package/esm/overlays/overlay/hooks/useOpenChangeAnimationComplete.js.map +1 -1
  122. package/esm/popover/Popover.js +1 -1
  123. package/esm/popover/Popover.js.map +1 -1
  124. package/esm/progress-bar/ProgressBar.js +10 -7
  125. package/esm/progress-bar/ProgressBar.js.map +1 -1
  126. package/esm/table/AnimateHeight.js +12 -13
  127. package/esm/table/AnimateHeight.js.map +1 -1
  128. package/esm/tabs/parts/tablist/useScrollButtons.d.ts +1 -1
  129. package/esm/tabs/parts/tablist/useScrollButtons.js +4 -4
  130. package/esm/tabs/parts/tablist/useScrollButtons.js.map +1 -1
  131. package/esm/util/TextareaAutoSize.js +1 -8
  132. package/esm/util/TextareaAutoSize.js.map +1 -1
  133. package/esm/util/create-context.d.ts +0 -1
  134. package/esm/util/create-context.js.map +1 -1
  135. package/esm/util/debounce.d.ts +1 -1
  136. package/esm/util/debounce.js +5 -8
  137. package/esm/util/debounce.js.map +1 -1
  138. package/esm/util/detectBrowser.d.ts +2 -0
  139. package/esm/util/detectBrowser.js +4 -0
  140. package/esm/util/detectBrowser.js.map +1 -0
  141. package/esm/util/focus-boundary/FocusBoundary.d.ts +44 -0
  142. package/esm/util/focus-boundary/FocusBoundary.js +329 -0
  143. package/esm/util/focus-boundary/FocusBoundary.js.map +1 -0
  144. package/esm/util/focus-guards/FocusGuards.d.ts +8 -0
  145. package/esm/util/focus-guards/FocusGuards.js +31 -0
  146. package/esm/util/focus-guards/FocusGuards.js.map +1 -0
  147. package/esm/util/hooks/descendants/useDescendant.js +3 -0
  148. package/esm/util/hooks/descendants/useDescendant.js.map +1 -1
  149. package/esm/util/hooks/useEventCallback.js.map +1 -0
  150. package/esm/{overlays/overlay → util}/hooks/useLatestRef.js +2 -1
  151. package/esm/util/hooks/useLatestRef.js.map +1 -0
  152. package/esm/util/hooks/useRefWithInit.js.map +1 -0
  153. package/esm/util/hooks/useTimeout.d.ts +16 -0
  154. package/esm/util/hooks/useTimeout.js +45 -0
  155. package/esm/util/hooks/useTimeout.js.map +1 -0
  156. package/esm/util/link-anchor/LinkAnchor.js +6 -7
  157. package/esm/util/link-anchor/LinkAnchor.js.map +1 -1
  158. package/esm/util/owner.d.ts +29 -0
  159. package/esm/util/owner.js +35 -0
  160. package/esm/util/owner.js.map +1 -0
  161. package/package.json +8 -8
  162. package/src/copybutton/CopyButton.tsx +5 -17
  163. package/src/form/combobox/Combobox.tsx +0 -4
  164. package/src/form/combobox/ComboboxWrapper.tsx +0 -3
  165. package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +65 -45
  166. package/src/form/combobox/FilteredOptions/FilteredOptionsItem.tsx +4 -0
  167. package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +1 -0
  168. package/src/form/combobox/Input/Input.context.tsx +5 -0
  169. package/src/form/combobox/Input/InputController.tsx +2 -1
  170. package/src/form/file-upload/parts/item/utils/format-file-size.test.ts +2 -2
  171. package/src/form/switch/Switch.tsx +4 -4
  172. package/src/help-text/HelpText.tsx +3 -2
  173. package/src/help-text/HelpTextIcon.tsx +2 -12
  174. package/src/layout/page/parts/PageBlock.tsx +9 -6
  175. package/src/modal/ModalUtils.ts +7 -4
  176. package/src/overlays/dismissablelayer/DismissableLayer.tsx +9 -18
  177. package/src/overlays/dismissablelayer/util/usePointerDownOutside.ts +5 -4
  178. package/src/overlays/floating-menu/Menu.tsx +13 -9
  179. package/src/overlays/floating-menu/parts/RovingFocus.tsx +3 -3
  180. package/src/overlays/overlay/hooks/useAnimationsFinished.ts +1 -1
  181. package/src/overlays/overlay/hooks/useOpenChangeAnimationComplete.ts +2 -2
  182. package/src/popover/Popover.tsx +1 -1
  183. package/src/progress-bar/ProgressBar.tsx +12 -10
  184. package/src/table/AnimateHeight.tsx +12 -15
  185. package/src/tabs/parts/tablist/useScrollButtons.ts +4 -3
  186. package/src/util/TextareaAutoSize.tsx +1 -9
  187. package/src/util/create-context.tsx +0 -1
  188. package/src/util/debounce.ts +7 -8
  189. package/src/util/detectBrowser.ts +5 -0
  190. package/src/util/focus-boundary/FocusBoundary.tsx +453 -0
  191. package/src/util/focus-guards/FocusGuards.tsx +56 -0
  192. package/src/util/hooks/descendants/useDescendant.tsx +3 -0
  193. package/src/{overlays/overlay → util}/hooks/useLatestRef.ts +2 -1
  194. package/src/util/hooks/useTimeout.ts +54 -0
  195. package/src/util/link-anchor/LinkAnchor.tsx +7 -6
  196. package/src/util/owner.ts +35 -0
  197. package/cjs/overlays/floating-menu/parts/FocusScope.d.ts +0 -22
  198. package/cjs/overlays/floating-menu/parts/FocusScope.js +0 -98
  199. package/cjs/overlays/floating-menu/parts/FocusScope.js.map +0 -1
  200. package/cjs/overlays/overlay/hooks/useEventCallback.js.map +0 -1
  201. package/cjs/overlays/overlay/hooks/useLatestRef.js.map +0 -1
  202. package/cjs/overlays/overlay/hooks/useRefWithInit.js.map +0 -1
  203. package/esm/overlays/floating-menu/parts/FocusScope.d.ts +0 -22
  204. package/esm/overlays/floating-menu/parts/FocusScope.js +0 -62
  205. package/esm/overlays/floating-menu/parts/FocusScope.js.map +0 -1
  206. package/esm/overlays/overlay/hooks/useEventCallback.js.map +0 -1
  207. package/esm/overlays/overlay/hooks/useLatestRef.js.map +0 -1
  208. package/esm/overlays/overlay/hooks/useRefWithInit.js.map +0 -1
  209. package/src/overlays/floating-menu/parts/FocusScope.tsx +0 -83
  210. /package/cjs/{overlays/overlay → util}/hooks/useEventCallback.d.ts +0 -0
  211. /package/cjs/{overlays/overlay → util}/hooks/useEventCallback.js +0 -0
  212. /package/cjs/{overlays/overlay → util}/hooks/useLatestRef.d.ts +0 -0
  213. /package/cjs/{overlays/overlay → util}/hooks/useRefWithInit.d.ts +0 -0
  214. /package/cjs/{overlays/overlay → util}/hooks/useRefWithInit.js +0 -0
  215. /package/esm/{overlays/overlay → util}/hooks/useEventCallback.d.ts +0 -0
  216. /package/esm/{overlays/overlay → util}/hooks/useEventCallback.js +0 -0
  217. /package/esm/{overlays/overlay → util}/hooks/useLatestRef.d.ts +0 -0
  218. /package/esm/{overlays/overlay → util}/hooks/useRefWithInit.d.ts +0 -0
  219. /package/esm/{overlays/overlay → util}/hooks/useRefWithInit.js +0 -0
  220. /package/src/{overlays/overlay → util}/hooks/useEventCallback.ts +0 -0
  221. /package/src/{overlays/overlay → util}/hooks/useRefWithInit.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navikt/ds-react",
3
- "version": "7.32.1",
3
+ "version": "7.32.3",
4
4
  "description": "React components from the Norwegian Labour and Welfare Administration.",
5
5
  "author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
6
6
  "license": "MIT",
@@ -643,21 +643,21 @@
643
643
  "clean": "rimraf cjs esm",
644
644
  "build": "yarn i18n-jsdoc && concurrently \"tsc -p tsconfig.build.json\" \"tsc -p tsconfig.esm.json && tsc-alias -p tsconfig.esm.json && yarn write-packagejson\" && yarn i18n-jsdoc --cleanup && yarn copy-types",
645
645
  "watch": "tsc --watch -p tsconfig.esm.json",
646
- "test": "TZ=UTC vitest run -c tests/vitest.config.ts",
646
+ "test": "TZ=UTC vitest run -c tests/vitest.config.ts --silent",
647
647
  "test:watch": "TZ=UTC vitest watch -c tests/vitest.config.ts",
648
648
  "copy-types": "copyfiles -f ./src/types/theme.d.ts ./esm/types"
649
649
  },
650
650
  "dependencies": {
651
651
  "@floating-ui/react": "0.27.8",
652
- "@floating-ui/react-dom": "^2.0.9",
653
- "@navikt/aksel-icons": "^7.32.1",
654
- "@navikt/ds-tokens": "^7.32.1",
652
+ "@floating-ui/react-dom": "^2.1.6",
653
+ "@navikt/aksel-icons": "^7.32.3",
654
+ "@navikt/ds-tokens": "^7.32.3",
655
655
  "clsx": "^2.1.0",
656
656
  "date-fns": "^4.0.0",
657
657
  "react-day-picker": "9.7.0"
658
658
  },
659
659
  "devDependencies": {
660
- "@testing-library/dom": "10.4.0",
660
+ "@testing-library/dom": "10.4.1",
661
661
  "@testing-library/jest-dom": "^6.6.3",
662
662
  "@testing-library/react": "^16.3.0",
663
663
  "@testing-library/user-event": "^14.5.2",
@@ -674,8 +674,8 @@
674
674
  "swr": "^2.3.6",
675
675
  "tsc-alias": "1.8.16",
676
676
  "tsx": "^4.20.6",
677
- "typescript": "5.8.3",
678
- "vitest": "^2.1.9"
677
+ "typescript": "5.9.3",
678
+ "vitest": "^3.2.4"
679
679
  },
680
680
  "peerDependencies": {
681
681
  "@types/react": ">=17.0.30",
@@ -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");
@@ -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
 
@@ -13,6 +13,10 @@ const useTextHighlight = (text: string, searchTerm: string) => {
13
13
  const indexOfHighlightedText = text
14
14
  .toLowerCase()
15
15
  .indexOf(searchTerm.toLowerCase());
16
+ if (indexOfHighlightedText === -1) {
17
+ // This can happen if the consumer has implemented their own filtering logic
18
+ return [text, "", ""];
19
+ }
16
20
  const start = text.substring(0, indexOfHighlightedText);
17
21
  const highlight = text.substring(
18
22
  indexOfHighlightedText,
@@ -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" });
@@ -124,14 +124,14 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
124
124
  htmlFor={inputProps.id}
125
125
  className={cn("navds-switch__label-wrapper")}
126
126
  >
127
- <div
127
+ <span
128
128
  className={cn("navds-switch__content", {
129
129
  "navds-sr-only": hideLabel,
130
130
  "navds-switch--with-description": description && !hideLabel,
131
131
  })}
132
132
  >
133
133
  <BodyShort
134
- as="div"
134
+ as="span"
135
135
  size={size}
136
136
  className={cn("navds-switch__label")}
137
137
  >
@@ -141,7 +141,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
141
141
  {description && (
142
142
  <BodyShort
143
143
  size={size}
144
- as="div"
144
+ as="span"
145
145
  className={cn(
146
146
  "navds-form-field__subdescription navds-switch__description",
147
147
  )}
@@ -149,7 +149,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
149
149
  {description}
150
150
  </BodyShort>
151
151
  )}
152
- </div>
152
+ </span>
153
153
  </label>
154
154
  </div>
155
155
  );
@@ -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,24 +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
11
- * ```
10
+ *
12
11
  * text: 576px + dynamic gutters
12
+ *
13
13
  * md: 768px
14
+ *
14
15
  * lg: 1024px
16
+ *
15
17
  * xl: 1280px
18
+ *
16
19
  * 2xl: 1440px
17
- * ```
20
+ *
18
21
  * @default max-width: 100%;
19
22
  */
20
23
  width?: (typeof widths)[number];
21
24
  /**
22
25
  * Adds a standardised responsive padding-inline
23
- * @example
24
- * ```
26
+ *
25
27
  * 3rem on > md
28
+ *
26
29
  * 1rem on < md
27
- * ```
30
+ *
28
31
  * @default false
29
32
  */
30
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
  }
@@ -8,6 +8,7 @@ import React, {
8
8
  import { Slot } from "../../slot/Slot";
9
9
  import { useMergeRefs } from "../../util/hooks";
10
10
  import { createDescendantContext } from "../../util/hooks/descendants/useDescendant";
11
+ import { ownerDocument } from "../../util/owner";
11
12
  import { AsChild } from "../../util/types/AsChild";
12
13
  import {
13
14
  CustomFocusEvent,
@@ -158,11 +159,7 @@ const DismissableLayerNode = forwardRef<HTMLDivElement, DismissableLayerProps>(
158
159
 
159
160
  const mergedRefs = useMergeRefs(setNode, register, ref);
160
161
 
161
- /**
162
- * In some cases the `node.ownerDocument` can differ from global document.
163
- * This can happend when portaling elements or using web-components
164
- */
165
- const ownerDocument = node?.ownerDocument ?? globalThis?.document;
162
+ const ownerDoc = ownerDocument(node);
166
163
 
167
164
  const hasInteractedOutsideRef = useRef(false);
168
165
  const hasPointerDownOutsideRef = useRef(false);
@@ -278,7 +275,7 @@ const DismissableLayerNode = forwardRef<HTMLDivElement, DismissableLayerProps>(
278
275
  if (!event.defaultPrevented && onDismiss) {
279
276
  onDismiss();
280
277
  }
281
- }, ownerDocument);
278
+ }, ownerDoc);
282
279
 
283
280
  const focusOutside = useFocusOutside((event) => {
284
281
  if (!enabled) {
@@ -302,7 +299,7 @@ const DismissableLayerNode = forwardRef<HTMLDivElement, DismissableLayerProps>(
302
299
  if (!event.defaultPrevented && onDismiss) {
303
300
  onDismiss();
304
301
  }
305
- }, ownerDocument);
302
+ }, ownerDoc);
306
303
 
307
304
  useEscapeKeydown((event) => {
308
305
  if (!enabled) {
@@ -331,7 +328,7 @@ const DismissableLayerNode = forwardRef<HTMLDivElement, DismissableLayerProps>(
331
328
  event.preventDefault();
332
329
  onDismiss();
333
330
  }
334
- }, ownerDocument);
331
+ }, ownerDoc);
335
332
 
336
333
  /**
337
334
  * If `disableOutsidePointerEvents` is true,
@@ -343,23 +340,17 @@ const DismissableLayerNode = forwardRef<HTMLDivElement, DismissableLayerProps>(
343
340
  if (!node || !enabled || !disableOutsidePointerEvents) return;
344
341
 
345
342
  if (bodyLockCount === 0) {
346
- originalBodyPointerEvents = ownerDocument.body.style.pointerEvents;
347
- ownerDocument.body.style.pointerEvents = "none";
343
+ originalBodyPointerEvents = ownerDoc.body.style.pointerEvents;
344
+ ownerDoc.body.style.pointerEvents = "none";
348
345
  }
349
346
  bodyLockCount++;
350
347
  return () => {
351
348
  if (bodyLockCount === 1) {
352
- ownerDocument.body.style.pointerEvents = originalBodyPointerEvents;
349
+ ownerDoc.body.style.pointerEvents = originalBodyPointerEvents;
353
350
  }
354
351
  bodyLockCount--;
355
352
  };
356
- }, [
357
- node,
358
- ownerDocument,
359
- disableOutsidePointerEvents,
360
- descendants,
361
- enabled,
362
- ]);
353
+ }, [node, ownerDoc, disableOutsidePointerEvents, descendants, enabled]);
363
354
 
364
355
  /**
365
356
  * To make sure pointerEvents are enabled for all parents and siblings when the layer is removed from the DOM
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import { useCallbackRef } from "../../../util/hooks";
3
+ import { useTimeout } from "../../../util/hooks/useTimeout";
3
4
  import {
4
5
  CUSTOM_EVENTS,
5
6
  CustomPointerDownEvent,
@@ -19,6 +20,7 @@ export function usePointerDownOutside(
19
20
  const handlePointerDownOutside = useCallbackRef(callback) as EventListener;
20
21
  const isPointerInsideReactTreeRef = useRef(false);
21
22
  const handleClickRef = useRef<typeof handlePointerDownOutside>(() => {});
23
+ const timeout = useTimeout();
22
24
 
23
25
  useEffect(() => {
24
26
  const handlePointerDown = (event: PointerEvent) => {
@@ -77,16 +79,15 @@ export function usePointerDownOutside(
77
79
  * })
78
80
  * });
79
81
  */
80
- const timerId = window.setTimeout(() => {
82
+ timeout.start(0, () => {
81
83
  ownerDocument.addEventListener("pointerdown", handlePointerDown);
82
- }, 0);
84
+ });
83
85
 
84
86
  return () => {
85
- window.clearTimeout(timerId);
86
87
  ownerDocument.removeEventListener("pointerdown", handlePointerDown);
87
88
  ownerDocument.removeEventListener("click", handleClickRef.current);
88
89
  };
89
- }, [ownerDocument, handlePointerDownOutside]);
90
+ }, [ownerDocument, handlePointerDownOutside, timeout]);
90
91
 
91
92
  return {
92
93
  // ensures we check React component tree (not just DOM tree)