@navikt/ds-react 7.37.0 → 7.38.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 (106) hide show
  1. package/cjs/date/Date.Dialog.js +5 -1
  2. package/cjs/date/Date.Dialog.js.map +1 -1
  3. package/cjs/dialog/index.d.ts +1 -1
  4. package/cjs/dialog/index.js +4 -1
  5. package/cjs/dialog/index.js.map +1 -1
  6. package/cjs/dialog/popup/DialogPopup.js +6 -1
  7. package/cjs/dialog/popup/DialogPopup.js.map +1 -1
  8. package/cjs/dialog/root/DialogRoot.d.ts +5 -5
  9. package/cjs/dialog/root/DialogRoot.js +12 -11
  10. package/cjs/dialog/root/DialogRoot.js.map +1 -1
  11. package/cjs/form/combobox/Combobox.d.ts +1 -1
  12. package/cjs/form/combobox/Input/InputController.d.ts +1 -1
  13. package/cjs/form/file-upload/useFileUpload.d.ts +1 -1
  14. package/cjs/layout/base/PrimitiveAsChildProps.d.ts +1 -4
  15. package/cjs/modal/Modal.js +9 -2
  16. package/cjs/modal/Modal.js.map +1 -1
  17. package/cjs/overlays/action-menu/ActionMenu.js +3 -1
  18. package/cjs/overlays/action-menu/ActionMenu.js.map +1 -1
  19. package/cjs/overlays/dismissablelayer/DismissableLayer.d.ts +1 -0
  20. package/cjs/overlays/dismissablelayer/DismissableLayer.js +33 -14
  21. package/cjs/overlays/dismissablelayer/DismissableLayer.js.map +1 -1
  22. package/cjs/overlays/dismissablelayer/util/useEscapeKeydown.js +7 -2
  23. package/cjs/overlays/dismissablelayer/util/useEscapeKeydown.js.map +1 -1
  24. package/cjs/provider/Provider.d.ts +1 -5
  25. package/cjs/provider/Provider.js +0 -2
  26. package/cjs/provider/Provider.js.map +1 -1
  27. package/cjs/slot/Slot.js +12 -5
  28. package/cjs/slot/Slot.js.map +1 -1
  29. package/cjs/tabs/Tabs.context.d.ts +1 -1
  30. package/cjs/tabs/parts/tab/useTab.d.ts +1 -1
  31. package/cjs/tabs/parts/tab/useTab.js +2 -1
  32. package/cjs/tabs/parts/tab/useTab.js.map +1 -1
  33. package/cjs/toggle-group/ToggleGroup.context.d.ts +1 -1
  34. package/cjs/toggle-group/parts/useToggleItem.d.ts +1 -1
  35. package/cjs/toggle-group/parts/useToggleItem.js +2 -1
  36. package/cjs/toggle-group/parts/useToggleItem.js.map +1 -1
  37. package/cjs/util/hooks/descendants/useDescendant.d.ts +1 -1
  38. package/cjs/util/hooks/descendants/useDescendant.js +2 -1
  39. package/cjs/util/hooks/descendants/useDescendant.js.map +1 -1
  40. package/cjs/util/hooks/useMergeRefs.d.ts +15 -9
  41. package/cjs/util/hooks/useMergeRefs.js +94 -29
  42. package/cjs/util/hooks/useMergeRefs.js.map +1 -1
  43. package/cjs/util/types/AsChildProps.d.ts +0 -4
  44. package/cjs/util/virtualfocus/Context.d.ts +1 -1
  45. package/esm/date/Date.Dialog.js +5 -1
  46. package/esm/date/Date.Dialog.js.map +1 -1
  47. package/esm/dialog/index.d.ts +1 -1
  48. package/esm/dialog/index.js +1 -1
  49. package/esm/dialog/index.js.map +1 -1
  50. package/esm/dialog/popup/DialogPopup.js +6 -1
  51. package/esm/dialog/popup/DialogPopup.js.map +1 -1
  52. package/esm/dialog/root/DialogRoot.d.ts +5 -5
  53. package/esm/dialog/root/DialogRoot.js +5 -5
  54. package/esm/dialog/root/DialogRoot.js.map +1 -1
  55. package/esm/form/combobox/Combobox.d.ts +1 -1
  56. package/esm/form/combobox/Input/InputController.d.ts +1 -1
  57. package/esm/form/file-upload/useFileUpload.d.ts +1 -1
  58. package/esm/layout/base/PrimitiveAsChildProps.d.ts +1 -4
  59. package/esm/modal/Modal.js +9 -2
  60. package/esm/modal/Modal.js.map +1 -1
  61. package/esm/overlays/action-menu/ActionMenu.js +3 -1
  62. package/esm/overlays/action-menu/ActionMenu.js.map +1 -1
  63. package/esm/overlays/dismissablelayer/DismissableLayer.d.ts +1 -0
  64. package/esm/overlays/dismissablelayer/DismissableLayer.js +34 -15
  65. package/esm/overlays/dismissablelayer/DismissableLayer.js.map +1 -1
  66. package/esm/overlays/dismissablelayer/util/useEscapeKeydown.js +7 -2
  67. package/esm/overlays/dismissablelayer/util/useEscapeKeydown.js.map +1 -1
  68. package/esm/provider/Provider.d.ts +1 -5
  69. package/esm/provider/Provider.js +0 -2
  70. package/esm/provider/Provider.js.map +1 -1
  71. package/esm/slot/Slot.js +12 -5
  72. package/esm/slot/Slot.js.map +1 -1
  73. package/esm/tabs/Tabs.context.d.ts +1 -1
  74. package/esm/tabs/parts/tab/useTab.d.ts +1 -1
  75. package/esm/tabs/parts/tab/useTab.js +3 -2
  76. package/esm/tabs/parts/tab/useTab.js.map +1 -1
  77. package/esm/toggle-group/ToggleGroup.context.d.ts +1 -1
  78. package/esm/toggle-group/parts/useToggleItem.d.ts +1 -1
  79. package/esm/toggle-group/parts/useToggleItem.js +3 -2
  80. package/esm/toggle-group/parts/useToggleItem.js.map +1 -1
  81. package/esm/util/hooks/descendants/useDescendant.d.ts +1 -1
  82. package/esm/util/hooks/descendants/useDescendant.js +3 -2
  83. package/esm/util/hooks/descendants/useDescendant.js.map +1 -1
  84. package/esm/util/hooks/useMergeRefs.d.ts +15 -9
  85. package/esm/util/hooks/useMergeRefs.js +93 -25
  86. package/esm/util/hooks/useMergeRefs.js.map +1 -1
  87. package/esm/util/types/AsChildProps.d.ts +0 -4
  88. package/esm/util/virtualfocus/Context.d.ts +1 -1
  89. package/package.json +3 -3
  90. package/src/date/Date.Dialog.tsx +6 -1
  91. package/src/dialog/index.ts +1 -1
  92. package/src/dialog/popup/DialogPopup.tsx +7 -1
  93. package/src/dialog/root/DialogRoot.tsx +5 -5
  94. package/src/layout/base/PrimitiveAsChildProps.ts +1 -4
  95. package/src/modal/Modal.tsx +9 -1
  96. package/src/overlays/action-menu/ActionMenu.tsx +3 -2
  97. package/src/overlays/dismissablelayer/DismissableLayer.tsx +52 -16
  98. package/src/overlays/dismissablelayer/util/useEscapeKeydown.ts +7 -2
  99. package/src/provider/Provider.tsx +1 -5
  100. package/src/slot/Slot.tsx +14 -9
  101. package/src/tabs/parts/tab/useTab.ts +4 -2
  102. package/src/toggle-group/parts/useToggleItem.ts +4 -2
  103. package/src/util/__tests__/useMergeRefs.test.ts +92 -0
  104. package/src/util/hooks/descendants/useDescendant.tsx +4 -2
  105. package/src/util/hooks/useMergeRefs.ts +147 -24
  106. package/src/util/types/AsChildProps.ts +0 -4
@@ -1 +1 @@
1
- {"version":3,"file":"useMergeRefs.js","sourceRoot":"","sources":["../../../src/util/hooks/useMergeRefs.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAEhD,sGAAsG;AACtG,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,gDAAgD;AAChD;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAI,IAAsB;IACjD,OAAO,CAAC,QAAkB,EAAE,EAAE;QAC5B,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACnB,IAAI,OAAO,GAAG,KAAK,UAAU,EAAE,CAAC;gBAC9B,GAAG,CAAC,QAAQ,CAAC,CAAC;YAChB,CAAC;iBAAM,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBAC5C,GAAwC,CAAC,OAAO,GAAG,QAAQ,CAAC;YAC/D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAI,GAAG,IAAsB;IACvD,iGAAiG;IACjG,OAAO,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;AAClD,CAAC"}
1
+ {"version":3,"file":"useMergeRefs.js","sourceRoot":"","sources":["../../../src/util/hooks/useMergeRefs.ts"],"names":[],"mappings":"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"}
@@ -5,14 +5,12 @@ export type AsChildProps = {
5
5
  * merging the props of the component with the props of the child.
6
6
  *
7
7
  * @example
8
- * ```
9
8
  * <Component asChild data-prop>
10
9
  * <ChildComponent data-child />
11
10
  * </Component>
12
11
  *
13
12
  * Out:
14
13
  * <MergedComponent data-prop data-child />
15
- * ```
16
14
  */
17
15
  asChild: true;
18
16
  as?: never;
@@ -23,14 +21,12 @@ export type AsChildProps = {
23
21
  * merging the props of the component with the props of the child.
24
22
  *
25
23
  * @example
26
- * ```
27
24
  * <Component asChild data-prop>
28
25
  * <ChildComponent data-child />
29
26
  * </Component>
30
27
  *
31
28
  * Out:
32
29
  * <MergedComponent data-prop data-child />
33
- * ```
34
30
  */
35
31
  asChild?: false;
36
32
  };
@@ -21,7 +21,7 @@ export declare const VirtualFocusDescendantsProvider: import("react").Provider<i
21
21
  }>;
22
22
  index: number;
23
23
  enabledIndex: number;
24
- register: (instance: HTMLDivElement | null) => void;
24
+ register: ((instance: HTMLDivElement | null) => void | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | null;
25
25
  };
26
26
  export declare const VirtualFocusInternalContextProvider: import("react").FC<{
27
27
  virtualFocusIdx: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navikt/ds-react",
3
- "version": "7.37.0",
3
+ "version": "7.38.0",
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",
@@ -705,8 +705,8 @@
705
705
  "dependencies": {
706
706
  "@floating-ui/react": "0.27.8",
707
707
  "@floating-ui/react-dom": "^2.1.6",
708
- "@navikt/aksel-icons": "^7.37.0",
709
- "@navikt/ds-tokens": "^7.37.0",
708
+ "@navikt/aksel-icons": "^7.38.0",
709
+ "@navikt/ds-tokens": "^7.38.0",
710
710
  "clsx": "^2.1.0",
711
711
  "date-fns": "^4.0.0",
712
712
  "react-day-picker": "9.7.0"
@@ -1,5 +1,6 @@
1
1
  import React, { useRef } from "react";
2
2
  import { Button } from "../button";
3
+ import { useDialogContext } from "../dialog/root/DialogRoot.context";
3
4
  import { Modal } from "../modal";
4
5
  import { useModalContext } from "../modal/Modal.context";
5
6
  import { Popover } from "../popover";
@@ -45,9 +46,13 @@ const DateDialog = ({
45
46
  const { cn } = useRenameCSS();
46
47
 
47
48
  const modalRef = useRef<HTMLDialogElement>(null);
49
+
48
50
  const isInModal = useModalContext(false) !== undefined;
51
+ const isInDialog = useDialogContext(false) !== undefined;
49
52
  const hideModal =
50
- useMedia("screen and (min-width: 768px)", true) && !isInModal;
53
+ useMedia("screen and (min-width: 768px)", true) &&
54
+ !isInModal &&
55
+ !isInDialog;
51
56
 
52
57
  if (!open) {
53
58
  return null;
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- export { Dialog } from "./root/DialogRoot";
2
+ export { default as Dialog } from "./root/DialogRoot";
3
3
  export type { DialogProps } from "./root/DialogRoot";
4
4
  export { DialogTrigger } from "./trigger/DialogTrigger";
5
5
  export type { DialogTriggerProps } from "./trigger/DialogTrigger";
@@ -1,4 +1,5 @@
1
1
  import React, { forwardRef } from "react";
2
+ import { useModalContext } from "../../modal/Modal.context";
2
3
  import { Portal, type PortalProps } from "../../portal";
3
4
  import { DialogBackdropInternal } from "../backdrop/DialogBackdropInternal";
4
5
  import { useDialogContext } from "../root/DialogRoot.context";
@@ -28,7 +29,7 @@ const DialogPopup = forwardRef<HTMLDivElement, DialogPopupProps>(
28
29
  {
29
30
  modal = true,
30
31
  withBackdrop = modal === true,
31
- rootElement,
32
+ rootElement: rootElementProp,
32
33
  position,
33
34
  ...restProps
34
35
  },
@@ -36,6 +37,11 @@ const DialogPopup = forwardRef<HTMLDivElement, DialogPopupProps>(
36
37
  ) => {
37
38
  const { mounted, nested } = useDialogContext();
38
39
 
40
+ const modalContext = useModalContext(false);
41
+ const rootElement = modalContext
42
+ ? modalContext.modalRef.current
43
+ : rootElementProp;
44
+
39
45
  if (!mounted) {
40
46
  return null;
41
47
  }
@@ -179,15 +179,15 @@ interface DialogComponent extends React.FC<DialogProps> {
179
179
  * Dialog body content
180
180
  * </Dialog.Body>
181
181
  * <Dialog.Footer>
182
- *. <Dialog.CloseTrigger>
183
- *. <Button>Close dialog</Button>
184
- *. </Dialog.CloseTrigger>
182
+ * <Dialog.CloseTrigger>
183
+ * <Button>Close dialog</Button>
184
+ * </Dialog.CloseTrigger>
185
185
  * </Dialog.Footer>
186
186
  * </Dialog.Popup>
187
187
  * </Dialog>
188
188
  * ```
189
189
  */
190
- const Dialog: DialogComponent = (props: DialogProps) => {
190
+ export const Dialog: DialogComponent = (props: DialogProps) => {
191
191
  const {
192
192
  children,
193
193
  defaultOpen = false,
@@ -292,5 +292,5 @@ Dialog.Body = DialogBody;
292
292
  Dialog.Footer = DialogFooter;
293
293
  Dialog.Popup = DialogPopup;
294
294
 
295
- export { Dialog };
295
+ export default Dialog;
296
296
  export type { DialogProps };
@@ -4,15 +4,14 @@ export type PrimitiveAsChildProps =
4
4
  /**
5
5
  * Renders the component and its child as a single element,
6
6
  * merging the props of the component with the props of the child.
7
+ *
7
8
  * @example
8
- * ```tsx
9
9
  * <Component asChild data-prop>
10
10
  * <ChildComponent data-child />
11
11
  * </Component>
12
12
  *
13
13
  * // Renders
14
14
  * <div data-prop data-child />
15
- * ```
16
15
  */
17
16
  asChild: true;
18
17
  /**
@@ -29,14 +28,12 @@ export type PrimitiveAsChildProps =
29
28
  * merging the props of the component with the props of the child.
30
29
  *
31
30
  * @example
32
- * ```tsx
33
31
  * <Component asChild data-prop>
34
32
  * <ChildComponent data-child />
35
33
  * </Component>
36
34
  *
37
35
  * // Renders
38
36
  * <div data-prop data-child />
39
- * ```
40
37
  */
41
38
  asChild?: false;
42
39
  };
@@ -230,7 +230,7 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
230
230
  : ariaLabelledby;
231
231
 
232
232
  const component = (
233
- // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
233
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
234
234
  <dialog
235
235
  {...rest}
236
236
  ref={mergedRef}
@@ -248,6 +248,14 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
248
248
  : onMouseDown
249
249
  }
250
250
  aria-labelledby={mergedAriaLabelledBy}
251
+ onKeyDown={(e) => {
252
+ /**
253
+ * Stops propagation of Escape key to prevent closing parent modals/dialogs
254
+ */
255
+ if (e.key === "Escape") {
256
+ e.stopPropagation();
257
+ }
258
+ }}
251
259
  >
252
260
  <ModalContextProvider
253
261
  closeHandler={getCloseHandler(modalRef, header, onBeforeClose)}
@@ -317,7 +317,6 @@ export const ActionMenuTrigger = forwardRef<
317
317
  ref,
318
318
  ) => {
319
319
  const context = useActionMenuContext();
320
-
321
320
  const mergedRefs = useMergeRefs(ref, context.triggerRef);
322
321
 
323
322
  return (
@@ -386,7 +385,9 @@ export const ActionMenuContent = forwardRef<
386
385
  sideOffset={4}
387
386
  collisionPadding={10}
388
387
  returnFocus={context.triggerRef}
389
- safeZone={{ anchor: context.triggerRef.current }}
388
+ safeZone={{
389
+ anchor: context.triggerRef.current,
390
+ }}
390
391
  style={{
391
392
  ...style,
392
393
  ...{
@@ -1,4 +1,10 @@
1
- import React, { forwardRef, useContext, useEffect, useState } from "react";
1
+ import React, {
2
+ forwardRef,
3
+ useContext,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ } from "react";
2
8
  import { Slot } from "../../slot/Slot";
3
9
  import { composeEventHandlers } from "../../util/composeEventHandlers";
4
10
  import { useMergeRefs } from "../../util/hooks";
@@ -61,6 +67,7 @@ interface DismissableLayerBaseProps
61
67
  onDismiss?: (event: Event) => void;
62
68
  /**
63
69
  * Stops `onDismiss` from beeing called when interacting with the `safeZone` elements.
70
+ * - anchor: The element that should be considered safe to interact with.
64
71
  */
65
72
  safeZone?: {
66
73
  anchor?: Element | null;
@@ -112,6 +119,8 @@ const DismissableLayer = forwardRef<HTMLDivElement, DismissableLayerProps>(
112
119
  ) => {
113
120
  const context = useContext(DismissableLayerContext);
114
121
 
122
+ const triggerPointerDownRef = useRef<boolean>(false);
123
+
115
124
  const [, forceRerender] = useState({});
116
125
  const [node, setNode] = React.useState<DismissableLayerElement | null>(
117
126
  null,
@@ -142,13 +151,10 @@ const DismissableLayer = forwardRef<HTMLDivElement, DismissableLayerProps>(
142
151
  return;
143
152
  }
144
153
 
145
- let hasPointerDownOutside = false;
146
-
147
- if (!event.defaultPrevented) {
148
- if (event.detail.originalEvent.type === "pointerdown") {
149
- hasPointerDownOutside = true;
150
- }
151
- }
154
+ const eventType = event.detail.originalEvent.type as
155
+ | "pointerup"
156
+ | "pointerdown"
157
+ | "focusin";
152
158
 
153
159
  const target = event.target as HTMLElement;
154
160
 
@@ -160,19 +166,17 @@ const DismissableLayer = forwardRef<HTMLDivElement, DismissableLayerProps>(
160
166
  }
161
167
 
162
168
  /**
163
- * In Safari, if the trigger element is inside a container with tabIndex={0}, a click on the trigger
164
- * will first fire a 'pointerdownoutside' event on the trigger itself. However, it will then fire a
165
- * 'focusoutside' event on the container.
166
- *
167
- * To handle this, we ignore any 'focusoutside' events if a 'pointerdownoutside' event has already occurred.
168
- * 'pointerdownoutside' event is sufficient to indicate interaction outside the DismissableLayer.
169
+ * If the target is inside a custom element, event.target will be that element on pointerdown.
170
+ * Therefore, checking anchor.contains(target) etc. won't work in that case.
169
171
  */
170
172
  if (
171
- event.detail.originalEvent.type === "focusin" &&
172
- hasPointerDownOutside
173
+ eventType === "pointerdown" &&
174
+ triggerPointerDownRef.current === true
173
175
  ) {
174
176
  event.preventDefault();
175
177
  }
178
+
179
+ triggerPointerDownRef.current = false;
176
180
  }
177
181
 
178
182
  const pointerDownOutside = usePointerDownOutside(
@@ -283,6 +287,38 @@ const DismissableLayer = forwardRef<HTMLDivElement, DismissableLayerProps>(
283
287
  enabled,
284
288
  );
285
289
 
290
+ useEffect(() => {
291
+ if (!safeZone?.anchor) {
292
+ return;
293
+ }
294
+
295
+ const handlePointerDown = () => {
296
+ triggerPointerDownRef.current = true;
297
+ };
298
+
299
+ const handlePointerEnd = () => {
300
+ triggerPointerDownRef.current = false;
301
+ };
302
+
303
+ const anchor = safeZone.anchor;
304
+
305
+ anchor.addEventListener("pointerdown", handlePointerDown, {
306
+ capture: true,
307
+ });
308
+ anchor.addEventListener("pointerup", handlePointerEnd);
309
+ anchor.addEventListener("pointerleave", handlePointerEnd);
310
+ anchor.addEventListener("pointercancel", handlePointerEnd);
311
+
312
+ return () => {
313
+ anchor.removeEventListener("pointerdown", handlePointerDown, {
314
+ capture: true,
315
+ });
316
+ anchor.removeEventListener("pointerup", handlePointerEnd);
317
+ anchor.removeEventListener("pointerleave", handlePointerEnd);
318
+ anchor.removeEventListener("pointercancel", handlePointerEnd);
319
+ };
320
+ }, [safeZone?.anchor]);
321
+
286
322
  /**
287
323
  * Handles registering `layers` and `layersWithOutsidePointerEventsDisabled`.
288
324
  */
@@ -19,10 +19,15 @@ export function useEscapeKeydown(
19
19
  }
20
20
  };
21
21
 
22
- ownerDocument.addEventListener("keydown", handleKeyDown, true);
22
+ /**
23
+ * We use the bubbling phase (not capture) so that elements inside the layer
24
+ * can handle Escape themselves and call stopPropagation() if needed.
25
+ * Layer ordering is handled programmatically via the DismissableLayerContext.
26
+ */
27
+ ownerDocument.addEventListener("keydown", handleKeyDown);
23
28
 
24
29
  return () => {
25
- ownerDocument.removeEventListener("keydown", handleKeyDown, true);
30
+ ownerDocument.removeEventListener("keydown", handleKeyDown);
26
31
  };
27
32
  }, [onEscapeKeyDown, ownerDocument, enabled]);
28
33
  }
@@ -24,12 +24,10 @@ export type ProviderProps = {
24
24
  * Aksel locale
25
25
  * @default nb
26
26
  * @example
27
- * ```jsx
28
27
  * import { en } from "@navikt/ds-react/locales";
29
28
  * <Provider locale={en}>
30
- * {app}
29
+ * {app}
31
30
  * </Provider>
32
- * ```
33
31
  */
34
32
  locale: Translations;
35
33
  /**
@@ -54,11 +52,9 @@ export const useProvider = () => useContext(ProviderContext);
54
52
  * @see 🏷️ {@link ProviderProps}
55
53
  *
56
54
  * @example
57
- * ```jsx
58
55
  * <Provider rootElement={rootElement}>
59
56
  * {app}
60
57
  * </Provider>
61
- * ```
62
58
  */
63
59
  export const Provider = ({
64
60
  children,
package/src/slot/Slot.tsx CHANGED
@@ -1,25 +1,30 @@
1
1
  import React from "react";
2
- import { mergeRefs } from "../util/hooks/useMergeRefs";
2
+ import { useMergeRefs } from "../util/hooks";
3
3
  import { mergeProps } from "./merge-props";
4
4
 
5
5
  interface SlotProps extends React.HTMLAttributes<HTMLElement> {
6
6
  children?: React.ReactNode;
7
7
  }
8
8
 
9
+ function getChildRef(children: React.ReactNode): React.Ref<HTMLElement> | null {
10
+ if (!React.isValidElement(children)) {
11
+ return null;
12
+ }
13
+ return Object.prototype.propertyIsEnumerable.call(children.props, "ref")
14
+ ? (children.props as any).ref // React 19 (children.ref still works, but gives a warning)
15
+ : (children as any).ref; // React <19
16
+ }
17
+
9
18
  const Slot = React.forwardRef<HTMLElement, SlotProps>((props, forwardedRef) => {
10
19
  const { children, ...slotProps } = props;
11
20
 
12
- if (React.isValidElement(children)) {
13
- const childRef = Object.prototype.propertyIsEnumerable.call(
14
- children.props,
15
- "ref",
16
- )
17
- ? (children.props as any).ref // React 19 (children.ref still works, but gives a warning)
18
- : (children as any).ref; // React <19
21
+ const childRef = getChildRef(children);
22
+ const mergedRef = useMergeRefs(forwardedRef, childRef);
19
23
 
24
+ if (React.isValidElement(children)) {
20
25
  return React.cloneElement<any>(children, {
21
26
  ...mergeProps(slotProps, children.props as any),
22
- ref: forwardedRef ? mergeRefs([forwardedRef, childRef]) : childRef,
27
+ ref: mergedRef,
23
28
  });
24
29
  }
25
30
 
@@ -1,5 +1,5 @@
1
1
  import { composeEventHandlers } from "../../../util/composeEventHandlers";
2
- import { mergeRefs } from "../../../util/hooks/useMergeRefs";
2
+ import { useMergeRefs } from "../../../util/hooks/useMergeRefs";
3
3
  import { useTabsContext, useTabsDescendant } from "../../Tabs.context";
4
4
 
5
5
  export interface UseTabProps {
@@ -40,8 +40,10 @@ export function useTab<P extends UseTabProps>(
40
40
  selectionFollowsFocus && setSelectedValue(value);
41
41
  };
42
42
 
43
+ const refs = useMergeRefs(register, ref);
44
+
43
45
  return {
44
- ref: mergeRefs([register, ref]),
46
+ ref: refs,
45
47
  isSelected,
46
48
  isFocused: focusedValue === value,
47
49
  id: makeTabId(id, value),
@@ -1,6 +1,6 @@
1
1
  import { useCallback } from "react";
2
2
  import { composeEventHandlers } from "../../util/composeEventHandlers";
3
- import { mergeRefs } from "../../util/hooks/useMergeRefs";
3
+ import { useMergeRefs } from "../../util/hooks/useMergeRefs";
4
4
  import {
5
5
  useToggleGroupContext,
6
6
  useToggleGroupDescendant,
@@ -96,8 +96,10 @@ export function useToggleItem<P extends UseToggleItemProps>(
96
96
  [descendants, focusedValue, selectedValue, setFocusedValue],
97
97
  );
98
98
 
99
+ const refs = useMergeRefs(register, ref);
100
+
99
101
  return {
100
- ref: mergeRefs([register, ref]),
102
+ ref: refs,
101
103
  isSelected,
102
104
  isFocused: focusedValue === value,
103
105
  onClick: composeEventHandlers(
@@ -0,0 +1,92 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { useRef } from "react";
3
+ import { describe, expect, test, vi } from "vitest";
4
+ import { useMergeRefs } from "../hooks/useMergeRefs";
5
+
6
+ describe("useMergeRefs", () => {
7
+ test("returns null when all refs are null or undefined", () => {
8
+ const { result } = renderHook(() => useMergeRefs(null, undefined));
9
+ expect(result.current).toBeNull();
10
+ });
11
+
12
+ test("assigns instance to object ref", () => {
13
+ const { result } = renderHook(() => {
14
+ const ref = useRef<HTMLDivElement | null>(null);
15
+ return { merged: useMergeRefs(ref, null), ref };
16
+ });
17
+
18
+ const div = document.createElement("div");
19
+ result.current.merged?.(div);
20
+
21
+ expect(result.current.ref.current).toBe(div);
22
+ });
23
+
24
+ test("calls function ref with instance", () => {
25
+ const fnRef = vi.fn();
26
+ const { result } = renderHook(() => useMergeRefs(fnRef, null));
27
+
28
+ const div = document.createElement("div");
29
+ result.current?.(div);
30
+
31
+ expect(fnRef).toHaveBeenCalledWith(div);
32
+ });
33
+
34
+ test("handles mixed ref types", () => {
35
+ const fnRef = vi.fn();
36
+ const { result } = renderHook(() => {
37
+ const objRef = useRef<HTMLDivElement | null>(null);
38
+ return { merged: useMergeRefs(objRef, fnRef, null), objRef };
39
+ });
40
+
41
+ const div = document.createElement("div");
42
+ result.current.merged?.(div);
43
+
44
+ expect(result.current.objRef.current).toBe(div);
45
+ expect(fnRef).toHaveBeenCalledWith(div);
46
+ });
47
+
48
+ test("cleanup resets object ref to null", () => {
49
+ const { result } = renderHook(() => {
50
+ const ref = useRef<HTMLDivElement | null>(null);
51
+ return { merged: useMergeRefs(ref, null), ref };
52
+ });
53
+
54
+ const div = document.createElement("div");
55
+ result.current.merged?.(div);
56
+ expect(result.current.ref.current).toBe(div);
57
+
58
+ result.current.merged?.(null);
59
+ expect(result.current.ref.current).toBeNull();
60
+ });
61
+
62
+ test("cleanup calls returned cleanup function from callback ref", () => {
63
+ const cleanup = vi.fn();
64
+ const fnRef = vi.fn().mockReturnValue(cleanup);
65
+ const { result } = renderHook(() => useMergeRefs(fnRef, null));
66
+
67
+ const div1 = document.createElement("div");
68
+ result.current?.(div1);
69
+
70
+ const div2 = document.createElement("div");
71
+ result.current?.(div2);
72
+
73
+ expect(cleanup).toHaveBeenCalledTimes(1);
74
+ expect(fnRef).not.toHaveBeenCalledWith(null);
75
+ });
76
+
77
+ test("runs previous cleanup before assigning new instance", () => {
78
+ const callOrder: string[] = [];
79
+ const cleanup = vi.fn(() => callOrder.push("cleanup"));
80
+ const fnRef = vi.fn(() => {
81
+ callOrder.push("ref");
82
+ return cleanup;
83
+ });
84
+
85
+ const { result } = renderHook(() => useMergeRefs(fnRef, null));
86
+
87
+ result.current?.(document.createElement("div"));
88
+ result.current?.(document.createElement("div"));
89
+
90
+ expect(callOrder).toEqual(["ref", "cleanup", "ref"]);
91
+ });
92
+ });
@@ -4,7 +4,7 @@
4
4
  import React, { useRef, useState } from "react";
5
5
  import { createStrictContext } from "../../create-strict-context";
6
6
  import { useClientLayoutEffect } from "../useClientLayoutEffect";
7
- import { mergeRefs } from "../useMergeRefs";
7
+ import { useMergeRefs } from "../useMergeRefs";
8
8
  import { DescendantOptions, DescendantsManager } from "./descendant";
9
9
  import { cast } from "./utils";
10
10
 
@@ -66,11 +66,13 @@ export function createDescendantContext<
66
66
  ? cast<React.RefCallback<T>>(descendants.register(options))
67
67
  : cast<React.RefCallback<T>>(descendants.register);
68
68
 
69
+ const refs = useMergeRefs(refCallback, ref);
70
+
69
71
  return {
70
72
  descendants,
71
73
  index,
72
74
  enabledIndex: descendants.enabledIndexOf(ref.current),
73
- register: mergeRefs([refCallback, ref]),
75
+ register: refs,
74
76
  };
75
77
  }
76
78