@funstack/ui 0.0.0-stage → 0.1.0-alpha.1
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.
- package/LICENSE +21 -0
- package/README.md +42 -2
- package/dist/accordion/accordion-context.d.mts +5 -0
- package/dist/accordion/accordion-context.d.mts.map +1 -0
- package/dist/accordion/accordion-context.mjs +9 -0
- package/dist/accordion/accordion-context.mjs.map +1 -0
- package/dist/accordion/accordion-item.d.mts +53 -0
- package/dist/accordion/accordion-item.d.mts.map +1 -0
- package/dist/accordion/accordion-item.mjs +85 -0
- package/dist/accordion/accordion-item.mjs.map +1 -0
- package/dist/accordion/accordion-panel.d.mts +40 -0
- package/dist/accordion/accordion-panel.d.mts.map +1 -0
- package/dist/accordion/accordion-panel.mjs +40 -0
- package/dist/accordion/accordion-panel.mjs.map +1 -0
- package/dist/accordion/accordion-root.d.mts +90 -0
- package/dist/accordion/accordion-root.d.mts.map +1 -0
- package/dist/accordion/accordion-root.mjs +229 -0
- package/dist/accordion/accordion-root.mjs.map +1 -0
- package/dist/accordion/accordion-trigger.d.mts +37 -0
- package/dist/accordion/accordion-trigger.d.mts.map +1 -0
- package/dist/accordion/accordion-trigger.mjs +81 -0
- package/dist/accordion/accordion-trigger.mjs.map +1 -0
- package/dist/accordion/index.d.mts +20 -0
- package/dist/accordion/index.d.mts.map +1 -0
- package/dist/accordion/index.mjs +20 -0
- package/dist/accordion/index.mjs.map +1 -0
- package/dist/checkbox/checkbox.d.mts +92 -0
- package/dist/checkbox/checkbox.d.mts.map +1 -0
- package/dist/checkbox/checkbox.mjs +82 -0
- package/dist/checkbox/checkbox.mjs.map +1 -0
- package/dist/checkbox/index.d.mts +3 -0
- package/dist/checkbox/index.mjs +1 -0
- package/dist/checkbox-group/checkbox-group-context.mjs +24 -0
- package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-item.d.mts +55 -0
- package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-item.mjs +90 -0
- package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-label.d.mts +21 -0
- package/dist/checkbox-group/checkbox-group-label.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-label.mjs +30 -0
- package/dist/checkbox-group/checkbox-group-label.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-parent.d.mts +68 -0
- package/dist/checkbox-group/checkbox-group-parent.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-parent.mjs +145 -0
- package/dist/checkbox-group/checkbox-group-parent.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-root.d.mts +100 -0
- package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-root.mjs +291 -0
- package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -0
- package/dist/checkbox-group/index.d.mts +23 -0
- package/dist/checkbox-group/index.d.mts.map +1 -0
- package/dist/checkbox-group/index.mjs +23 -0
- package/dist/checkbox-group/index.mjs.map +1 -0
- package/dist/dialog/dialog-close.d.mts +44 -0
- package/dist/dialog/dialog-close.d.mts.map +1 -0
- package/dist/dialog/dialog-close.mjs +76 -0
- package/dist/dialog/dialog-close.mjs.map +1 -0
- package/dist/dialog/dialog-context.mjs +13 -0
- package/dist/dialog/dialog-context.mjs.map +1 -0
- package/dist/dialog/dialog-description.d.mts +22 -0
- package/dist/dialog/dialog-description.d.mts.map +1 -0
- package/dist/dialog/dialog-description.mjs +31 -0
- package/dist/dialog/dialog-description.mjs.map +1 -0
- package/dist/dialog/dialog-form.d.mts +58 -0
- package/dist/dialog/dialog-form.d.mts.map +1 -0
- package/dist/dialog/dialog-form.mjs +108 -0
- package/dist/dialog/dialog-form.mjs.map +1 -0
- package/dist/dialog/dialog-popup.d.mts +81 -0
- package/dist/dialog/dialog-popup.d.mts.map +1 -0
- package/dist/dialog/dialog-popup.mjs +367 -0
- package/dist/dialog/dialog-popup.mjs.map +1 -0
- package/dist/dialog/dialog-root.d.mts +70 -0
- package/dist/dialog/dialog-root.d.mts.map +1 -0
- package/dist/dialog/dialog-root.mjs +113 -0
- package/dist/dialog/dialog-root.mjs.map +1 -0
- package/dist/dialog/dialog-title.d.mts +23 -0
- package/dist/dialog/dialog-title.d.mts.map +1 -0
- package/dist/dialog/dialog-title.mjs +31 -0
- package/dist/dialog/dialog-title.mjs.map +1 -0
- package/dist/dialog/dialog-trigger.d.mts +39 -0
- package/dist/dialog/dialog-trigger.d.mts.map +1 -0
- package/dist/dialog/dialog-trigger.mjs +80 -0
- package/dist/dialog/dialog-trigger.mjs.map +1 -0
- package/dist/dialog/index.d.mts +28 -0
- package/dist/dialog/index.d.mts.map +1 -0
- package/dist/dialog/index.mjs +28 -0
- package/dist/dialog/index.mjs.map +1 -0
- package/dist/index.d.mts +60 -0
- package/dist/index.mjs +12 -0
- package/dist/internal/action-details.mjs +47 -0
- package/dist/internal/action-details.mjs.map +1 -0
- package/dist/internal/anchor-position.d.mts +7 -0
- package/dist/internal/anchor-position.d.mts.map +1 -0
- package/dist/internal/anchor-position.mjs +16 -0
- package/dist/internal/anchor-position.mjs.map +1 -0
- package/dist/internal/data-attr.mjs +13 -0
- package/dist/internal/data-attr.mjs.map +1 -0
- package/dist/internal/merge-props.mjs +119 -0
- package/dist/internal/merge-props.mjs.map +1 -0
- package/dist/internal/platform-attributes.d.mts +55 -0
- package/dist/internal/platform-attributes.d.mts.map +1 -0
- package/dist/internal/sync-presence.mjs +19 -0
- package/dist/internal/sync-presence.mjs.map +1 -0
- package/dist/internal/transition-boundary.mjs +52 -0
- package/dist/internal/transition-boundary.mjs.map +1 -0
- package/dist/internal/transition-types.mjs +15 -0
- package/dist/internal/transition-types.mjs.map +1 -0
- package/dist/internal/types.d.mts +38 -0
- package/dist/internal/types.d.mts.map +1 -0
- package/dist/internal/use-action-trigger.mjs +58 -0
- package/dist/internal/use-action-trigger.mjs.map +1 -0
- package/dist/internal/use-controllable-action.mjs +115 -0
- package/dist/internal/use-controllable-action.mjs.map +1 -0
- package/dist/internal/use-delayed-pair.mjs +40 -0
- package/dist/internal/use-delayed-pair.mjs.map +1 -0
- package/dist/internal/use-dev-warning.mjs +21 -0
- package/dist/internal/use-dev-warning.mjs.map +1 -0
- package/dist/internal/use-exit-animation.mjs +55 -0
- package/dist/internal/use-exit-animation.mjs.map +1 -0
- package/dist/internal/use-form-reset.mjs +50 -0
- package/dist/internal/use-form-reset.mjs.map +1 -0
- package/dist/internal/use-native-checked.mjs +113 -0
- package/dist/internal/use-native-checked.mjs.map +1 -0
- package/dist/internal/use-native-group-reconciliation.mjs +64 -0
- package/dist/internal/use-native-group-reconciliation.mjs.map +1 -0
- package/dist/internal/use-popover-popup.mjs +274 -0
- package/dist/internal/use-popover-popup.mjs.map +1 -0
- package/dist/internal/use-popover-root.d.mts +2 -0
- package/dist/internal/use-popover-root.mjs +112 -0
- package/dist/internal/use-popover-root.mjs.map +1 -0
- package/dist/internal/use-popover-trigger.mjs +86 -0
- package/dist/internal/use-popover-trigger.mjs.map +1 -0
- package/dist/internal/use-render.mjs +51 -0
- package/dist/internal/use-render.mjs.map +1 -0
- package/dist/internal/use-until-found.mjs +40 -0
- package/dist/internal/use-until-found.mjs.map +1 -0
- package/dist/menu/index.d.mts +34 -0
- package/dist/menu/index.d.mts.map +1 -0
- package/dist/menu/index.mjs +34 -0
- package/dist/menu/index.mjs.map +1 -0
- package/dist/menu/menu-checkbox-item.d.mts +42 -0
- package/dist/menu/menu-checkbox-item.d.mts.map +1 -0
- package/dist/menu/menu-checkbox-item.mjs +71 -0
- package/dist/menu/menu-checkbox-item.mjs.map +1 -0
- package/dist/menu/menu-context.d.mts +1 -0
- package/dist/menu/menu-context.mjs +28 -0
- package/dist/menu/menu-context.mjs.map +1 -0
- package/dist/menu/menu-dom.mjs +37 -0
- package/dist/menu/menu-dom.mjs.map +1 -0
- package/dist/menu/menu-group.d.mts +48 -0
- package/dist/menu/menu-group.d.mts.map +1 -0
- package/dist/menu/menu-group.mjs +69 -0
- package/dist/menu/menu-group.mjs.map +1 -0
- package/dist/menu/menu-hover.mjs +29 -0
- package/dist/menu/menu-hover.mjs.map +1 -0
- package/dist/menu/menu-item.d.mts +45 -0
- package/dist/menu/menu-item.d.mts.map +1 -0
- package/dist/menu/menu-item.mjs +54 -0
- package/dist/menu/menu-item.mjs.map +1 -0
- package/dist/menu/menu-popup.d.mts +69 -0
- package/dist/menu/menu-popup.d.mts.map +1 -0
- package/dist/menu/menu-popup.mjs +207 -0
- package/dist/menu/menu-popup.mjs.map +1 -0
- package/dist/menu/menu-radio-group.d.mts +37 -0
- package/dist/menu/menu-radio-group.d.mts.map +1 -0
- package/dist/menu/menu-radio-group.mjs +72 -0
- package/dist/menu/menu-radio-group.mjs.map +1 -0
- package/dist/menu/menu-radio-item.d.mts +33 -0
- package/dist/menu/menu-radio-item.d.mts.map +1 -0
- package/dist/menu/menu-radio-item.mjs +62 -0
- package/dist/menu/menu-radio-item.mjs.map +1 -0
- package/dist/menu/menu-root.d.mts +54 -0
- package/dist/menu/menu-root.d.mts.map +1 -0
- package/dist/menu/menu-root.mjs +102 -0
- package/dist/menu/menu-root.mjs.map +1 -0
- package/dist/menu/menu-submenu-trigger.d.mts +31 -0
- package/dist/menu/menu-submenu-trigger.d.mts.map +1 -0
- package/dist/menu/menu-submenu-trigger.mjs +92 -0
- package/dist/menu/menu-submenu-trigger.mjs.map +1 -0
- package/dist/menu/menu-trigger.d.mts +36 -0
- package/dist/menu/menu-trigger.d.mts.map +1 -0
- package/dist/menu/menu-trigger.mjs +81 -0
- package/dist/menu/menu-trigger.mjs.map +1 -0
- package/dist/menu/use-menu-item.mjs +54 -0
- package/dist/menu/use-menu-item.mjs.map +1 -0
- package/dist/popover/index.d.mts +28 -0
- package/dist/popover/index.d.mts.map +1 -0
- package/dist/popover/index.mjs +28 -0
- package/dist/popover/index.mjs.map +1 -0
- package/dist/popover/popover-close.d.mts +41 -0
- package/dist/popover/popover-close.d.mts.map +1 -0
- package/dist/popover/popover-close.mjs +90 -0
- package/dist/popover/popover-close.mjs.map +1 -0
- package/dist/popover/popover-context.mjs +26 -0
- package/dist/popover/popover-context.mjs.map +1 -0
- package/dist/popover/popover-description.d.mts +22 -0
- package/dist/popover/popover-description.d.mts.map +1 -0
- package/dist/popover/popover-description.mjs +28 -0
- package/dist/popover/popover-description.mjs.map +1 -0
- package/dist/popover/popover-popup.d.mts +82 -0
- package/dist/popover/popover-popup.d.mts.map +1 -0
- package/dist/popover/popover-popup.mjs +93 -0
- package/dist/popover/popover-popup.mjs.map +1 -0
- package/dist/popover/popover-root.d.mts +64 -0
- package/dist/popover/popover-root.d.mts.map +1 -0
- package/dist/popover/popover-root.mjs +39 -0
- package/dist/popover/popover-root.mjs.map +1 -0
- package/dist/popover/popover-title.d.mts +23 -0
- package/dist/popover/popover-title.d.mts.map +1 -0
- package/dist/popover/popover-title.mjs +28 -0
- package/dist/popover/popover-title.mjs.map +1 -0
- package/dist/popover/popover-trigger.d.mts +37 -0
- package/dist/popover/popover-trigger.d.mts.map +1 -0
- package/dist/popover/popover-trigger.mjs +40 -0
- package/dist/popover/popover-trigger.mjs.map +1 -0
- package/dist/radio-group/index.d.mts +20 -0
- package/dist/radio-group/index.d.mts.map +1 -0
- package/dist/radio-group/index.mjs +20 -0
- package/dist/radio-group/index.mjs.map +1 -0
- package/dist/radio-group/radio-group-context.mjs +13 -0
- package/dist/radio-group/radio-group-context.mjs.map +1 -0
- package/dist/radio-group/radio-group-item.d.mts +54 -0
- package/dist/radio-group/radio-group-item.d.mts.map +1 -0
- package/dist/radio-group/radio-group-item.mjs +83 -0
- package/dist/radio-group/radio-group-item.mjs.map +1 -0
- package/dist/radio-group/radio-group-label.d.mts +21 -0
- package/dist/radio-group/radio-group-label.d.mts.map +1 -0
- package/dist/radio-group/radio-group-label.mjs +30 -0
- package/dist/radio-group/radio-group-label.mjs.map +1 -0
- package/dist/radio-group/radio-group-root.d.mts +92 -0
- package/dist/radio-group/radio-group-root.d.mts.map +1 -0
- package/dist/radio-group/radio-group-root.mjs +137 -0
- package/dist/radio-group/radio-group-root.mjs.map +1 -0
- package/dist/switch/index.d.mts +3 -0
- package/dist/switch/index.mjs +1 -0
- package/dist/switch/switch.d.mts +78 -0
- package/dist/switch/switch.d.mts.map +1 -0
- package/dist/switch/switch.mjs +77 -0
- package/dist/switch/switch.mjs.map +1 -0
- package/dist/tabs/index.d.mts +24 -0
- package/dist/tabs/index.d.mts.map +1 -0
- package/dist/tabs/index.mjs +23 -0
- package/dist/tabs/index.mjs.map +1 -0
- package/dist/tabs/tabs-context.d.mts +7 -0
- package/dist/tabs/tabs-context.d.mts.map +1 -0
- package/dist/tabs/tabs-context.mjs +14 -0
- package/dist/tabs/tabs-context.mjs.map +1 -0
- package/dist/tabs/tabs-ids.mjs +39 -0
- package/dist/tabs/tabs-ids.mjs.map +1 -0
- package/dist/tabs/tabs-indicator.d.mts +32 -0
- package/dist/tabs/tabs-indicator.d.mts.map +1 -0
- package/dist/tabs/tabs-indicator.mjs +49 -0
- package/dist/tabs/tabs-indicator.mjs.map +1 -0
- package/dist/tabs/tabs-list.d.mts +34 -0
- package/dist/tabs/tabs-list.d.mts.map +1 -0
- package/dist/tabs/tabs-list.mjs +130 -0
- package/dist/tabs/tabs-list.mjs.map +1 -0
- package/dist/tabs/tabs-panel.d.mts +50 -0
- package/dist/tabs/tabs-panel.d.mts.map +1 -0
- package/dist/tabs/tabs-panel.mjs +81 -0
- package/dist/tabs/tabs-panel.mjs.map +1 -0
- package/dist/tabs/tabs-root.d.mts +64 -0
- package/dist/tabs/tabs-root.d.mts.map +1 -0
- package/dist/tabs/tabs-root.mjs +90 -0
- package/dist/tabs/tabs-root.mjs.map +1 -0
- package/dist/tabs/tabs-tab.d.mts +49 -0
- package/dist/tabs/tabs-tab.d.mts.map +1 -0
- package/dist/tabs/tabs-tab.mjs +89 -0
- package/dist/tabs/tabs-tab.mjs.map +1 -0
- package/dist/toggle/index.d.mts +3 -0
- package/dist/toggle/index.mjs +1 -0
- package/dist/toggle/toggle.d.mts +57 -0
- package/dist/toggle/toggle.d.mts.map +1 -0
- package/dist/toggle/toggle.mjs +59 -0
- package/dist/toggle/toggle.mjs.map +1 -0
- package/dist/tooltip/index.d.mts +20 -0
- package/dist/tooltip/index.d.mts.map +1 -0
- package/dist/tooltip/index.mjs +19 -0
- package/dist/tooltip/index.mjs.map +1 -0
- package/dist/tooltip/tooltip-context.d.mts +5 -0
- package/dist/tooltip/tooltip-context.d.mts.map +1 -0
- package/dist/tooltip/tooltip-context.mjs +13 -0
- package/dist/tooltip/tooltip-context.mjs.map +1 -0
- package/dist/tooltip/tooltip-popup.d.mts +49 -0
- package/dist/tooltip/tooltip-popup.d.mts.map +1 -0
- package/dist/tooltip/tooltip-popup.mjs +52 -0
- package/dist/tooltip/tooltip-popup.mjs.map +1 -0
- package/dist/tooltip/tooltip-root.d.mts +41 -0
- package/dist/tooltip/tooltip-root.d.mts.map +1 -0
- package/dist/tooltip/tooltip-root.mjs +86 -0
- package/dist/tooltip/tooltip-root.mjs.map +1 -0
- package/dist/tooltip/tooltip-trigger.d.mts +38 -0
- package/dist/tooltip/tooltip-trigger.d.mts.map +1 -0
- package/dist/tooltip/tooltip-trigger.mjs +55 -0
- package/dist/tooltip/tooltip-trigger.mjs.map +1 -0
- package/dist/tooltip/use-tooltip-interaction.mjs +119 -0
- package/dist/tooltip/use-tooltip-interaction.mjs.map +1 -0
- package/package.json +73 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useFormReset } from "./use-form-reset.mjs";
|
|
3
|
+
import { useCallback, useEffectEvent, useLayoutEffect, useRef } from "react";
|
|
4
|
+
//#region src/internal/use-native-checked.ts
|
|
5
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
6
|
+
/**
|
|
7
|
+
* Per-element machinery for a controlled native `<input type="checkbox" | "radio">`
|
|
8
|
+
* (conventions 3.8): returns the ref to attach to the input (merged by `useRender`).
|
|
9
|
+
*
|
|
10
|
+
* 1. Ref callback: stores the node; in development warns once when it is not an `<input>` of
|
|
11
|
+
* the expected `type`.
|
|
12
|
+
* 2. Mount reconciliation (single controls): a click before hydration or Firefox's form-state
|
|
13
|
+
* restoration left the DOM different from what the user intends; reported once with
|
|
14
|
+
* `event: undefined`.
|
|
15
|
+
* 3. Re-apply after every commit: `checked` (optimistic, or the latest in-flight value),
|
|
16
|
+
* `indeterminate` and `defaultChecked`, none of which React manages for a controlled input
|
|
17
|
+
* except `checked`.
|
|
18
|
+
* 4. Form reset (single controls): in a microtask after the reset, compares `el.defaultChecked`
|
|
19
|
+
* with what the user intends and reports a difference with the `reset` event.
|
|
20
|
+
*
|
|
21
|
+
* The hook knows nothing about transitions or `reason`; the part wires `onPlatformChange`.
|
|
22
|
+
*/
|
|
23
|
+
function useNativeChecked(options) {
|
|
24
|
+
const { type, component, checked, defaultChecked, indeterminate, group = false } = options;
|
|
25
|
+
const node = useRef(null);
|
|
26
|
+
const warnedElement = useRef(false);
|
|
27
|
+
const ref = useCallback((el) => {
|
|
28
|
+
node.current = el;
|
|
29
|
+
if (isDev && !warnedElement.current && !isInputOfType(el, type)) {
|
|
30
|
+
warnedElement.current = true;
|
|
31
|
+
console.error(`${component}: expected to render an \`<input type="${type}">\` but got ${describeElement(el)}; the platform semantics (form participation, labels, keyboard, reset) need a real input. A \`render\` element must keep the input.`);
|
|
32
|
+
}
|
|
33
|
+
return () => {
|
|
34
|
+
node.current = null;
|
|
35
|
+
};
|
|
36
|
+
}, [type, component]);
|
|
37
|
+
/** What the user intends: the latest in-flight dispatch, else the optimistic value (rule 1). */
|
|
38
|
+
const readBase = useEffectEvent(() => options.latestInFlight?.()?.next ?? checked);
|
|
39
|
+
const report = useEffectEvent((next, event) => {
|
|
40
|
+
options.onPlatformChange?.(next, event);
|
|
41
|
+
});
|
|
42
|
+
const reconcileMount = useEffectEvent(() => {
|
|
43
|
+
if (group) return;
|
|
44
|
+
const el = node.current;
|
|
45
|
+
if (el === null) return;
|
|
46
|
+
if (el.checked !== readBase()) report(el.checked, void 0);
|
|
47
|
+
});
|
|
48
|
+
useLayoutEffect(() => {
|
|
49
|
+
reconcileMount();
|
|
50
|
+
}, []);
|
|
51
|
+
const notifyMounted = useEffectEvent(() => {
|
|
52
|
+
if (group) options.onItemMounted?.(node.current);
|
|
53
|
+
});
|
|
54
|
+
useLayoutEffect(() => {
|
|
55
|
+
notifyMounted();
|
|
56
|
+
}, []);
|
|
57
|
+
const renderToken = {};
|
|
58
|
+
const mountToken = useRef(null);
|
|
59
|
+
useLayoutEffect(() => {
|
|
60
|
+
if (mountToken.current === null) mountToken.current = renderToken;
|
|
61
|
+
const el = node.current;
|
|
62
|
+
if (el === null) return;
|
|
63
|
+
if (defaultChecked !== void 0 && el.defaultChecked !== defaultChecked) el.defaultChecked = defaultChecked;
|
|
64
|
+
if (mountToken.current !== renderToken) {
|
|
65
|
+
const want = readBase();
|
|
66
|
+
if (el.checked !== want) el.checked = want;
|
|
67
|
+
}
|
|
68
|
+
if (indeterminate !== void 0 && el.indeterminate !== indeterminate) el.indeterminate = indeterminate;
|
|
69
|
+
});
|
|
70
|
+
useFormReset(node, (event) => {
|
|
71
|
+
if (group) return;
|
|
72
|
+
const el = node.current;
|
|
73
|
+
if (el === null) return;
|
|
74
|
+
const next = el.defaultChecked;
|
|
75
|
+
const base = options.latestInFlight?.()?.next ?? checked;
|
|
76
|
+
if (next !== base) options.onPlatformChange?.(next, event);
|
|
77
|
+
else if (el.checked !== base) el.checked = base;
|
|
78
|
+
});
|
|
79
|
+
return ref;
|
|
80
|
+
}
|
|
81
|
+
function isInputOfType(el, type) {
|
|
82
|
+
return el.tagName === "INPUT" && el.type === type;
|
|
83
|
+
}
|
|
84
|
+
function describeElement(el) {
|
|
85
|
+
const tag = el.tagName.toLowerCase();
|
|
86
|
+
return tag === "input" ? `\`<input type="${el.type}">\`` : `\`<${tag}>\``;
|
|
87
|
+
}
|
|
88
|
+
const warnedPreventDefault = /* @__PURE__ */ new WeakSet();
|
|
89
|
+
/**
|
|
90
|
+
* Shared `onChange` guard for checkable inputs (conventions 3.8 rule 9, 2.3 item 3):
|
|
91
|
+
*
|
|
92
|
+
* - A click that is already cancelled (`preventDefault()` in a pass-through `onClick`, which runs
|
|
93
|
+
* before the library's `onChange`) is not a change: the browser reverts the DOM after dispatch
|
|
94
|
+
* and React restores unchanged props. Nothing is dispatched.
|
|
95
|
+
* - Otherwise `dispatch()` runs (the action's sync prefix included); if the event is cancelled
|
|
96
|
+
* now, the action's sync prefix called `preventDefault()`, which desyncs the input while the
|
|
97
|
+
* action is pending. Development warns once per input element.
|
|
98
|
+
*/
|
|
99
|
+
function guardCheckableChange(event, dispatch, component = "A checkable input") {
|
|
100
|
+
if (event.defaultPrevented) return;
|
|
101
|
+
dispatch();
|
|
102
|
+
if (!isDev || !event.defaultPrevented) return;
|
|
103
|
+
const target = event.target;
|
|
104
|
+
if (target !== null) {
|
|
105
|
+
if (warnedPreventDefault.has(target)) return;
|
|
106
|
+
warnedPreventDefault.add(target);
|
|
107
|
+
}
|
|
108
|
+
console.error(`${component}: \`preventDefault()\` on the change event is not supported; it desyncs the input while the action is pending. Use controlled mode to veto a change.`);
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { guardCheckableChange, useNativeChecked };
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=use-native-checked.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-native-checked.mjs","names":[],"sources":["../../src/internal/use-native-checked.ts"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffectEvent, useLayoutEffect, useRef } from \"react\";\nimport type { RefCallback } from \"react\";\nimport { useFormReset } from \"./use-form-reset.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nexport interface UseNativeCheckedOptions {\n /** Dev-only: the expected `input.type`. */\n readonly type: \"checkbox\" | \"radio\";\n /** Dev-only: the part's name in warnings (`\"Switch\"`, `\"RadioGroup.Item\"`). */\n readonly component: string;\n /** What the DOM must show after every commit: the optimistic value. */\n readonly checked: boolean;\n /**\n * The form-reset target. Uncontrolled parts pass the latest `defaultChecked` prop (`?? false`),\n * mirrored into `el.defaultChecked` after every commit. Controlled parts pass `undefined`:\n * React's mount-time value stays (conventions 3.8 rule 6).\n */\n readonly defaultChecked: boolean | undefined;\n /** Checkbox only: `el.indeterminate` after every commit (the part passes `indeterminate && !pending`). */\n readonly indeterminate?: boolean | undefined;\n /**\n * `true` inside a group (`RadioGroup.Item`): the hook does the element check and the re-apply\n * only; mount reconciliation and form reset are reported once per group by the Root through\n * `useNativeGroupReconciliation`. Default `false`: the hook reports both paths itself.\n */\n readonly group?: boolean | undefined;\n /**\n * Group members only: the Root's `onItemMounted` from `useNativeGroupReconciliation`. Called\n * from this member's mount layout effect so that a member mounting after the Root (a Suspense\n * boundary that hydrates later) still gets the group reconciled. Receives the member's element\n * (`null` if the ref is not attached). Ignored without `group`.\n */\n readonly onItemMounted?: ((element: HTMLInputElement | null) => void) | undefined;\n /** From `useControllableAction`: the value the DOM is compared against on the residual paths. */\n readonly latestInFlight?: (() => { readonly next: boolean } | null) | undefined;\n /**\n * Single controls only. The browser changed the value on a residual path and it differs from\n * what the user intends. `event` is the `reset` event, or `undefined` for mount reconciliation.\n * The part dispatches with `reason: \"platform\"`.\n */\n readonly onPlatformChange?: ((next: boolean, event: Event | undefined) => void) | undefined;\n}\n\n/**\n * Per-element machinery for a controlled native `<input type=\"checkbox\" | \"radio\">`\n * (conventions 3.8): returns the ref to attach to the input (merged by `useRender`).\n *\n * 1. Ref callback: stores the node; in development warns once when it is not an `<input>` of\n * the expected `type`.\n * 2. Mount reconciliation (single controls): a click before hydration or Firefox's form-state\n * restoration left the DOM different from what the user intends; reported once with\n * `event: undefined`.\n * 3. Re-apply after every commit: `checked` (optimistic, or the latest in-flight value),\n * `indeterminate` and `defaultChecked`, none of which React manages for a controlled input\n * except `checked`.\n * 4. Form reset (single controls): in a microtask after the reset, compares `el.defaultChecked`\n * with what the user intends and reports a difference with the `reset` event.\n *\n * The hook knows nothing about transitions or `reason`; the part wires `onPlatformChange`.\n */\nexport function useNativeChecked(options: UseNativeCheckedOptions): RefCallback<HTMLInputElement> {\n const { type, component, checked, defaultChecked, indeterminate, group = false } = options;\n const node = useRef<HTMLInputElement | null>(null);\n const warnedElement = useRef(false);\n\n const ref = useCallback(\n (el: HTMLInputElement) => {\n node.current = el;\n if (isDev && !warnedElement.current && !isInputOfType(el, type)) {\n warnedElement.current = true;\n console.error(\n `${component}: expected to render an \\`<input type=\"${type}\">\\` but got ` +\n `${describeElement(el)}; the platform semantics (form participation, labels, ` +\n `keyboard, reset) need a real input. A \\`render\\` element must keep the input.`,\n );\n }\n return () => {\n node.current = null;\n };\n },\n [type, component],\n );\n\n /** What the user intends: the latest in-flight dispatch, else the optimistic value (rule 1). */\n const readBase = useEffectEvent(() => options.latestInFlight?.()?.next ?? checked);\n const report = useEffectEvent((next: boolean, event: Event | undefined) => {\n options.onPlatformChange?.(next, event);\n });\n\n // 2. Mount reconciliation. Declared before the re-apply effect so that the hydrated DOM value is\n // read before anything overwrites it (rule 3). StrictMode's second run finds the first run's\n // dispatch in flight and does nothing.\n const reconcileMount = useEffectEvent(() => {\n if (group) return;\n const el = node.current;\n if (el === null) return;\n if (el.checked !== readBase()) report(el.checked, undefined);\n });\n useLayoutEffect(() => {\n reconcileMount();\n }, []);\n\n // 2b. Group members tell the Root they mounted; the Root reconciles the group again if its own\n // mount reconciliation already ran (a member mounting later), which covers the `checked`\n // write this member skips in its mount commit (below).\n const notifyMounted = useEffectEvent(() => {\n if (group) options.onItemMounted?.(node.current);\n });\n useLayoutEffect(() => {\n notifyMounted();\n }, []);\n\n // 3. Re-apply after every commit (rule 8). `checked` is skipped in the mount commit (including\n // StrictMode's re-run of it, which closes over the same render): React has just written it\n // from the prop, or kept the hydrated DOM value for the residual path to read. That matters\n // for groups, whose Root reconciles in its own layout effect, after its Items' effects.\n const renderToken = {};\n const mountToken = useRef<object | null>(null);\n useLayoutEffect(() => {\n if (mountToken.current === null) mountToken.current = renderToken;\n const el = node.current;\n if (el === null) return;\n // The order defaultChecked -> checked -> indeterminate is load-bearing for the first two.\n // React's automatic reset after a <form action> runs in the mutation phase, after react-dom\n // wrote `checked` and before this effect, and clears the dirty-checkedness flag; writing\n // `defaultChecked` (a new default from the same transition) then moves `checked` too, and the\n // `checked` write that follows restores the intended value. The other way round, the `checked`\n // write is skipped (it still matches) and the DOM ends up on the new default.\n // `indeterminate` is independent of both writes and goes last. Tested in\n // use-native-checked.test.tsx (\"re-apply order\").\n if (defaultChecked !== undefined && el.defaultChecked !== defaultChecked) {\n el.defaultChecked = defaultChecked;\n }\n if (mountToken.current !== renderToken) {\n const want = readBase();\n if (el.checked !== want) el.checked = want;\n }\n if (indeterminate !== undefined && el.indeterminate !== indeterminate) {\n el.indeterminate = indeterminate;\n }\n });\n\n // 4. Form reset (rules 4 to 7). `el.defaultChecked` is the value the browser reset to; by the\n // time the microtask runs after React's own reset, the re-apply effect may already have put\n // the optimistic value back into `checked`. (`useFormReset` already calls this through an\n // Effect Event, so it reads the latest render's options directly.)\n useFormReset(node, (event) => {\n if (group) return;\n const el = node.current;\n if (el === null) return;\n const next = el.defaultChecked;\n const base = options.latestInFlight?.()?.next ?? checked;\n if (next !== base) options.onPlatformChange?.(next, event);\n else if (el.checked !== base) el.checked = base;\n });\n\n return ref;\n}\n\nfunction isInputOfType(el: Element, type: string): boolean {\n return el.tagName === \"INPUT\" && (el as HTMLInputElement).type === type;\n}\n\nfunction describeElement(el: Element): string {\n const tag = el.tagName.toLowerCase();\n return tag === \"input\" ? `\\`<input type=\"${(el as HTMLInputElement).type}\">\\`` : `\\`<${tag}>\\``;\n}\n\nconst warnedPreventDefault = new WeakSet<EventTarget>();\n\n/**\n * Shared `onChange` guard for checkable inputs (conventions 3.8 rule 9, 2.3 item 3):\n *\n * - A click that is already cancelled (`preventDefault()` in a pass-through `onClick`, which runs\n * before the library's `onChange`) is not a change: the browser reverts the DOM after dispatch\n * and React restores unchanged props. Nothing is dispatched.\n * - Otherwise `dispatch()` runs (the action's sync prefix included); if the event is cancelled\n * now, the action's sync prefix called `preventDefault()`, which desyncs the input while the\n * action is pending. Development warns once per input element.\n */\nexport function guardCheckableChange(\n event: Event,\n dispatch: () => void,\n component: string = \"A checkable input\",\n): void {\n if (event.defaultPrevented) return;\n dispatch();\n if (!isDev || !event.defaultPrevented) return;\n const target = event.target;\n if (target !== null) {\n if (warnedPreventDefault.has(target)) return;\n warnedPreventDefault.add(target);\n }\n console.error(\n `${component}: \\`preventDefault()\\` on the change event is not supported; it desyncs the ` +\n `input while the action is pending. Use controlled mode to veto a change.`,\n );\n}\n"],"mappings":";;;;AAKA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;;;;;;;;AAyDvC,SAAgB,iBAAiB,SAAiE;CAChG,MAAM,EAAE,MAAM,WAAW,SAAS,gBAAgB,eAAe,QAAQ,UAAU;CACnF,MAAM,OAAO,OAAgC,IAAI;CACjD,MAAM,gBAAgB,OAAO,KAAK;CAElC,MAAM,MAAM,aACT,OAAyB;EACxB,KAAK,UAAU;EACf,IAAI,SAAS,CAAC,cAAc,WAAW,CAAC,cAAc,IAAI,IAAI,GAAG;GAC/D,cAAc,UAAU;GACxB,QAAQ,MACN,GAAG,UAAU,yCAAyC,KAAK,eACtD,gBAAgB,EAAE,EAAE,oIAE3B;EACF;EACA,aAAa;GACX,KAAK,UAAU;EACjB;CACF,GACA,CAAC,MAAM,SAAS,CAClB;;CAGA,MAAM,WAAW,qBAAqB,QAAQ,iBAAiB,CAAC,EAAE,QAAQ,OAAO;CACjF,MAAM,SAAS,gBAAgB,MAAe,UAA6B;EACzE,QAAQ,mBAAmB,MAAM,KAAK;CACxC,CAAC;CAKD,MAAM,iBAAiB,qBAAqB;EAC1C,IAAI,OAAO;EACX,MAAM,KAAK,KAAK;EAChB,IAAI,OAAO,MAAM;EACjB,IAAI,GAAG,YAAY,SAAS,GAAG,OAAO,GAAG,SAAS,KAAA,CAAS;CAC7D,CAAC;CACD,sBAAsB;EACpB,eAAe;CACjB,GAAG,CAAC,CAAC;CAKL,MAAM,gBAAgB,qBAAqB;EACzC,IAAI,OAAO,QAAQ,gBAAgB,KAAK,OAAO;CACjD,CAAC;CACD,sBAAsB;EACpB,cAAc;CAChB,GAAG,CAAC,CAAC;CAML,MAAM,cAAc,CAAC;CACrB,MAAM,aAAa,OAAsB,IAAI;CAC7C,sBAAsB;EACpB,IAAI,WAAW,YAAY,MAAM,WAAW,UAAU;EACtD,MAAM,KAAK,KAAK;EAChB,IAAI,OAAO,MAAM;EASjB,IAAI,mBAAmB,KAAA,KAAa,GAAG,mBAAmB,gBACxD,GAAG,iBAAiB;EAEtB,IAAI,WAAW,YAAY,aAAa;GACtC,MAAM,OAAO,SAAS;GACtB,IAAI,GAAG,YAAY,MAAM,GAAG,UAAU;EACxC;EACA,IAAI,kBAAkB,KAAA,KAAa,GAAG,kBAAkB,eACtD,GAAG,gBAAgB;CAEvB,CAAC;CAMD,aAAa,OAAO,UAAU;EAC5B,IAAI,OAAO;EACX,MAAM,KAAK,KAAK;EAChB,IAAI,OAAO,MAAM;EACjB,MAAM,OAAO,GAAG;EAChB,MAAM,OAAO,QAAQ,iBAAiB,CAAC,EAAE,QAAQ;EACjD,IAAI,SAAS,MAAM,QAAQ,mBAAmB,MAAM,KAAK;OACpD,IAAI,GAAG,YAAY,MAAM,GAAG,UAAU;CAC7C,CAAC;CAED,OAAO;AACT;AAEA,SAAS,cAAc,IAAa,MAAuB;CACzD,OAAO,GAAG,YAAY,WAAY,GAAwB,SAAS;AACrE;AAEA,SAAS,gBAAgB,IAAqB;CAC5C,MAAM,MAAM,GAAG,QAAQ,YAAY;CACnC,OAAO,QAAQ,UAAU,kBAAmB,GAAwB,KAAK,QAAQ,MAAM,IAAI;AAC7F;AAEA,MAAM,uCAAuB,IAAI,QAAqB;;;;;;;;;;;AAYtD,SAAgB,qBACd,OACA,UACA,YAAoB,qBACd;CACN,IAAI,MAAM,kBAAkB;CAC5B,SAAS;CACT,IAAI,CAAC,SAAS,CAAC,MAAM,kBAAkB;CACvC,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,MAAM;EACnB,IAAI,qBAAqB,IAAI,MAAM,GAAG;EACtC,qBAAqB,IAAI,MAAM;CACjC;CACA,QAAQ,MACN,GAAG,UAAU,qJAEf;AACF"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useFormReset } from "./use-form-reset.mjs";
|
|
3
|
+
import { useCallback, useEffectEvent, useLayoutEffect, useRef } from "react";
|
|
4
|
+
//#region src/internal/use-native-group-reconciliation.ts
|
|
5
|
+
/**
|
|
6
|
+
* Group adapter of the native-input machinery (conventions 3.8): one dispatch per group for the
|
|
7
|
+
* residual browser paths, from the Root rather than once per member.
|
|
8
|
+
*
|
|
9
|
+
* - Mount reconciliation, once, in a layout effect: `readCurrent()` vs `base()`. The members'
|
|
10
|
+
* `useNativeChecked({ group: true })` leaves `checked` alone in their mount commit, so this
|
|
11
|
+
* effect, which runs after the members' (children first), still reads the hydrated DOM.
|
|
12
|
+
* - Late members: a member mounting in a later commit (a Suspense boundary that hydrates after the
|
|
13
|
+
* Root, where a click before hydration changed the DOM) reports through `onItemMounted`, and the
|
|
14
|
+
* group is reconciled again the same way (see `UseNativeGroupReconciliationResult`).
|
|
15
|
+
* - Form reset through `useFormReset` on the container: in a microtask after the reset,
|
|
16
|
+
* `readDefault()` vs `base()`.
|
|
17
|
+
*
|
|
18
|
+
* Values are compared with `equals` (default `Object.is`).
|
|
19
|
+
*/
|
|
20
|
+
function useNativeGroupReconciliation(options) {
|
|
21
|
+
const { element } = options;
|
|
22
|
+
const latest = useRef(options);
|
|
23
|
+
useLayoutEffect(() => {
|
|
24
|
+
latest.current = options;
|
|
25
|
+
});
|
|
26
|
+
/** The Root's own mount reconciliation has run (reset by its cleanup, StrictMode included). */
|
|
27
|
+
const rootReconciled = useRef(false);
|
|
28
|
+
/** A late reconciliation is queued; members mounting in the same commit share it. */
|
|
29
|
+
const lateQueued = useRef(false);
|
|
30
|
+
const reconcileMount = useEffectEvent(() => {
|
|
31
|
+
if (element.current === null) return;
|
|
32
|
+
reconcile(options, options.readCurrent());
|
|
33
|
+
});
|
|
34
|
+
useLayoutEffect(() => {
|
|
35
|
+
reconcileMount();
|
|
36
|
+
rootReconciled.current = true;
|
|
37
|
+
return () => {
|
|
38
|
+
rootReconciled.current = false;
|
|
39
|
+
};
|
|
40
|
+
}, []);
|
|
41
|
+
const onItemMounted = useCallback(() => {
|
|
42
|
+
if (!rootReconciled.current || lateQueued.current) return;
|
|
43
|
+
lateQueued.current = true;
|
|
44
|
+
queueMicrotask(() => {
|
|
45
|
+
lateQueued.current = false;
|
|
46
|
+
if (!rootReconciled.current) return;
|
|
47
|
+
const current = latest.current;
|
|
48
|
+
if (current.element.current === null) return;
|
|
49
|
+
reconcile(current, current.readCurrent());
|
|
50
|
+
});
|
|
51
|
+
}, []);
|
|
52
|
+
useFormReset(element, (event) => {
|
|
53
|
+
if (element.current === null) return;
|
|
54
|
+
reconcile(options, options.readDefault(), event);
|
|
55
|
+
});
|
|
56
|
+
return { onItemMounted };
|
|
57
|
+
}
|
|
58
|
+
function reconcile(options, next, event) {
|
|
59
|
+
if (!(options.equals ?? Object.is)(next, options.base())) options.onPlatformChange(next, event);
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { useNativeGroupReconciliation };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=use-native-group-reconciliation.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-native-group-reconciliation.mjs","names":[],"sources":["../../src/internal/use-native-group-reconciliation.ts"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffectEvent, useLayoutEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\nimport { useFormReset } from \"./use-form-reset.js\";\n\nexport interface UseNativeGroupReconciliationOptions<T> {\n /** The group's form-associated container (`RadioGroup.Root`'s `<fieldset>`). */\n readonly element: RefObject<HTMLFieldSetElement | null>;\n /** The group's value as the DOM shows it now (`checked` of the members). */\n readonly readCurrent: () => T;\n /** The value the browser resets the group to (`defaultChecked` of the members, rule 5). */\n readonly readDefault: () => T;\n /**\n * What the user intends (rule 1): the latest in-flight dispatch's `next`, else the optimistic\n * value. Use an explicit null check, `const f = latestInFlight(); return f === null ?\n * optimistic : f.next;`, not `?.next ?? optimistic`, which drops an in-flight dispatch of a\n * nullish value (a pending reset to \"nothing checked\").\n */\n readonly base: () => T;\n /**\n * The browser changed the group's value on a residual path and it differs from `base()`.\n * `event` is the `reset` event, or `undefined` for mount reconciliation (the Root's mount, or a\n * member mounting later). The Root dispatches once for the whole group with\n * `reason: \"platform\"`.\n */\n readonly onPlatformChange: (next: T, event: Event | undefined) => void;\n /**\n * Equality between a read value and `base()`. Default `Object.is`, right for a primitive value\n * (RadioGroup's `string | null`); a group with a collection value (CheckboxGroup's\n * `ReadonlySet<string>`) passes structural equality, since every read builds a new object.\n */\n readonly equals?: ((a: T, b: T) => boolean) | undefined;\n}\n\nexport interface UseNativeGroupReconciliationResult {\n /**\n * Stable. Pass it to every member, which hands it to\n * `useNativeChecked({ group: true, onItemMounted })`; the hook calls it from the member's mount\n * layout effect. A member that mounts after the Root's own mount reconciliation (inside a\n * Suspense boundary that hydrates later, selective hydration, or a member added on the client)\n * makes the adapter reconcile the group again: `readCurrent()` vs `base()`, in a microtask, once\n * for all members mounted in the same commit. Members mounting in the Root's own commit are\n * covered by the Root's mount reconciliation (their effects run before the Root's) and are\n * ignored.\n *\n * RadioGroup wiring: `const { onItemMounted } = useNativeGroupReconciliation(...)` in the Root,\n * put `onItemMounted` in the group context, and in each Item\n * `useNativeChecked({ type: \"radio\", group: true, onItemMounted: ctx.onItemMounted, ... })`.\n * The member's element argument is not used here; the Root may use it (dev warnings).\n */\n readonly onItemMounted: (element?: Element | null) => void;\n}\n\n/**\n * Group adapter of the native-input machinery (conventions 3.8): one dispatch per group for the\n * residual browser paths, from the Root rather than once per member.\n *\n * - Mount reconciliation, once, in a layout effect: `readCurrent()` vs `base()`. The members'\n * `useNativeChecked({ group: true })` leaves `checked` alone in their mount commit, so this\n * effect, which runs after the members' (children first), still reads the hydrated DOM.\n * - Late members: a member mounting in a later commit (a Suspense boundary that hydrates after the\n * Root, where a click before hydration changed the DOM) reports through `onItemMounted`, and the\n * group is reconciled again the same way (see `UseNativeGroupReconciliationResult`).\n * - Form reset through `useFormReset` on the container: in a microtask after the reset,\n * `readDefault()` vs `base()`.\n *\n * Values are compared with `equals` (default `Object.is`).\n */\nexport function useNativeGroupReconciliation<T>(\n options: UseNativeGroupReconciliationOptions<T>,\n): UseNativeGroupReconciliationResult {\n const { element } = options;\n\n // The latest committed options, for the late reconciliation, which runs in a microtask outside\n // any effect (so it cannot go through an Effect Event).\n const latest = useRef(options);\n useLayoutEffect(() => {\n latest.current = options;\n });\n\n /** The Root's own mount reconciliation has run (reset by its cleanup, StrictMode included). */\n const rootReconciled = useRef(false);\n /** A late reconciliation is queued; members mounting in the same commit share it. */\n const lateQueued = useRef(false);\n\n const reconcileMount = useEffectEvent(() => {\n if (element.current === null) return;\n reconcile(options, options.readCurrent());\n });\n useLayoutEffect(() => {\n reconcileMount();\n rootReconciled.current = true;\n return () => {\n rootReconciled.current = false;\n };\n }, []);\n\n const onItemMounted = useCallback(() => {\n if (!rootReconciled.current || lateQueued.current) return;\n lateQueued.current = true;\n queueMicrotask(() => {\n lateQueued.current = false;\n if (!rootReconciled.current) return;\n const current = latest.current;\n if (current.element.current === null) return;\n reconcile(current, current.readCurrent());\n });\n }, []);\n\n useFormReset(element, (event) => {\n if (element.current === null) return;\n reconcile(options, options.readDefault(), event);\n });\n\n return { onItemMounted };\n}\n\nfunction reconcile<T>(options: UseNativeGroupReconciliationOptions<T>, next: T, event?: Event) {\n const equals = options.equals ?? Object.is;\n if (!equals(next, options.base())) options.onPlatformChange(next, event);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoEA,SAAgB,6BACd,SACoC;CACpC,MAAM,EAAE,YAAY;CAIpB,MAAM,SAAS,OAAO,OAAO;CAC7B,sBAAsB;EACpB,OAAO,UAAU;CACnB,CAAC;;CAGD,MAAM,iBAAiB,OAAO,KAAK;;CAEnC,MAAM,aAAa,OAAO,KAAK;CAE/B,MAAM,iBAAiB,qBAAqB;EAC1C,IAAI,QAAQ,YAAY,MAAM;EAC9B,UAAU,SAAS,QAAQ,YAAY,CAAC;CAC1C,CAAC;CACD,sBAAsB;EACpB,eAAe;EACf,eAAe,UAAU;EACzB,aAAa;GACX,eAAe,UAAU;EAC3B;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,CAAC,eAAe,WAAW,WAAW,SAAS;EACnD,WAAW,UAAU;EACrB,qBAAqB;GACnB,WAAW,UAAU;GACrB,IAAI,CAAC,eAAe,SAAS;GAC7B,MAAM,UAAU,OAAO;GACvB,IAAI,QAAQ,QAAQ,YAAY,MAAM;GACtC,UAAU,SAAS,QAAQ,YAAY,CAAC;EAC1C,CAAC;CACH,GAAG,CAAC,CAAC;CAEL,aAAa,UAAU,UAAU;EAC/B,IAAI,QAAQ,YAAY,MAAM;EAC9B,UAAU,SAAS,QAAQ,YAAY,GAAG,KAAK;CACjD,CAAC;CAED,OAAO,EAAE,cAAc;AACzB;AAEA,SAAS,UAAa,SAAiD,MAAS,OAAe;CAE7F,IAAI,EADW,QAAQ,UAAU,OAAO,GAAA,CAC5B,MAAM,QAAQ,KAAK,CAAC,GAAG,QAAQ,iBAAiB,MAAM,KAAK;AACzE"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { exitAnimations, useExitAnimation } from "./use-exit-animation.mjs";
|
|
3
|
+
import { createElement, useCallback, useEffect, useEffectEvent, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
//#region src/internal/use-popover-popup.ts
|
|
5
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
6
|
+
function isPopoverOpen(element) {
|
|
7
|
+
try {
|
|
8
|
+
return element.matches(":popover-open");
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
let focusVisibleOption;
|
|
14
|
+
/**
|
|
15
|
+
* The engine reads the `focusVisible` member of `focus()`'s options (Chromium 145, Firefox 104,
|
|
16
|
+
* Safari 18.4). Detected once through a getter: the bindings convert the dictionary before the
|
|
17
|
+
* method runs, so a detached element is enough and nothing is focused.
|
|
18
|
+
*/
|
|
19
|
+
function supportsFocusVisibleOption(doc) {
|
|
20
|
+
if (focusVisibleOption === void 0) {
|
|
21
|
+
let read = false;
|
|
22
|
+
try {
|
|
23
|
+
doc.createElement("div").focus({ get focusVisible() {
|
|
24
|
+
read = true;
|
|
25
|
+
return false;
|
|
26
|
+
} });
|
|
27
|
+
} catch {
|
|
28
|
+
read = false;
|
|
29
|
+
}
|
|
30
|
+
focusVisibleOption = read;
|
|
31
|
+
}
|
|
32
|
+
return focusVisibleOption;
|
|
33
|
+
}
|
|
34
|
+
/** Resolves `initialFocus` against the popup (a ref, or a selector inside the popup). */
|
|
35
|
+
function resolveInitialFocus(popup, initialFocus) {
|
|
36
|
+
if (typeof initialFocus === "string") return popup.querySelector(initialFocus);
|
|
37
|
+
return initialFocus.current;
|
|
38
|
+
}
|
|
39
|
+
/** Whether `element` sits inside a popover that is not shown (an outer popup that closed). */
|
|
40
|
+
function insideHiddenPopover(element) {
|
|
41
|
+
for (let node = element.parentElement; node !== null; node = node.parentElement) if (node.hasAttribute("popover") && !isPopoverOpen(node)) return true;
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Records, from its layout-effect cleanup, whether focus is inside the popup when the content
|
|
46
|
+
* goes away: React runs a deleted component's layout cleanups before it detaches the host nodes,
|
|
47
|
+
* so `document.activeElement` still names the focused element there.
|
|
48
|
+
*/
|
|
49
|
+
function ContentFocusProbe({ onDetach, children }) {
|
|
50
|
+
useLayoutEffect(() => onDetach, [onDetach]);
|
|
51
|
+
return children;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Everything in a popover-like Popup except rendering (design/components/popover.md 14.2): mount
|
|
55
|
+
* reconciliation, the apply effect, the native `beforetoggle` / `toggle` / `command` listeners,
|
|
56
|
+
* close intents, the focus-return fallback, the JS-free `aria-expanded` sync, the deferred
|
|
57
|
+
* unmount while a hide animates, and `initialFocus`. Shared by Popover and Menu.
|
|
58
|
+
*/
|
|
59
|
+
function usePopoverPopup(ctx, options) {
|
|
60
|
+
const { part, initialFocus, transition, onBeforeToggle: onBeforeToggleOption } = options;
|
|
61
|
+
const onToggleOption = options.onToggle;
|
|
62
|
+
const isLibrary = ctx.mode === "library";
|
|
63
|
+
const { popupRef, triggerRef } = ctx;
|
|
64
|
+
const opening = useRef(false);
|
|
65
|
+
const awaitingClose = useRef(false);
|
|
66
|
+
const focusWasInside = useRef(false);
|
|
67
|
+
const contentHadFocus = useRef(false);
|
|
68
|
+
const warnedFocus = useRef(false);
|
|
69
|
+
const exit = useExitAnimation();
|
|
70
|
+
const [prevCommitted, setPrevCommitted] = useState(ctx.committed);
|
|
71
|
+
if (prevCommitted !== ctx.committed) {
|
|
72
|
+
setPrevCommitted(ctx.committed);
|
|
73
|
+
if (isLibrary && prevCommitted && transition === void 0) exit.holdDuringRender();
|
|
74
|
+
}
|
|
75
|
+
const recordContentFocus = useCallback(() => {
|
|
76
|
+
const element = popupRef.current;
|
|
77
|
+
if (element === null) return;
|
|
78
|
+
const active = element.ownerDocument.activeElement;
|
|
79
|
+
if (active !== null && active !== element && element.contains(active)) contentHadFocus.current = true;
|
|
80
|
+
}, [popupRef]);
|
|
81
|
+
const wrapContent = useCallback((children) => createElement(ContentFocusProbe, { onDetach: recordContentFocus }, children), [recordContentFocus]);
|
|
82
|
+
const syncExpanded = (element) => {
|
|
83
|
+
triggerRef.current?.setAttribute("aria-expanded", String(isPopoverOpen(element)));
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Design 6.2 rule 2, for a close that began with focus inside: when focus is now on `<body>`
|
|
87
|
+
* or still inside the hidden popup, focus the Trigger (connected, not inside a hidden
|
|
88
|
+
* popover), else blur what holds focus.
|
|
89
|
+
*/
|
|
90
|
+
const returnFocus = (element) => {
|
|
91
|
+
const doc = element.ownerDocument;
|
|
92
|
+
const active = doc.activeElement;
|
|
93
|
+
if (active !== null && active !== doc.body && !element.contains(active)) return;
|
|
94
|
+
const trigger = triggerRef.current;
|
|
95
|
+
if (trigger !== null && trigger.isConnected && !insideHiddenPopover(trigger)) trigger.focus();
|
|
96
|
+
else if (active instanceof HTMLElement && active !== doc.body) active.blur();
|
|
97
|
+
};
|
|
98
|
+
const resolveFocusTarget = (element) => {
|
|
99
|
+
if (options.resolveInitialFocus !== void 0) {
|
|
100
|
+
const resolved = options.resolveInitialFocus(element);
|
|
101
|
+
if (resolved === null) return null;
|
|
102
|
+
const target = resolved instanceof HTMLElement ? resolved : resolved.element;
|
|
103
|
+
return element.contains(target) ? resolved : null;
|
|
104
|
+
}
|
|
105
|
+
if (initialFocus === void 0) return null;
|
|
106
|
+
const target = resolveInitialFocus(element, initialFocus);
|
|
107
|
+
if (target !== null && element.contains(target)) return target;
|
|
108
|
+
if (isDev && !warnedFocus.current) {
|
|
109
|
+
warnedFocus.current = true;
|
|
110
|
+
console.error(target === null ? `${part}: \`initialFocus\` resolved to no element; the platform picks the focus target.` : `${part}: \`initialFocus\` is outside the popup; the platform picks the focus target.`);
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
};
|
|
114
|
+
const showPopup = (element) => {
|
|
115
|
+
const resolved = resolveFocusTarget(element);
|
|
116
|
+
const target = resolved === null || resolved instanceof HTMLElement ? resolved : resolved.element;
|
|
117
|
+
const quiet = target !== null && !(resolved instanceof HTMLElement) && supportsFocusVisibleOption(element.ownerDocument);
|
|
118
|
+
const added = target !== null && !quiet && !target.hasAttribute("autofocus");
|
|
119
|
+
if (added) target.setAttribute("autofocus", "");
|
|
120
|
+
opening.current = true;
|
|
121
|
+
try {
|
|
122
|
+
const source = triggerRef.current;
|
|
123
|
+
element.showPopover(source !== null && source.isConnected ? { source } : void 0);
|
|
124
|
+
} finally {
|
|
125
|
+
opening.current = false;
|
|
126
|
+
if (added) target.removeAttribute("autofocus");
|
|
127
|
+
}
|
|
128
|
+
if (quiet && isPopoverOpen(element) && element.contains(target)) target.focus({ focusVisible: false });
|
|
129
|
+
};
|
|
130
|
+
const reconcileOnMount = useEffectEvent(() => {
|
|
131
|
+
const element = popupRef.current;
|
|
132
|
+
if (element === null) return;
|
|
133
|
+
if (!isLibrary) {
|
|
134
|
+
syncExpanded(element);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (isPopoverOpen(element)) ctx.requestOpen(void 0, "platform");
|
|
138
|
+
});
|
|
139
|
+
useLayoutEffect(() => {
|
|
140
|
+
reconcileOnMount();
|
|
141
|
+
}, []);
|
|
142
|
+
useLayoutEffect(() => {
|
|
143
|
+
if (!isLibrary) return;
|
|
144
|
+
const element = popupRef.current;
|
|
145
|
+
if (element === null || !element.isConnected) return;
|
|
146
|
+
if (!ctx.pending && ctx.latestInFlight() === null) {
|
|
147
|
+
const shown = isPopoverOpen(element);
|
|
148
|
+
if (ctx.committed && !shown) {
|
|
149
|
+
if (!awaitingClose.current) showPopup(element);
|
|
150
|
+
} else if (!ctx.committed && shown) {
|
|
151
|
+
ctx.recordCloseIntent(void 0);
|
|
152
|
+
element.hidePopover();
|
|
153
|
+
if (focusWasInside.current) returnFocus(element);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (ctx.committed && isPopoverOpen(element)) contentHadFocus.current = false;
|
|
157
|
+
if (exit.held && !ctx.committed && !exit.waiting() && !awaitingClose.current && !isPopoverOpen(element)) exit.release();
|
|
158
|
+
});
|
|
159
|
+
const onBeforeToggle = (event) => {
|
|
160
|
+
const element = popupRef.current;
|
|
161
|
+
if (element === null || event.target !== element) return;
|
|
162
|
+
const toggle = event;
|
|
163
|
+
if (toggle.newState === "open") {
|
|
164
|
+
if (isLibrary && !opening.current) {
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
ctx.requestOpen(event, "activate");
|
|
167
|
+
} else ctx.nextOpenGeneration();
|
|
168
|
+
contentHadFocus.current = false;
|
|
169
|
+
} else {
|
|
170
|
+
awaitingClose.current = true;
|
|
171
|
+
const active = element.ownerDocument.activeElement;
|
|
172
|
+
focusWasInside.current = active !== null && element.contains(active) || contentHadFocus.current;
|
|
173
|
+
contentHadFocus.current = false;
|
|
174
|
+
}
|
|
175
|
+
onBeforeToggleOption?.(toggle);
|
|
176
|
+
};
|
|
177
|
+
const onToggle = (event) => {
|
|
178
|
+
const element = popupRef.current;
|
|
179
|
+
if (element === null || event.target !== element) return;
|
|
180
|
+
awaitingClose.current = false;
|
|
181
|
+
if (!isLibrary) syncExpanded(element);
|
|
182
|
+
if (!isPopoverOpen(element)) {
|
|
183
|
+
const wasInside = focusWasInside.current;
|
|
184
|
+
focusWasInside.current = false;
|
|
185
|
+
if (wasInside) returnFocus(element);
|
|
186
|
+
const intent = ctx.takeCloseIntent();
|
|
187
|
+
if (isLibrary) {
|
|
188
|
+
if (!!ctx.intended() || transition === void 0) {
|
|
189
|
+
const animations = exitAnimations(element);
|
|
190
|
+
if (animations.length > 0) exit.holdUntil(animations);
|
|
191
|
+
else exit.release();
|
|
192
|
+
}
|
|
193
|
+
ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? "dismiss");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
onToggleOption?.(event);
|
|
197
|
+
};
|
|
198
|
+
const onCommand = (event) => {
|
|
199
|
+
const element = popupRef.current;
|
|
200
|
+
if (element === null || event.target !== element || event.defaultPrevented) return;
|
|
201
|
+
const command = String(event.command ?? "").toLowerCase();
|
|
202
|
+
if (command === "hide-popover" || command === "toggle-popover" && isPopoverOpen(element)) ctx.recordCloseIntent({
|
|
203
|
+
reason: "activate",
|
|
204
|
+
event
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
const handlers = useRef({
|
|
208
|
+
onBeforeToggle,
|
|
209
|
+
onToggle,
|
|
210
|
+
onCommand
|
|
211
|
+
});
|
|
212
|
+
useInsertionEffect(() => {
|
|
213
|
+
handlers.current = {
|
|
214
|
+
onBeforeToggle,
|
|
215
|
+
onToggle,
|
|
216
|
+
onCommand
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
const setElement = useCallback((element) => {
|
|
220
|
+
if (isDev && element.tagName !== "DIALOG" && element.tagName !== "DIV") console.error(`${part}: expected to render a <dialog> or a <div> but got <${element.tagName.toLowerCase()}>.`);
|
|
221
|
+
popupRef.current = element;
|
|
222
|
+
const listeners = [
|
|
223
|
+
["beforetoggle", (event) => handlers.current.onBeforeToggle(event)],
|
|
224
|
+
["toggle", (event) => handlers.current.onToggle(event)],
|
|
225
|
+
["command", (event) => handlers.current.onCommand(event)]
|
|
226
|
+
];
|
|
227
|
+
for (const [type, listener] of listeners) element.addEventListener(type, listener);
|
|
228
|
+
let autofocused = null;
|
|
229
|
+
if (!isLibrary && initialFocus !== void 0) {
|
|
230
|
+
const target = resolveInitialFocus(element, initialFocus);
|
|
231
|
+
if (target !== null && element.contains(target) && !target.hasAttribute("autofocus")) {
|
|
232
|
+
target.setAttribute("autofocus", "");
|
|
233
|
+
autofocused = target;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return () => {
|
|
237
|
+
for (const [type, listener] of listeners) element.removeEventListener(type, listener);
|
|
238
|
+
autofocused?.removeAttribute("autofocus");
|
|
239
|
+
if (popupRef.current === element) popupRef.current = null;
|
|
240
|
+
};
|
|
241
|
+
}, [
|
|
242
|
+
part,
|
|
243
|
+
popupRef,
|
|
244
|
+
isLibrary,
|
|
245
|
+
initialFocus
|
|
246
|
+
]);
|
|
247
|
+
const shown = !isLibrary || ctx.committed;
|
|
248
|
+
const warnedName = useRef(false);
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
const element = popupRef.current;
|
|
251
|
+
if (!isDev || element === null || !shown || warnedName.current) return;
|
|
252
|
+
if (element.hasAttribute("aria-label")) return;
|
|
253
|
+
const labelledBy = (element.getAttribute("aria-labelledby") ?? "").split(/\s+/);
|
|
254
|
+
const doc = element.ownerDocument;
|
|
255
|
+
if (labelledBy.some((id) => id !== "" && doc.getElementById(id) !== null)) return;
|
|
256
|
+
const role = element.getAttribute("role");
|
|
257
|
+
if (role !== "dialog" && role !== "alertdialog" && role !== "menu") return;
|
|
258
|
+
warnedName.current = true;
|
|
259
|
+
console.error(`${part}: the popup has no accessible name. Render a Title, or give the Popup an \`aria-label\`.`);
|
|
260
|
+
}, [
|
|
261
|
+
popupRef,
|
|
262
|
+
shown,
|
|
263
|
+
part
|
|
264
|
+
]);
|
|
265
|
+
return {
|
|
266
|
+
setElement,
|
|
267
|
+
visible: ctx.committed || exit.held,
|
|
268
|
+
wrapContent
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
//#endregion
|
|
272
|
+
export { usePopoverPopup };
|
|
273
|
+
|
|
274
|
+
//# sourceMappingURL=use-popover-popup.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-popover-popup.mjs","names":[],"sources":["../../src/internal/use-popover-popup.ts"],"sourcesContent":["\"use client\";\nimport {\n createElement,\n useCallback,\n useEffect,\n useEffectEvent,\n useInsertionEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport type { ReactNode, RefObject, ViewTransitionClass } from \"react\";\nimport { exitAnimations, useExitAnimation } from \"./use-exit-animation.js\";\nimport type { PopoverRootValue } from \"./use-popover-root.js\";\n\nexport interface UsePopoverPopupOptions {\n /** Public name of the part for development warnings (`\"Popover.Popup\"`). */\n readonly part: string;\n /** Element to focus on open: a ref, or a selector resolved inside the popup (design 6.1). */\n readonly initialFocus: RefObject<HTMLElement | null> | string | undefined;\n /**\n * Replaces `initialFocus` with a function called right before each library open (Menu: the\n * first or last item by open cause). `null` leaves the choice to the platform. A\n * `QuietFocusTarget` asks for focus without the focus ring (a pointer-opened menu).\n */\n readonly resolveInitialFocus?: (popup: HTMLElement) => HTMLElement | QuietFocusTarget | null;\n readonly keepMounted: boolean | undefined;\n /** The consumer's `transition`; decides how a library-driven close keeps its content (4.4). */\n readonly transition: ViewTransitionClass | undefined;\n /** Runs after the library's own `beforetoggle` handling (Menu: focus bookkeeping). */\n readonly onBeforeToggle?: (event: ToggleEvent) => void;\n /** Runs after the library's own `toggle` handling (Menu: close the stack on Tab-out). */\n readonly onToggle?: (event: ToggleEvent) => void;\n}\n\n/**\n * An initial focus target opened by a pointer: where the engine reads `focus({ focusVisible })`,\n * the library focuses it with `focusVisible: false` right after `showPopover()` instead of\n * writing `autofocus` (WebKit shows the ring for an `autofocus` target after a click; review H1).\n * Elsewhere it is an ordinary target.\n */\nexport interface QuietFocusTarget {\n readonly element: HTMLElement;\n readonly focusVisible: false;\n}\n\nexport interface PopoverPopupHook {\n /** Callback ref for the popup element; pass as `useRender`'s `ref`. */\n readonly setElement: (element: HTMLElement) => () => void;\n /**\n * Whether the children are shown: committed open, or held while the hide animates (4.4).\n * Library mode only; JS-free mode always renders them.\n */\n readonly visible: boolean;\n /**\n * Wraps the library-mode content: call it only while `visible` (`visible ? wrapContent(x) :\n * null`), or inside the `<Activity>` for `keepMounted`. When a close commit unmounts the\n * content while it holds focus (a library-driven close with `transition`, where the focused\n * element is gone before `hidePopover()` runs and the platform restores nothing), the hide\n * that follows returns focus to the Trigger (design 6.2).\n */\n readonly wrapContent: (children: ReactNode) => ReactNode;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction isPopoverOpen(element: Element): boolean {\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\nlet focusVisibleOption: boolean | undefined;\n\n/**\n * The engine reads the `focusVisible` member of `focus()`'s options (Chromium 145, Firefox 104,\n * Safari 18.4). Detected once through a getter: the bindings convert the dictionary before the\n * method runs, so a detached element is enough and nothing is focused.\n */\nfunction supportsFocusVisibleOption(doc: Document): boolean {\n if (focusVisibleOption === undefined) {\n let read = false;\n try {\n doc.createElement(\"div\").focus({\n get focusVisible() {\n read = true;\n return false;\n },\n } as FocusOptions);\n } catch {\n read = false;\n }\n focusVisibleOption = read;\n }\n return focusVisibleOption;\n}\n\n/** Resolves `initialFocus` against the popup (a ref, or a selector inside the popup). */\nfunction resolveInitialFocus(\n popup: HTMLElement,\n initialFocus: RefObject<HTMLElement | null> | string,\n): HTMLElement | null {\n if (typeof initialFocus === \"string\") return popup.querySelector<HTMLElement>(initialFocus);\n return initialFocus.current;\n}\n\n/** Whether `element` sits inside a popover that is not shown (an outer popup that closed). */\nfunction insideHiddenPopover(element: Element): boolean {\n for (let node = element.parentElement; node !== null; node = node.parentElement) {\n if (node.hasAttribute(\"popover\") && !isPopoverOpen(node)) return true;\n }\n return false;\n}\n\n/**\n * Records, from its layout-effect cleanup, whether focus is inside the popup when the content\n * goes away: React runs a deleted component's layout cleanups before it detaches the host nodes,\n * so `document.activeElement` still names the focused element there.\n */\nfunction ContentFocusProbe({\n onDetach,\n children,\n}: {\n onDetach: () => void;\n children?: ReactNode;\n}): ReactNode {\n useLayoutEffect(() => onDetach, [onDetach]);\n return children;\n}\n\n/**\n * Everything in a popover-like Popup except rendering (design/components/popover.md 14.2): mount\n * reconciliation, the apply effect, the native `beforetoggle` / `toggle` / `command` listeners,\n * close intents, the focus-return fallback, the JS-free `aria-expanded` sync, the deferred\n * unmount while a hide animates, and `initialFocus`. Shared by Popover and Menu.\n */\nexport function usePopoverPopup(\n ctx: PopoverRootValue,\n options: UsePopoverPopupOptions,\n): PopoverPopupHook {\n const { part, initialFocus, transition, onBeforeToggle: onBeforeToggleOption } = options;\n const onToggleOption = options.onToggle;\n const isLibrary = ctx.mode === \"library\";\n const { popupRef, triggerRef } = ctx;\n\n // The opening `beforetoggle` lets this one through: the library's own `showPopover()`.\n const opening = useRef(false);\n // Set by the synchronous `beforetoggle` of a close, cleared by `toggle` a task later: the\n // popup is hidden but its `toggle` has not arrived, so the apply effect must not re-open it\n // from a commit that lands in between (design 2.5).\n const awaitingClose = useRef(false);\n // Whether focus was inside the popup when its last close began (design 6.2 rule 1).\n const focusWasInside = useRef(false);\n // Whether the content unmounted while holding focus since the popup was last shown (H1).\n const contentHadFocus = useRef(false);\n const warnedFocus = useRef(false);\n\n // --- `held`: children stay mounted while the hide animates (design 4.4) -------------------\n\n const exit = useExitAnimation();\n\n // A library-driven close commits `false` first and hides in the layout effect, so without\n // `transition` the children are held through the close commit (\"previous value in state\").\n // With `transition` the close commit unmounts them inside the view transition, whose old\n // snapshot carries the content out.\n const [prevCommitted, setPrevCommitted] = useState(ctx.committed);\n if (prevCommitted !== ctx.committed) {\n setPrevCommitted(ctx.committed);\n if (isLibrary && prevCommitted && transition === undefined) exit.holdDuringRender();\n }\n\n const recordContentFocus = useCallback(() => {\n const element = popupRef.current;\n if (element === null) return;\n const active = element.ownerDocument.activeElement;\n if (active !== null && active !== element && element.contains(active)) {\n contentHadFocus.current = true;\n }\n }, [popupRef]);\n\n const wrapContent = useCallback(\n (children: ReactNode): ReactNode =>\n createElement(ContentFocusProbe, { onDetach: recordContentFocus }, children),\n [recordContentFocus],\n );\n\n const syncExpanded = (element: HTMLElement): void => {\n // JS-free mode only (design 9): read the DOM, not `newState`, so a stale task cannot lie.\n triggerRef.current?.setAttribute(\"aria-expanded\", String(isPopoverOpen(element)));\n };\n\n /**\n * Design 6.2 rule 2, for a close that began with focus inside: when focus is now on `<body>`\n * or still inside the hidden popup, focus the Trigger (connected, not inside a hidden\n * popover), else blur what holds focus.\n */\n const returnFocus = (element: HTMLElement): void => {\n const doc = element.ownerDocument;\n const active = doc.activeElement;\n if (active !== null && active !== doc.body && !element.contains(active)) return;\n const trigger = triggerRef.current;\n if (trigger !== null && trigger.isConnected && !insideHiddenPopover(trigger)) {\n trigger.focus();\n } else if (active instanceof HTMLElement && active !== doc.body) {\n active.blur();\n }\n };\n\n // --- Opening ------------------------------------------------------------------------------\n\n const resolveFocusTarget = (element: HTMLElement): HTMLElement | QuietFocusTarget | null => {\n if (options.resolveInitialFocus !== undefined) {\n const resolved = options.resolveInitialFocus(element);\n if (resolved === null) return null;\n const target = resolved instanceof HTMLElement ? resolved : resolved.element;\n return element.contains(target) ? resolved : null;\n }\n if (initialFocus === undefined) return null;\n const target = resolveInitialFocus(element, initialFocus);\n if (target !== null && element.contains(target)) return target;\n if (isDev && !warnedFocus.current) {\n warnedFocus.current = true;\n console.error(\n target === null\n ? `${part}: \\`initialFocus\\` resolved to no element; the platform picks the focus target.`\n : `${part}: \\`initialFocus\\` is outside the popup; the platform picks the focus target.`,\n );\n }\n return null;\n };\n\n const showPopup = (element: HTMLElement): void => {\n const resolved = resolveFocusTarget(element);\n const target =\n resolved === null || resolved instanceof HTMLElement ? resolved : resolved.element;\n // A pointer open asks for no focus ring: `focus({ focusVisible: false })` after the show,\n // where the engine reads that option (QuietFocusTarget).\n const quiet =\n target !== null &&\n !(resolved instanceof HTMLElement) &&\n supportsFocusVisibleOption(element.ownerDocument);\n // Otherwise the popover focusing steps honour `autofocus` (and run the dialog focusing steps\n // for a <dialog>, in every engine); React drops `autoFocus` on client-rendered content.\n // Never `.focus()` (6.1) except for the quiet target above.\n const added = target !== null && !quiet && !target.hasAttribute(\"autofocus\");\n if (added) target.setAttribute(\"autofocus\", \"\");\n opening.current = true;\n try {\n const source = triggerRef.current;\n element.showPopover(source !== null && source.isConnected ? { source } : undefined);\n } finally {\n opening.current = false;\n if (added) target.removeAttribute(\"autofocus\");\n }\n if (quiet && isPopoverOpen(element) && element.contains(target)) {\n target.focus({ focusVisible: false } as FocusOptions);\n }\n };\n\n // Mount reconciliation (design 2.4): a click before hydration opened the popover natively.\n // Runs before the apply effect; the dispatch makes the apply effect wait for it.\n const reconcileOnMount = useEffectEvent(() => {\n const element = popupRef.current;\n if (element === null) return;\n if (!isLibrary) {\n syncExpanded(element);\n return;\n }\n if (isPopoverOpen(element)) ctx.requestOpen(undefined, \"platform\");\n });\n useLayoutEffect(() => {\n reconcileOnMount();\n }, []);\n\n // The apply effect (design 2.5): after every commit, once nothing is in flight, the DOM\n // follows committed state. One effect covers the normal open, `defaultOpen`, a controlled open\n // or close, a library-driven close after `openChangeAction(false)` settled, the re-open of a\n // refused dismissal, and the close after a refused or failed pre-hydration open.\n useLayoutEffect(() => {\n if (!isLibrary) return;\n const element = popupRef.current;\n if (element === null || !element.isConnected) return;\n if (!ctx.pending && ctx.latestInFlight() === null) {\n const shown = isPopoverOpen(element);\n if (ctx.committed && !shown) {\n if (!awaitingClose.current) showPopup(element);\n } else if (!ctx.committed && shown) {\n // An echo: the `toggle` listener finds `false` intended and skips the dispatch.\n ctx.recordCloseIntent(undefined);\n element.hidePopover();\n // The close commit may have unmounted the focused content before the hide (H1): the\n // platform restores focus only from inside the popover, so do it now.\n if (focusWasInside.current) returnFocus(element);\n }\n }\n // Content that unmounted while the popup stays open (not a close): forget it.\n if (ctx.committed && isPopoverOpen(element)) contentHadFocus.current = false;\n // A hold the close commit set (or a dismissal's notification commit) that no hide animation\n // is going to release: the popup is already hidden and its `toggle` has been handled.\n if (\n exit.held &&\n !ctx.committed &&\n !exit.waiting() &&\n !awaitingClose.current &&\n !isPopoverOpen(element)\n ) {\n exit.release();\n }\n });\n\n // --- Native listeners (conventions 7.4; filtered on target: React 19.3.1 emulates bubbling\n // of `toggle`, and a nested popover's events reach this element through the real DOM too) ---\n\n const onBeforeToggle = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element) return;\n const toggle = event as ToggleEvent;\n if (toggle.newState === \"open\") {\n // Every open the library did not start itself is routed through the Root (design 2.2):\n // a foreign `show-popover` / `toggle-popover` invoker, a script `showPopover()`.\n if (isLibrary && !opening.current) {\n event.preventDefault();\n ctx.requestOpen(event, \"activate\");\n } else {\n // A Close action that resolves after this open must not close it (design 3.4).\n ctx.nextOpenGeneration();\n }\n contentHadFocus.current = false;\n } else {\n awaitingClose.current = true;\n const active = element.ownerDocument.activeElement;\n focusWasInside.current =\n (active !== null && element.contains(active)) || contentHadFocus.current;\n contentHadFocus.current = false;\n }\n onBeforeToggleOption?.(toggle);\n };\n\n const onToggle = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element) return;\n awaitingClose.current = false;\n if (!isLibrary) syncExpanded(element);\n\n if (!isPopoverOpen(element)) {\n // Focus return for what the platform leaves out (design 6.2): a nested popover restores\n // nothing, and an opener that is gone leaves focus inside the hidden popup.\n const wasInside = focusWasInside.current;\n focusWasInside.current = false;\n if (wasInside) returnFocus(element);\n\n const intent = ctx.takeCloseIntent();\n if (isLibrary) {\n // `false` already intended: the library's own hide (or a dismissal during a pending\n // part-initiated close). With `transition` such a close needs no hold (4.4).\n const echo = !ctx.intended();\n if (!echo || transition === undefined) {\n const animations = exitAnimations(element);\n if (animations.length > 0) {\n // Before the notification, so its commit keeps the children (urgent, internal).\n exit.holdUntil(animations);\n } else {\n // Nothing animates (Firefox, or no CSS exit): release a hold now; the commit is\n // invisible, the popup is already `display: none`.\n exit.release();\n }\n }\n ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? \"dismiss\");\n }\n }\n onToggleOption?.(event as ToggleEvent);\n };\n\n const onCommand = (event: Event): void => {\n const element = popupRef.current;\n if (element === null || event.target !== element || event.defaultPrevented) return;\n const command = String((event as Event & { command?: string }).command ?? \"\").toLowerCase();\n // A foreign invoker closing the popup (design 2.3). Opens are caught at `beforetoggle`.\n if (command === \"hide-popover\" || (command === \"toggle-popover\" && isPopoverOpen(element))) {\n ctx.recordCloseIntent({ reason: \"activate\", event });\n }\n };\n\n // The listeners the callback ref attaches call the latest render's handlers. An insertion\n // effect publishes them before refs attach and before any layout effect (whose\n // `hidePopover()` dispatches `beforetoggle` synchronously).\n const handlers = useRef({ onBeforeToggle, onToggle, onCommand });\n useInsertionEffect(() => {\n handlers.current = { onBeforeToggle, onToggle, onCommand };\n });\n\n // --- Element ------------------------------------------------------------------------------\n\n // The listeners attach in the callback ref, so they are in place from the commit that mounts\n // the element (no passive-effect window, no extra render). JS-free mode: there is no \"right\n // before showPopover()\" (the browser runs the command), so the `autofocus` attribute stays on\n // the `initialFocus` target while it is mounted (as Dialog).\n const setElement = useCallback(\n (element: HTMLElement) => {\n if (isDev && element.tagName !== \"DIALOG\" && element.tagName !== \"DIV\") {\n console.error(\n `${part}: expected to render a <dialog> or a <div> but got ` +\n `<${element.tagName.toLowerCase()}>.`,\n );\n }\n popupRef.current = element;\n const listeners: [string, EventListener][] = [\n [\"beforetoggle\", (event) => handlers.current.onBeforeToggle(event)],\n [\"toggle\", (event) => handlers.current.onToggle(event)],\n [\"command\", (event) => handlers.current.onCommand(event)],\n ];\n for (const [type, listener] of listeners) element.addEventListener(type, listener);\n\n let autofocused: HTMLElement | null = null;\n if (!isLibrary && initialFocus !== undefined) {\n const target = resolveInitialFocus(element, initialFocus);\n if (target !== null && element.contains(target) && !target.hasAttribute(\"autofocus\")) {\n target.setAttribute(\"autofocus\", \"\");\n autofocused = target;\n }\n }\n\n return () => {\n for (const [type, listener] of listeners) element.removeEventListener(type, listener);\n autofocused?.removeAttribute(\"autofocus\");\n if (popupRef.current === element) popupRef.current = null;\n };\n },\n [part, popupRef, isLibrary, initialFocus],\n );\n\n // Development check (design 9): `role=\"dialog\"` needs a name once the popup is (or can be)\n // shown.\n const shown = !isLibrary || ctx.committed;\n const warnedName = useRef(false);\n useEffect(() => {\n const element = popupRef.current;\n if (!isDev || element === null || !shown || warnedName.current) return;\n if (element.hasAttribute(\"aria-label\")) return;\n const labelledBy = (element.getAttribute(\"aria-labelledby\") ?? \"\").split(/\\s+/);\n const doc = element.ownerDocument;\n if (labelledBy.some((id) => id !== \"\" && doc.getElementById(id) !== null)) return;\n const role = element.getAttribute(\"role\");\n if (role !== \"dialog\" && role !== \"alertdialog\" && role !== \"menu\") return;\n warnedName.current = true;\n console.error(\n `${part}: the popup has no accessible name. Render a Title, or give the Popup an ` +\n \"`aria-label`.\",\n );\n }, [popupRef, shown, part]);\n\n return { setElement, visible: ctx.committed || exit.held, wrapContent };\n}\n"],"mappings":"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"}
|