@funstack/ui 0.1.0-alpha.0 → 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- 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/combobox/combobox-clear.d.mts +34 -0
- package/dist/combobox/combobox-clear.d.mts.map +1 -0
- package/dist/combobox/combobox-clear.mjs +58 -0
- package/dist/combobox/combobox-clear.mjs.map +1 -0
- package/dist/combobox/combobox-context.mjs +23 -0
- package/dist/combobox/combobox-context.mjs.map +1 -0
- package/dist/combobox/combobox-group.d.mts +72 -0
- package/dist/combobox/combobox-group.d.mts.map +1 -0
- package/dist/combobox/combobox-group.mjs +104 -0
- package/dist/combobox/combobox-group.mjs.map +1 -0
- package/dist/combobox/combobox-input.d.mts +40 -0
- package/dist/combobox/combobox-input.d.mts.map +1 -0
- package/dist/combobox/combobox-input.mjs +88 -0
- package/dist/combobox/combobox-input.mjs.map +1 -0
- package/dist/combobox/combobox-label.d.mts +22 -0
- package/dist/combobox/combobox-label.d.mts.map +1 -0
- package/dist/combobox/combobox-label.mjs +32 -0
- package/dist/combobox/combobox-label.mjs.map +1 -0
- package/dist/combobox/combobox-list.d.mts +37 -0
- package/dist/combobox/combobox-list.d.mts.map +1 -0
- package/dist/combobox/combobox-list.mjs +70 -0
- package/dist/combobox/combobox-list.mjs.map +1 -0
- package/dist/combobox/combobox-option.d.mts +45 -0
- package/dist/combobox/combobox-option.d.mts.map +1 -0
- package/dist/combobox/combobox-option.mjs +88 -0
- package/dist/combobox/combobox-option.mjs.map +1 -0
- package/dist/combobox/combobox-popup.d.mts +49 -0
- package/dist/combobox/combobox-popup.d.mts.map +1 -0
- package/dist/combobox/combobox-popup.mjs +86 -0
- package/dist/combobox/combobox-popup.mjs.map +1 -0
- package/dist/combobox/combobox-root.d.mts +135 -0
- package/dist/combobox/combobox-root.d.mts.map +1 -0
- package/dist/combobox/combobox-root.mjs +508 -0
- package/dist/combobox/combobox-root.mjs.map +1 -0
- package/dist/combobox/combobox-trigger.d.mts +36 -0
- package/dist/combobox/combobox-trigger.d.mts.map +1 -0
- package/dist/combobox/combobox-trigger.mjs +92 -0
- package/dist/combobox/combobox-trigger.mjs.map +1 -0
- package/dist/combobox/index.d.mts +36 -0
- package/dist/combobox/index.d.mts.map +1 -0
- package/dist/combobox/index.mjs +36 -0
- package/dist/combobox/index.mjs.map +1 -0
- package/dist/dialog/dialog-close.d.mts.map +1 -1
- package/dist/dialog/dialog-close.mjs +4 -4
- package/dist/dialog/dialog-close.mjs.map +1 -1
- package/dist/dialog/dialog-context.mjs.map +1 -1
- package/dist/dialog/dialog-popup.mjs +1 -0
- package/dist/dialog/dialog-popup.mjs.map +1 -1
- package/dist/dialog/dialog-root.mjs +9 -1
- package/dist/dialog/dialog-root.mjs.map +1 -1
- package/dist/index.d.mts +42 -2
- package/dist/index.mjs +6 -1
- 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/platform-attributes.d.mts +18 -1
- package/dist/internal/platform-attributes.d.mts.map +1 -1
- package/dist/internal/sync-presence.mjs +19 -0
- package/dist/internal/sync-presence.mjs.map +1 -0
- package/dist/internal/types.d.mts +8 -0
- package/dist/internal/types.d.mts.map +1 -1
- 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 +5 -1
- package/dist/internal/use-controllable-action.mjs.map +1 -1
- package/dist/internal/use-delayed-pair.mjs +40 -0
- package/dist/internal/use-delayed-pair.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 +5 -1
- package/dist/internal/use-form-reset.mjs.map +1 -1
- package/dist/internal/use-hydrated-mount.mjs +21 -0
- package/dist/internal/use-hydrated-mount.mjs.map +1 -0
- package/dist/internal/use-native-group-reconciliation.mjs +2 -2
- package/dist/internal/use-native-group-reconciliation.mjs.map +1 -1
- package/dist/internal/use-native-select-value.mjs +182 -0
- package/dist/internal/use-native-select-value.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/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/radio-group-item.d.mts.map +1 -1
- package/dist/radio-group/radio-group-item.mjs +1 -6
- package/dist/radio-group/radio-group-item.mjs.map +1 -1
- package/dist/select/index.d.mts +30 -0
- package/dist/select/index.d.mts.map +1 -0
- package/dist/select/index.mjs +30 -0
- package/dist/select/index.mjs.map +1 -0
- package/dist/select/select-context.mjs +13 -0
- package/dist/select/select-context.mjs.map +1 -0
- package/dist/select/select-dom.mjs +44 -0
- package/dist/select/select-dom.mjs.map +1 -0
- package/dist/select/select-group-label.d.mts +22 -0
- package/dist/select/select-group-label.d.mts.map +1 -0
- package/dist/select/select-group-label.mjs +39 -0
- package/dist/select/select-group-label.mjs.map +1 -0
- package/dist/select/select-group.d.mts +28 -0
- package/dist/select/select-group.d.mts.map +1 -0
- package/dist/select/select-group.mjs +45 -0
- package/dist/select/select-group.mjs.map +1 -0
- package/dist/select/select-option.d.mts +42 -0
- package/dist/select/select-option.d.mts.map +1 -0
- package/dist/select/select-option.mjs +65 -0
- package/dist/select/select-option.mjs.map +1 -0
- package/dist/select/select-root.d.mts +66 -0
- package/dist/select/select-root.d.mts.map +1 -0
- package/dist/select/select-root.mjs +94 -0
- package/dist/select/select-root.mjs.map +1 -0
- package/dist/select/select-separator.d.mts +20 -0
- package/dist/select/select-separator.d.mts.map +1 -0
- package/dist/select/select-separator.mjs +30 -0
- package/dist/select/select-separator.mjs.map +1 -0
- package/dist/select/select-trigger.d.mts +29 -0
- package/dist/select/select-trigger.d.mts.map +1 -0
- package/dist/select/select-trigger.mjs +48 -0
- package/dist/select/select-trigger.mjs.map +1 -0
- package/dist/select/select-value.d.mts +31 -0
- package/dist/select/select-value.d.mts.map +1 -0
- package/dist/select/select-value.mjs +101 -0
- package/dist/select/select-value.mjs.map +1 -0
- package/dist/tabs/tabs-context.mjs +2 -1
- package/dist/tabs/tabs-context.mjs.map +1 -1
- package/dist/tabs/tabs-list.d.mts +1 -1
- package/dist/tabs/tabs-list.d.mts.map +1 -1
- package/dist/tabs/tabs-list.mjs +17 -9
- package/dist/tabs/tabs-list.mjs.map +1 -1
- package/dist/tabs/tabs-tab.d.mts +1 -1
- package/dist/tabs/tabs-tab.d.mts.map +1 -1
- package/dist/tabs/tabs-tab.mjs +6 -3
- package/dist/tabs/tabs-tab.mjs.map +1 -1
- package/dist/tooltip/tooltip-popup.d.mts.map +1 -1
- package/dist/tooltip/tooltip-popup.mjs +2 -7
- package/dist/tooltip/tooltip-popup.mjs.map +1 -1
- package/dist/tooltip/use-tooltip-interaction.mjs +5 -17
- package/dist/tooltip/use-tooltip-interaction.mjs.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { dataAttr } from "../internal/data-attr.mjs";
|
|
3
|
+
import { rendersButton, useRender } from "../internal/use-render.mjs";
|
|
4
|
+
import { useComboboxContext } from "./combobox-context.mjs";
|
|
5
|
+
import { useCallback, useRef } from "react";
|
|
6
|
+
//#region src/combobox/combobox-trigger.tsx
|
|
7
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
8
|
+
/**
|
|
9
|
+
* Development check (design 9): an icon-only Trigger needs a name from the consumer. Checked a
|
|
10
|
+
* microtask after the element attaches, once per instance.
|
|
11
|
+
*/
|
|
12
|
+
function useNameWarning(part) {
|
|
13
|
+
const warned = useRef(false);
|
|
14
|
+
return useCallback((element) => {
|
|
15
|
+
if (!isDev || warned.current) return () => {};
|
|
16
|
+
queueMicrotask(() => {
|
|
17
|
+
if (warned.current || !element.isConnected) return;
|
|
18
|
+
if (element.hasAttribute("aria-label") || element.hasAttribute("aria-labelledby")) return;
|
|
19
|
+
if ((element.textContent ?? "").trim() !== "" || element.hasAttribute("title")) return;
|
|
20
|
+
warned.current = true;
|
|
21
|
+
console.error(`${part}: the button has no accessible name. Give it an \`aria-label\`.`);
|
|
22
|
+
});
|
|
23
|
+
return () => {};
|
|
24
|
+
}, [part]);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The optional button that toggles the listbox: `<button type="button" tabindex="-1"
|
|
28
|
+
* aria-expanded aria-controls>` (the APG keeps it out of the Tab sequence; the input is the one
|
|
29
|
+
* stop). A press does not take focus from the input, and a click focuses the input. No
|
|
30
|
+
* `commandfor`: the listbox is anchored to the input, never to this button (design 1).
|
|
31
|
+
*
|
|
32
|
+
* Owned keys: `type` (on a `<button>`), `tabIndex`, `disabled`, `aria-expanded`,
|
|
33
|
+
* `aria-controls` (token-merged), `onClick`, `onPointerDown`, `onPointerCancel`, `onMouseDown`,
|
|
34
|
+
* `data-open`, `data-pending`, `data-disabled`.
|
|
35
|
+
*/
|
|
36
|
+
function ComboboxTrigger({ disabled: ownDisabled = false, className, style, children, render, ...rest }) {
|
|
37
|
+
const ctx = useComboboxContext("Combobox.Trigger");
|
|
38
|
+
const { popover } = ctx;
|
|
39
|
+
const disabled = ownDisabled || ctx.disabled;
|
|
40
|
+
const openAtPress = useRef(null);
|
|
41
|
+
const ref = useNameWarning("Combobox.Trigger");
|
|
42
|
+
const state = {
|
|
43
|
+
open: popover.optimistic,
|
|
44
|
+
pending: popover.pending,
|
|
45
|
+
disabled
|
|
46
|
+
};
|
|
47
|
+
const libraryProps = {
|
|
48
|
+
...rendersButton(render) && { type: "button" },
|
|
49
|
+
tabIndex: -1,
|
|
50
|
+
disabled,
|
|
51
|
+
"aria-expanded": popover.optimistic,
|
|
52
|
+
"aria-controls": ctx.ids.list,
|
|
53
|
+
onPointerDown: (event) => {
|
|
54
|
+
openAtPress.current = popover.intended();
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
},
|
|
57
|
+
onPointerCancel: () => {
|
|
58
|
+
openAtPress.current = null;
|
|
59
|
+
},
|
|
60
|
+
onMouseDown: (event) => {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
},
|
|
63
|
+
onClick: (event) => {
|
|
64
|
+
const wasOpen = (event.detail === 0 ? null : openAtPress.current) ?? popover.intended();
|
|
65
|
+
openAtPress.current = null;
|
|
66
|
+
if (disabled) return;
|
|
67
|
+
const native = event.nativeEvent;
|
|
68
|
+
if (wasOpen) popover.requestClose(native, "activate");
|
|
69
|
+
else popover.requestOpen(native, "activate");
|
|
70
|
+
ctx.inputRef.current?.focus();
|
|
71
|
+
},
|
|
72
|
+
"data-open": dataAttr(popover.optimistic),
|
|
73
|
+
"data-pending": dataAttr(popover.pending),
|
|
74
|
+
"data-disabled": dataAttr(disabled)
|
|
75
|
+
};
|
|
76
|
+
return useRender({
|
|
77
|
+
defaultElement: "button",
|
|
78
|
+
render,
|
|
79
|
+
state,
|
|
80
|
+
className,
|
|
81
|
+
style,
|
|
82
|
+
children,
|
|
83
|
+
props: rest,
|
|
84
|
+
libraryProps,
|
|
85
|
+
ref
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
ComboboxTrigger.displayName = "ComboboxTrigger";
|
|
89
|
+
//#endregion
|
|
90
|
+
export { ComboboxTrigger, useNameWarning };
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=combobox-trigger.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combobox-trigger.mjs","names":[],"sources":["../../src/combobox/combobox-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useRef } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n PointerEvent,\n ReactNode,\n} from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useComboboxContext } from \"./combobox-context.js\";\n\nexport interface ComboboxTriggerState {\n /** Optimistic open state. */\n readonly open: boolean;\n /** The open (or close) transition is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface ComboboxTriggerProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n | \"onClick\"\n | \"onPointerDown\"\n | \"onMouseDown\"\n | \"onPointerCancel\"\n | \"tabIndex\"\n | \"aria-expanded\"\n | \"aria-controls\"\n | \"disabled\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /** Disables this button; the Root's `disabled` does too. */\n disabled?: boolean;\n className?: StateValue<string | undefined, ComboboxTriggerState>;\n style?: StateValue<CSSProperties | undefined, ComboboxTriggerState>;\n /** Rendered from optimistic state; must not suspend. */\n children?: StateValue<ReactNode, ComboboxTriggerState>;\n /** Should render a `<button>`. */\n render?: RenderProp<ComponentPropsWithRef<\"button\">, ComboboxTriggerState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * Development check (design 9): an icon-only Trigger needs a name from the consumer. Checked a\n * microtask after the element attaches, once per instance.\n */\nexport function useNameWarning(part: string): (element: HTMLElement) => () => void {\n const warned = useRef(false);\n return useCallback(\n (element: HTMLElement) => {\n if (!isDev || warned.current) return () => {};\n queueMicrotask(() => {\n if (warned.current || !element.isConnected) return;\n if (element.hasAttribute(\"aria-label\") || element.hasAttribute(\"aria-labelledby\")) return;\n if ((element.textContent ?? \"\").trim() !== \"\" || element.hasAttribute(\"title\")) return;\n warned.current = true;\n console.error(`${part}: the button has no accessible name. Give it an \\`aria-label\\`.`);\n });\n return () => {};\n },\n [part],\n );\n}\n\n/**\n * The optional button that toggles the listbox: `<button type=\"button\" tabindex=\"-1\"\n * aria-expanded aria-controls>` (the APG keeps it out of the Tab sequence; the input is the one\n * stop). A press does not take focus from the input, and a click focuses the input. No\n * `commandfor`: the listbox is anchored to the input, never to this button (design 1).\n *\n * Owned keys: `type` (on a `<button>`), `tabIndex`, `disabled`, `aria-expanded`,\n * `aria-controls` (token-merged), `onClick`, `onPointerDown`, `onPointerCancel`, `onMouseDown`,\n * `data-open`, `data-pending`, `data-disabled`.\n */\nexport function ComboboxTrigger({\n disabled: ownDisabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: ComboboxTriggerProps) {\n const ctx = useComboboxContext(\"Combobox.Trigger\");\n const { popover } = ctx;\n const disabled = ownDisabled || ctx.disabled;\n // What the user saw at the press: a press outside the popup light-dismisses it before the\n // click arrives, so the toggle is decided against the state at `pointerdown`.\n const openAtPress = useRef<boolean | null>(null);\n const ref = useNameWarning(\"Combobox.Trigger\");\n\n const state: ComboboxTriggerState = {\n open: popover.optimistic,\n pending: popover.pending,\n disabled,\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n tabIndex: -1,\n disabled,\n \"aria-expanded\": popover.optimistic,\n \"aria-controls\": ctx.ids.list,\n onPointerDown: (event: PointerEvent<HTMLButtonElement>) => {\n openAtPress.current = popover.intended();\n event.preventDefault();\n },\n onPointerCancel: () => {\n // The press never becomes a click.\n openAtPress.current = null;\n },\n onMouseDown: (event: MouseEvent<HTMLButtonElement>) => {\n event.preventDefault();\n },\n onClick: (event: MouseEvent<HTMLButtonElement>) => {\n // `detail === 0`: a click without a press (assistive technology, `element.click()`); a\n // snapshot left by a press that never clicked (dragged off) does not belong to it.\n const pressed = event.detail === 0 ? null : openAtPress.current;\n const wasOpen = pressed ?? popover.intended();\n openAtPress.current = null;\n if (disabled) return;\n const native = event.nativeEvent;\n if (wasOpen) popover.requestClose(native, \"activate\");\n else popover.requestOpen(native, \"activate\");\n ctx.inputRef.current?.focus();\n },\n \"data-open\": dataAttr(popover.optimistic),\n \"data-pending\": dataAttr(popover.pending),\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, ComboboxTriggerState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref,\n });\n}\n\nComboboxTrigger.displayName = \"ComboboxTrigger\";\n"],"mappings":";;;;;;AA+CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;AAMvC,SAAgB,eAAe,MAAoD;CACjF,MAAM,SAAS,OAAO,KAAK;CAC3B,OAAO,aACJ,YAAyB;EACxB,IAAI,CAAC,SAAS,OAAO,SAAS,aAAa,CAAC;EAC5C,qBAAqB;GACnB,IAAI,OAAO,WAAW,CAAC,QAAQ,aAAa;GAC5C,IAAI,QAAQ,aAAa,YAAY,KAAK,QAAQ,aAAa,iBAAiB,GAAG;GACnF,KAAK,QAAQ,eAAe,GAAA,CAAI,KAAK,MAAM,MAAM,QAAQ,aAAa,OAAO,GAAG;GAChF,OAAO,UAAU;GACjB,QAAQ,MAAM,GAAG,KAAK,gEAAgE;EACxF,CAAC;EACD,aAAa,CAAC;CAChB,GACA,CAAC,IAAI,CACP;AACF;;;;;;;;;;;AAYA,SAAgB,gBAAgB,EAC9B,UAAU,cAAc,OACxB,WACA,OACA,UACA,QACA,GAAG,QACoB;CACvB,MAAM,MAAM,mBAAmB,kBAAkB;CACjD,MAAM,EAAE,YAAY;CACpB,MAAM,WAAW,eAAe,IAAI;CAGpC,MAAM,cAAc,OAAuB,IAAI;CAC/C,MAAM,MAAM,eAAe,kBAAkB;CAE7C,MAAM,QAA8B;EAClC,MAAM,QAAQ;EACd,SAAS,QAAQ;EACjB;CACF;CAEA,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EACvD,UAAU;EACV;EACA,iBAAiB,QAAQ;EACzB,iBAAiB,IAAI,IAAI;EACzB,gBAAgB,UAA2C;GACzD,YAAY,UAAU,QAAQ,SAAS;GACvC,MAAM,eAAe;EACvB;EACA,uBAAuB;GAErB,YAAY,UAAU;EACxB;EACA,cAAc,UAAyC;GACrD,MAAM,eAAe;EACvB;EACA,UAAU,UAAyC;GAIjD,MAAM,WADU,MAAM,WAAW,IAAI,OAAO,YAAY,YAC7B,QAAQ,SAAS;GAC5C,YAAY,UAAU;GACtB,IAAI,UAAU;GACd,MAAM,SAAS,MAAM;GACrB,IAAI,SAAS,QAAQ,aAAa,QAAQ,UAAU;QAC/C,QAAQ,YAAY,QAAQ,UAAU;GAC3C,IAAI,SAAS,SAAS,MAAM;EAC9B;EACA,aAAa,SAAS,QAAQ,UAAU;EACxC,gBAAgB,SAAS,QAAQ,OAAO;EACxC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAAiE;EACtE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA;CACF,CAAC;AACH;AAEA,gBAAgB,cAAc"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import "../internal/platform-attributes.mjs";
|
|
2
|
+
import { ComboboxClear, ComboboxClearProps, ComboboxClearState } from "./combobox-clear.mjs";
|
|
3
|
+
import { ComboboxList, ComboboxListProps, ComboboxListState } from "./combobox-list.mjs";
|
|
4
|
+
import { ComboboxEmpty, ComboboxEmptyProps, ComboboxEmptyState, ComboboxGroup, ComboboxGroupLabel, ComboboxGroupLabelProps, ComboboxGroupLabelState, ComboboxGroupProps, ComboboxGroupState, ComboboxStatus, ComboboxStatusProps, ComboboxStatusState } from "./combobox-group.mjs";
|
|
5
|
+
import { ComboboxInput, ComboboxInputProps, ComboboxInputState } from "./combobox-input.mjs";
|
|
6
|
+
import { ComboboxLabel, ComboboxLabelProps, ComboboxLabelState } from "./combobox-label.mjs";
|
|
7
|
+
import { ComboboxOption, ComboboxOptionProps, ComboboxOptionState } from "./combobox-option.mjs";
|
|
8
|
+
import { ComboboxPopup, ComboboxPopupProps, ComboboxPopupState } from "./combobox-popup.mjs";
|
|
9
|
+
import { ComboboxInputValueChangeDetails, ComboboxOpenChangeDetails, ComboboxRoot, ComboboxRootProps, ComboboxRootState, ComboboxValueChangeDetails } from "./combobox-root.mjs";
|
|
10
|
+
import { ComboboxTrigger, ComboboxTriggerProps, ComboboxTriggerState } from "./combobox-trigger.mjs";
|
|
11
|
+
//#region src/combobox/index.d.ts
|
|
12
|
+
/**
|
|
13
|
+
* Editable combobox with list autocomplete (WAI-ARIA APG) on `popover="auto"`: `Root` (no DOM,
|
|
14
|
+
* or one hidden input with `name`) > `Label`, `Input` (`input[role=combobox]`), `Trigger`,
|
|
15
|
+
* `Clear`, `Popup` (`div[popover=auto]`) > `List` (`div[role=listbox]`) > `Option`, `Group` >
|
|
16
|
+
* `GroupLabel`; `Empty` and `Status` in the Popup. Typing is urgent (`inputValueChangeAction`,
|
|
17
|
+
* the library's urgent lane); the results render from the deferred `query`; accepting an option
|
|
18
|
+
* is a transition with `data-pending` on it, and the listbox closes when it settles.
|
|
19
|
+
*/
|
|
20
|
+
export declare const Combobox: {
|
|
21
|
+
readonly Root: typeof ComboboxRoot;
|
|
22
|
+
readonly Label: typeof ComboboxLabel;
|
|
23
|
+
readonly Input: typeof ComboboxInput;
|
|
24
|
+
readonly Trigger: typeof ComboboxTrigger;
|
|
25
|
+
readonly Clear: typeof ComboboxClear;
|
|
26
|
+
readonly Popup: typeof ComboboxPopup;
|
|
27
|
+
readonly List: typeof ComboboxList;
|
|
28
|
+
readonly Option: typeof ComboboxOption;
|
|
29
|
+
readonly Group: typeof ComboboxGroup;
|
|
30
|
+
readonly GroupLabel: typeof ComboboxGroupLabel;
|
|
31
|
+
readonly Empty: typeof ComboboxEmpty;
|
|
32
|
+
readonly Status: typeof ComboboxStatus;
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
export type { ComboboxClearProps, ComboboxClearState, ComboboxEmptyProps, ComboboxEmptyState, ComboboxGroupLabelProps, ComboboxGroupLabelState, ComboboxGroupProps, ComboboxGroupState, ComboboxInputProps, ComboboxInputState, ComboboxInputValueChangeDetails, ComboboxLabelProps, ComboboxLabelState, ComboboxListProps, ComboboxListState, ComboboxOpenChangeDetails, ComboboxOptionProps, ComboboxOptionState, ComboboxPopupProps, ComboboxPopupState, ComboboxRootProps, ComboboxRootState, ComboboxStatusProps, ComboboxStatusState, ComboboxTriggerProps, ComboboxTriggerState, ComboboxValueChangeDetails };
|
|
36
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/combobox/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;qBA0Ba;WACX,aAAI;WACJ,cAAK;WACL,cAAK;WACL,gBAAO;WACP,cAAK;WACL,cAAK;WACL,aAAI;WACJ,eAAM;WACN,cAAK;WACL,mBAAU;WACV,cAAK;WACL,eAAM"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ComboboxTrigger } from "./combobox-trigger.mjs";
|
|
2
|
+
import { ComboboxClear } from "./combobox-clear.mjs";
|
|
3
|
+
import { ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxStatus } from "./combobox-group.mjs";
|
|
4
|
+
import { ComboboxInput } from "./combobox-input.mjs";
|
|
5
|
+
import { ComboboxLabel } from "./combobox-label.mjs";
|
|
6
|
+
import { ComboboxList } from "./combobox-list.mjs";
|
|
7
|
+
import { ComboboxOption } from "./combobox-option.mjs";
|
|
8
|
+
import { ComboboxPopup } from "./combobox-popup.mjs";
|
|
9
|
+
import { ComboboxRoot } from "./combobox-root.mjs";
|
|
10
|
+
//#region src/combobox/index.ts
|
|
11
|
+
/**
|
|
12
|
+
* Editable combobox with list autocomplete (WAI-ARIA APG) on `popover="auto"`: `Root` (no DOM,
|
|
13
|
+
* or one hidden input with `name`) > `Label`, `Input` (`input[role=combobox]`), `Trigger`,
|
|
14
|
+
* `Clear`, `Popup` (`div[popover=auto]`) > `List` (`div[role=listbox]`) > `Option`, `Group` >
|
|
15
|
+
* `GroupLabel`; `Empty` and `Status` in the Popup. Typing is urgent (`inputValueChangeAction`,
|
|
16
|
+
* the library's urgent lane); the results render from the deferred `query`; accepting an option
|
|
17
|
+
* is a transition with `data-pending` on it, and the listbox closes when it settles.
|
|
18
|
+
*/
|
|
19
|
+
const Combobox = {
|
|
20
|
+
Root: ComboboxRoot,
|
|
21
|
+
Label: ComboboxLabel,
|
|
22
|
+
Input: ComboboxInput,
|
|
23
|
+
Trigger: ComboboxTrigger,
|
|
24
|
+
Clear: ComboboxClear,
|
|
25
|
+
Popup: ComboboxPopup,
|
|
26
|
+
List: ComboboxList,
|
|
27
|
+
Option: ComboboxOption,
|
|
28
|
+
Group: ComboboxGroup,
|
|
29
|
+
GroupLabel: ComboboxGroupLabel,
|
|
30
|
+
Empty: ComboboxEmpty,
|
|
31
|
+
Status: ComboboxStatus
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { Combobox };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/combobox/index.ts"],"sourcesContent":["// Ships the platform attribute typings with this subpath (conventions 7.4). Type-only: the built\n// JS has no import of it.\nimport \"../internal/platform-attributes.js\";\nimport { ComboboxClear } from \"./combobox-clear.js\";\nimport {\n ComboboxEmpty,\n ComboboxGroup,\n ComboboxGroupLabel,\n ComboboxStatus,\n} from \"./combobox-group.js\";\nimport { ComboboxInput } from \"./combobox-input.js\";\nimport { ComboboxLabel } from \"./combobox-label.js\";\nimport { ComboboxList } from \"./combobox-list.js\";\nimport { ComboboxOption } from \"./combobox-option.js\";\nimport { ComboboxPopup } from \"./combobox-popup.js\";\nimport { ComboboxRoot } from \"./combobox-root.js\";\nimport { ComboboxTrigger } from \"./combobox-trigger.js\";\n\n/**\n * Editable combobox with list autocomplete (WAI-ARIA APG) on `popover=\"auto\"`: `Root` (no DOM,\n * or one hidden input with `name`) > `Label`, `Input` (`input[role=combobox]`), `Trigger`,\n * `Clear`, `Popup` (`div[popover=auto]`) > `List` (`div[role=listbox]`) > `Option`, `Group` >\n * `GroupLabel`; `Empty` and `Status` in the Popup. Typing is urgent (`inputValueChangeAction`,\n * the library's urgent lane); the results render from the deferred `query`; accepting an option\n * is a transition with `data-pending` on it, and the listbox closes when it settles.\n */\nexport const Combobox = {\n Root: ComboboxRoot,\n Label: ComboboxLabel,\n Input: ComboboxInput,\n Trigger: ComboboxTrigger,\n Clear: ComboboxClear,\n Popup: ComboboxPopup,\n List: ComboboxList,\n Option: ComboboxOption,\n Group: ComboboxGroup,\n GroupLabel: ComboboxGroupLabel,\n Empty: ComboboxEmpty,\n Status: ComboboxStatus,\n} as const;\n\nexport type {\n ComboboxRootProps,\n ComboboxRootState,\n ComboboxValueChangeDetails,\n ComboboxInputValueChangeDetails,\n ComboboxOpenChangeDetails,\n} from \"./combobox-root.js\";\nexport type { ComboboxLabelProps, ComboboxLabelState } from \"./combobox-label.js\";\nexport type { ComboboxInputProps, ComboboxInputState } from \"./combobox-input.js\";\nexport type { ComboboxTriggerProps, ComboboxTriggerState } from \"./combobox-trigger.js\";\nexport type { ComboboxClearProps, ComboboxClearState } from \"./combobox-clear.js\";\nexport type { ComboboxPopupProps, ComboboxPopupState } from \"./combobox-popup.js\";\nexport type { ComboboxListProps, ComboboxListState } from \"./combobox-list.js\";\nexport type { ComboboxOptionProps, ComboboxOptionState } from \"./combobox-option.js\";\nexport type {\n ComboboxGroupProps,\n ComboboxGroupState,\n ComboboxGroupLabelProps,\n ComboboxGroupLabelState,\n ComboboxEmptyProps,\n ComboboxEmptyState,\n ComboboxStatusProps,\n ComboboxStatusState,\n} from \"./combobox-group.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AA0BA,MAAa,WAAW;CACtB,MAAM;CACN,OAAO;CACP,OAAO;CACP,SAAS;CACT,OAAO;CACP,OAAO;CACP,MAAM;CACN,QAAQ;CACR,OAAO;CACP,YAAY;CACZ,OAAO;CACP,QAAQ;AACV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-close.d.mts","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"dialog-close.d.mts","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"mappings":";;;iBAWiB;;WAEN;WACA;;;iBAIM,2BAA2B;;WAEjC;;iBAGM,yBAAyB,KACxC;;;;;;EAQA,SAAS,QAAQ,SAAS;;EAE1B;EACA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;wBAevC,cACd,QACA,OACA,UACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBATH"}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { createActionDetails } from "../internal/action-details.mjs";
|
|
3
3
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
4
|
import { rendersButton, useRender } from "../internal/use-render.mjs";
|
|
5
|
+
import { useActionTrigger } from "../internal/use-action-trigger.mjs";
|
|
5
6
|
import { useDialogContext } from "./dialog-context.mjs";
|
|
6
|
-
import { useCallback
|
|
7
|
+
import { useCallback } from "react";
|
|
7
8
|
//#region src/dialog/dialog-close.tsx
|
|
8
9
|
const isDev = process.env.NODE_ENV !== "production";
|
|
9
10
|
/**
|
|
@@ -18,7 +19,7 @@ const isDev = process.env.NODE_ENV !== "production";
|
|
|
18
19
|
*/
|
|
19
20
|
function DialogClose({ action, value, disabled = false, className, style, children, render, ...rest }) {
|
|
20
21
|
const ctx = useDialogContext("Dialog.Close");
|
|
21
|
-
const
|
|
22
|
+
const { pending, run } = useActionTrigger(ctx.openGeneration);
|
|
22
23
|
const { ids, dialogRef, recordCloseIntent } = ctx;
|
|
23
24
|
const hasAction = action !== void 0;
|
|
24
25
|
const state = {
|
|
@@ -36,8 +37,7 @@ function DialogClose({ action, value, disabled = false, className, style, childr
|
|
|
36
37
|
const native = event.nativeEvent;
|
|
37
38
|
const returnValue = value ?? "";
|
|
38
39
|
const details = createActionDetails(native, { returnValue });
|
|
39
|
-
|
|
40
|
-
await action(details);
|
|
40
|
+
run(() => action(details), () => {
|
|
41
41
|
const dialog = dialogRef.current;
|
|
42
42
|
if (dialog === null || !dialog.open) return;
|
|
43
43
|
recordCloseIntent({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-close.mjs","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback
|
|
1
|
+
{"version":3,"file":"dialog-close.mjs","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogCloseState {\n /** The `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\n/** Details passed to `Dialog.Close`'s `action`. */\nexport interface DialogCloseDetails extends ActionDetails {\n /** The `value` the dialog closes with (`dialog.returnValue`); `\"\"` without `value`. */\n readonly returnValue: string;\n}\n\nexport interface DialogCloseProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"value\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this part's own transition, then closes the dialog with `close(value)`. A\n * rejection reaches the nearest Error Boundary above the Close part and leaves the dialog open.\n * Without `action` the button is a native `command=\"request-close\"` invoker (JS-free).\n */\n action?: Action<[details: DialogCloseDetails]>;\n /** Becomes `dialog.returnValue` (and `details.returnValue` of the close notification). */\n value?: string;\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogCloseState>;\n style?: StateValue<CSSProperties | undefined, DialogCloseState>;\n children?: StateValue<ReactNode, DialogCloseState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogCloseState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * A close button: `<button type=\"button\" commandfor command=\"request-close\">`. Without `action`\n * the browser closes the dialog (a close request: `cancel`, then `close`), with `value` as the\n * `returnValue`. With `action` the click is cancelled, the action runs in this part's own\n * transition (`data-pending` on the button) and the dialog closes with `close(value)` once it\n * resolves; a rejection leaves the dialog open.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `value`, `onClick` (with `action`), `data-pending`, `data-disabled`.\n */\nexport function DialogClose({\n action,\n value,\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogCloseProps) {\n const ctx = useDialogContext(\"Dialog.Close\");\n // The open-generation guard: a close from a previous opening must not close a new one.\n const { pending, run } = useActionTrigger(ctx.openGeneration);\n const { ids, dialogRef, recordCloseIntent } = ctx;\n const hasAction = action !== undefined;\n\n const state: DialogCloseState = { pending, disabled };\n\n const devRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !hasAction && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Close: rendered a <${element.tagName.toLowerCase()}> without \\`action\\`. Only a ` +\n '<button> runs `command=\"request-close\"`; this element will not close the dialog.',\n );\n }\n // A cleanup, so the merged ref never calls back with `null`.\n return () => {};\n },\n [hasAction],\n );\n\n const onClick = (event: MouseEvent<HTMLElement>) => {\n if (action === undefined) return;\n // Cancelling the click cancels the command.\n event.preventDefault();\n if (disabled) return;\n const native = event.nativeEvent;\n const returnValue = value ?? \"\";\n const details = createActionDetails(native, { returnValue });\n run(\n () => action(details),\n () => {\n // Repeated clicks re-run the action; the first `close()` wins (design 3.4).\n const dialog = dialogRef.current;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event: native });\n dialog.close(returnValue);\n },\n );\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Close.\n disabled,\n commandfor: ids.dialog,\n command: \"request-close\",\n value,\n ...(hasAction && { onClick }),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogCloseState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: devRef,\n });\n}\n\nDialogClose.displayName = \"DialogClose\";\n"],"mappings":";;;;;;;;AA0CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;AAYvC,SAAgB,YAAY,EAC1B,QACA,OACA,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACgB;CACnB,MAAM,MAAM,iBAAiB,cAAc;CAE3C,MAAM,EAAE,SAAS,QAAQ,iBAAiB,IAAI,cAAc;CAC5D,MAAM,EAAE,KAAK,WAAW,sBAAsB;CAC9C,MAAM,YAAY,WAAW,KAAA;CAE7B,MAAM,QAA0B;EAAE;EAAS;CAAS;CAEpD,MAAM,SAAS,aACZ,YAAyB;EACxB,IAAI,SAAS,CAAC,aAAa,QAAQ,YAAY,UAC7C,QAAQ,MACN,6BAA6B,QAAQ,QAAQ,YAAY,EAAE,gHAE7D;EAGF,aAAa,CAAC;CAChB,GACA,CAAC,SAAS,CACZ;CAEA,MAAM,WAAW,UAAmC;EAClD,IAAI,WAAW,KAAA,GAAW;EAE1B,MAAM,eAAe;EACrB,IAAI,UAAU;EACd,MAAM,SAAS,MAAM;EACrB,MAAM,cAAc,SAAS;EAC7B,MAAM,UAAU,oBAAoB,QAAQ,EAAE,YAAY,CAAC;EAC3D,UACQ,OAAO,OAAO,SACd;GAEJ,MAAM,SAAS,UAAU;GACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;GACrC,kBAAkB;IAAE,QAAQ;IAAY,OAAO;GAAO,CAAC;GACvD,OAAO,MAAM,WAAW;EAC1B,CACF;CACF;CAEA,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAEvD;EACA,YAAY,IAAI;EAChB,SAAS;EACT;EACA,GAAI,aAAa,EAAE,QAAQ;EAC3B,gBAAgB,SAAS,OAAO;EAChC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,YAAY,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-context.mjs","names":[],"sources":["../../src/dialog/dialog-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\nimport type { RefObject } from \"react\";\nimport type { ChangeReason } from \"../internal/types.js\";\n\n/**\n * \"static\": JS-free mode, the browser owns open and close through Invoker Commands. \"library\":\n * open is library-owned (a transition keyed on committed state), close is browser-owned and\n * notified (design section 2).\n */\nexport type DialogMode = \"static\" | \"library\";\n\n/**\n * What caused the next `close` event, recorded just before the close happens (design 2.3). The\n * `close` event carries no source; without an intent the close is a `\"dismiss\"`.\n */\nexport interface DialogCloseIntent {\n readonly reason: ChangeReason;\n readonly event: Event | undefined;\n}\n\nexport interface DialogContextValue {\n readonly mode: DialogMode;\n /** `useId`-based ids: the `<dialog>`, its Title and its Description. */\n readonly ids: { readonly dialog: string; readonly title: string; readonly description: string };\n readonly modal: boolean;\n /** Committed open state (library mode); `false` in static mode. */\n readonly committed: boolean;\n /** Optimistic open state (library mode); `false` in static mode. */\n readonly optimistic: boolean;\n /** The Root's transition is pending (library mode). */\n readonly pending: boolean;\n /**\n * Open the dialog through the Root's transition (library mode): a Trigger click, a foreign\n * `show-modal` command, or mount reconciliation. A no-op when the user already intends `true`.\n */\n readonly requestOpen: (event: Event | undefined, reason: ChangeReason) => void;\n /**\n * The dialog closed natively: dispatch the `false` notification unless the user (or the\n * library's own echo) already intends `false`.\n */\n readonly notifyClosed: (event: Event, reason: ChangeReason, returnValue: string) => void;\n /** From `useControllableAction`: the latest in-flight intent, or `null`. */\n readonly latestInFlight: () => { readonly next: boolean } | null;\n /** The registered Trigger element, for the focus-return fallback (design 6.2). */\n readonly triggerRef: RefObject<HTMLElement | null>;\n /** The `<dialog>`, set by the Popup's ref; how Close and Form reach `close(value)`. */\n readonly dialogRef: RefObject<HTMLDialogElement | null>;\n /** Record what causes the next close. Cleared after a task if the dialog did not close. */\n readonly recordCloseIntent: (intent: DialogCloseIntent | undefined) => void;\n /** Read and clear the recorded intent (the Popup's `close` listener). */\n readonly takeCloseIntent: () => DialogCloseIntent | undefined;\n}\n\nexport const DialogContext = createContext<DialogContextValue | null>(null);\n\nexport function useDialogContext(part: string): DialogContextValue {\n const context = use(DialogContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a Dialog.Root.`);\n }\n return context;\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"dialog-context.mjs","names":[],"sources":["../../src/dialog/dialog-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\nimport type { RefObject } from \"react\";\nimport type { ChangeReason } from \"../internal/types.js\";\n\n/**\n * \"static\": JS-free mode, the browser owns open and close through Invoker Commands. \"library\":\n * open is library-owned (a transition keyed on committed state), close is browser-owned and\n * notified (design section 2).\n */\nexport type DialogMode = \"static\" | \"library\";\n\n/**\n * What caused the next `close` event, recorded just before the close happens (design 2.3). The\n * `close` event carries no source; without an intent the close is a `\"dismiss\"`.\n */\nexport interface DialogCloseIntent {\n readonly reason: ChangeReason;\n readonly event: Event | undefined;\n}\n\nexport interface DialogContextValue {\n readonly mode: DialogMode;\n /** `useId`-based ids: the `<dialog>`, its Title and its Description. */\n readonly ids: { readonly dialog: string; readonly title: string; readonly description: string };\n readonly modal: boolean;\n /** Committed open state (library mode); `false` in static mode. */\n readonly committed: boolean;\n /** Optimistic open state (library mode); `false` in static mode. */\n readonly optimistic: boolean;\n /** The Root's transition is pending (library mode). */\n readonly pending: boolean;\n /**\n * Open the dialog through the Root's transition (library mode): a Trigger click, a foreign\n * `show-modal` command, or mount reconciliation. A no-op when the user already intends `true`.\n */\n readonly requestOpen: (event: Event | undefined, reason: ChangeReason) => void;\n /**\n * The dialog closed natively: dispatch the `false` notification unless the user (or the\n * library's own echo) already intends `false`.\n */\n readonly notifyClosed: (event: Event, reason: ChangeReason, returnValue: string) => void;\n /** From `useControllableAction`: the latest in-flight intent, or `null`. */\n readonly latestInFlight: () => { readonly next: boolean } | null;\n /** The registered Trigger element, for the focus-return fallback (design 6.2). */\n readonly triggerRef: RefObject<HTMLElement | null>;\n /** The `<dialog>`, set by the Popup's ref; how Close and Form reach `close(value)`. */\n readonly dialogRef: RefObject<HTMLDialogElement | null>;\n /** Record what causes the next close. Cleared after a task if the dialog did not close. */\n readonly recordCloseIntent: (intent: DialogCloseIntent | undefined) => void;\n /** Read and clear the recorded intent (the Popup's `close` listener). */\n readonly takeCloseIntent: () => DialogCloseIntent | undefined;\n /**\n * Counts the opens of this dialog. A Close action reads it when it starts and skips its close\n * if a newer open started meanwhile (the dialog was dismissed and opened again).\n */\n readonly openGeneration: () => number;\n /** Advance `openGeneration` (an open dispatch, the `<dialog>`'s native `beforetoggle` to open). */\n readonly nextOpenGeneration: () => void;\n}\n\nexport const DialogContext = createContext<DialogContextValue | null>(null);\n\nexport function useDialogContext(part: string): DialogContextValue {\n const context = use(DialogContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a Dialog.Root.`);\n }\n return context;\n}\n"],"mappings":";;;AA6DA,MAAa,gBAAgB,cAAyC,IAAI;AAE1E,SAAgB,iBAAiB,MAAkC;CACjE,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,wCAAwC;CAElE,OAAO;AACT"}
|
|
@@ -198,6 +198,7 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
|
|
|
198
198
|
if (event.target !== dialogRef.current) return;
|
|
199
199
|
const newState = event.newState;
|
|
200
200
|
awaitingClose.current = newState === "closed";
|
|
201
|
+
if (newState === "open") ctx.nextOpenGeneration();
|
|
201
202
|
});
|
|
202
203
|
useEffect(() => {
|
|
203
204
|
if (dialog === null) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-popup.mjs","names":[],"sources":["../../src/dialog/dialog-popup.tsx"],"sourcesContent":["\"use client\";\nimport {\n Activity,\n startTransition,\n useCallback,\n useEffect,\n useEffectEvent,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n ReactNode,\n RefObject,\n ViewTransitionClass,\n} from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogPopupState {\n /** Committed open state (library mode); `false` in JS-free mode. Read `:open` for the DOM truth. */\n readonly open: boolean;\n readonly modal: boolean;\n}\n\nexport interface DialogPopupProps extends Omit<\n ComponentPropsWithRef<\"dialog\">,\n \"id\" | \"open\" | \"closedby\" | \"onToggle\" | \"onBeforeToggle\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Library mode: what happens to the children while the dialog is closed.\n * - `false` (default): not rendered. Mounting them is part of the opening transition, so\n * content that suspends holds the open until it is ready. Unmount waits for the dialog's\n * exit animations.\n * - `true`: rendered inside `<Activity mode=\"hidden\">`: DOM and state preserved, effects\n * stopped, pre-rendered at low priority. Not in the server HTML.\n *\n * Ignored in JS-free mode (always mounted).\n */\n keepMounted?: boolean;\n /**\n * Element to focus when the dialog opens: a ref, or a selector resolved inside the dialog.\n * The library writes `autofocus` on it right before `showModal()` / `show()` and the platform\n * focuses it. Default: the platform's choice (first focusable descendant; the dialog itself if\n * none). Use `tabIndex={-1}` on a heading or wrapper for long or destructive dialogs (APG).\n *\n * In JS-free mode the browser opens the dialog itself, so the attribute is written after\n * hydration and kept while mounted; before hydration only `autoFocus` in the markup works.\n */\n initialFocus?: RefObject<HTMLElement | null> | string;\n /**\n * Close on a click outside the dialog (on the backdrop for modal dialogs). Default `false`.\n * Baseline: JS `pointerdown` + `click` listeners, which close only when both the press and the\n * click land outside (a drag that starts inside, such as selecting text, never closes); with\n * `experimental_closedBy`, native `closedby=\"any\"`, which behaves the same.\n */\n lightDismiss?: boolean;\n /**\n * Experimental (Chrome 134+, Firefox 141+; Safari 27.0 ignores it). Renders `closedby`\n * (`\"any\"` when `lightDismiss`, else `\"closerequest\"`) and skips the JS dismiss listeners where\n * `HTMLDialogElement.prototype.closedBy` exists. Elsewhere the JS baseline stays active.\n * Default `false`.\n */\n experimental_closedBy?: boolean;\n /**\n * Library mode only. Forwarded to the `<ViewTransition default>` around the dialog. Animates\n * the opening commit and content changes while open; the close is the browser's and animates\n * with CSS. Ignored (dev warning) in JS-free mode.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, DialogPopupState>;\n style?: StateValue<CSSProperties | undefined, DialogPopupState>;\n children?: StateValue<ReactNode, DialogPopupState>;\n /** Must render a `<dialog>`. */\n render?: RenderProp<ComponentPropsWithRef<\"dialog\">, DialogPopupState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** Resolves `initialFocus` against the dialog (a ref, or a selector inside the dialog). */\nfunction resolveInitialFocus(\n dialog: HTMLDialogElement,\n initialFocus: RefObject<HTMLElement | null> | string,\n): HTMLElement | null {\n if (typeof initialFocus === \"string\") return dialog.querySelector<HTMLElement>(initialFocus);\n return initialFocus.current;\n}\n\n/** The nearest `<dialog>` around a node (the dialog itself counts). */\nfunction closestDialog(target: EventTarget | null): Element | null {\n return target instanceof Element ? target.closest(\"dialog\") : null;\n}\n\n/**\n * An open popover that takes Escape before the dialog: `auto` or `hint` (they have a close\n * watcher). A `manual` popover, or `hint` where the engine maps it to `manual`, does not.\n */\nfunction isOpenLightPopover(element: Element): boolean {\n if (!element.hasAttribute(\"popover\")) return false;\n // The IDL attribute reflects the state the engine applies; jsdom has none.\n const reflected: unknown = (element as HTMLElement).popover;\n const state =\n typeof reflected === \"string\" ? reflected : element.getAttribute(\"popover\")!.toLowerCase();\n if (state === \"manual\") return false;\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\n/**\n * Whether Escape belongs to a layer inside `dialog` that sits above it (design 9: \"closes the\n * innermost one only\"): the target is inside an open nested `<dialog>` or popover, or an open\n * `auto` / `hint` popover inside the dialog would take the close request (close watchers are\n * per document, not per focus).\n */\nfunction escapeBelongsToInnerLayer(dialog: HTMLDialogElement, target: EventTarget | null): boolean {\n for (\n let node = target instanceof Element ? target : null;\n node !== null && node !== dialog;\n node = node.parentElement\n ) {\n if (node instanceof HTMLDialogElement && node.open) return true;\n if (isOpenLightPopover(node)) return true;\n }\n for (const element of dialog.querySelectorAll(\"[popover]\")) {\n if (isOpenLightPopover(element)) return true;\n }\n return false;\n}\n\n/** Whether a pointer event on the dialog itself lies outside its border box (the `::backdrop`). */\nfunction onBackdrop(dialog: HTMLDialogElement, event: Event): boolean {\n if (event.target !== dialog) return false;\n const { clientX, clientY } = event as MouseEvent;\n const rect = dialog.getBoundingClientRect();\n return !(\n rect.top <= clientY &&\n clientY <= rect.bottom &&\n rect.left <= clientX &&\n clientX <= rect.right\n );\n}\n\n/** An animation that ends: infinite ones (a spinner) would hold the content forever. */\nfunction ends(animation: Animation): boolean {\n const end = animation.effect?.getComputedTiming().endTime;\n return typeof end === \"number\" && Number.isFinite(end);\n}\n\n/**\n * The dialog itself: a `<dialog>` with the id the Trigger's `commandfor` names, wired to Title\n * and Description through `aria-labelledby` / `aria-describedby`. Never renders `open`: modal\n * opening and closing go through `showModal()` / `close()` only. Style it with `:open` and\n * `:modal`; it emits `data-modal` but no `data-open`.\n *\n * In library mode the children render from committed state, so content that suspends keeps the\n * dialog closed (and the Trigger pending) until it is ready; `showModal()` runs in the layout\n * effect of the commit that mounted them. Closing is the browser's: the native `close` event\n * becomes `openChangeAction(false, { reason, returnValue })`.\n *\n * Owned keys (conventions 1.2): `id`, `open`, `closedby`, `aria-labelledby` and\n * `aria-describedby` (token-merged), `data-modal`, `suppressHydrationWarning`.\n */\nexport function DialogPopup({\n keepMounted,\n initialFocus,\n lightDismiss = false,\n experimental_closedBy = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogPopupProps) {\n const ctx = useDialogContext(\"Dialog.Popup\");\n const isLibrary = ctx.mode === \"library\";\n const { modal, ids, dialogRef, triggerRef } = ctx;\n const [dialog, setDialog] = useState<HTMLDialogElement | null>(null);\n // Children stay mounted while the dialog's exit animation runs (design 4.4).\n const [exiting, setExiting] = useState(false);\n const exitGeneration = useRef(0);\n // Set by the synchronous `beforetoggle` of a close, cleared by the `close` event a task later:\n // the dialog is closed but its notification is not dispatched yet, so the apply effect must\n // not re-open it from a commit that lands in between.\n const awaitingClose = useRef(false);\n const warnedFocus = useRef(false);\n\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Dialog.Popup: `transition` has no effect in JS-free mode; the browser opens and closes the \" +\n \"dialog outside any React transition. Animate it with CSS (`@starting-style`).\",\n );\n useDevWarning(\n !isLibrary && keepMounted === false,\n \"Dialog.Popup: `keepMounted={false}` cannot be honoured in JS-free mode; the content is \" +\n \"always mounted so that the server HTML is complete.\",\n );\n\n const libraryRef = useCallback(\n (element: HTMLDialogElement) => {\n if (isDev && element.tagName !== \"DIALOG\") {\n console.error(\n `Dialog.Popup: expected to render a <dialog> element but got <${element.tagName.toLowerCase()}>. ` +\n \"Top layer, backdrop, focus and Escape all come from a real <dialog>.\",\n );\n }\n dialogRef.current = element;\n setDialog(element);\n return () => {\n if (dialogRef.current === element) dialogRef.current = null;\n setDialog(null);\n };\n },\n [dialogRef],\n );\n\n // --- Opening -----------------------------------------------------------------------------\n\n const openDialog = (element: HTMLDialogElement): void => {\n let target: HTMLElement | null = null;\n if (initialFocus !== undefined) {\n target = resolveInitialFocus(element, initialFocus);\n if (target === null || !element.contains(target)) {\n if (isDev && !warnedFocus.current) {\n warnedFocus.current = true;\n console.error(\n target === null\n ? \"Dialog.Popup: `initialFocus` resolved to no element; the platform picks the focus target.\"\n : \"Dialog.Popup: `initialFocus` is outside the dialog; the platform picks the focus target.\",\n );\n }\n target = null;\n }\n }\n // The platform's dialog focusing steps honour `autofocus`; React drops `autoFocus` on\n // client-rendered content (react/react#23301). Never `.focus()` (design 6.1).\n const added = target !== null && !target.hasAttribute(\"autofocus\");\n if (added) target!.setAttribute(\"autofocus\", \"\");\n try {\n if (modal) element.showModal();\n else element.show();\n } finally {\n if (added) target!.removeAttribute(\"autofocus\");\n }\n };\n\n // Mount reconciliation (design 2.4): a click before hydration opened the dialog natively.\n // Runs before the apply effect; the dispatch makes the apply effect wait for it.\n const reconcileOnMount = useEffectEvent(() => {\n const element = dialogRef.current;\n if (element === null || !element.open) return;\n ctx.requestOpen(undefined, \"platform\");\n });\n useLayoutEffect(() => {\n if (isLibrary) reconcileOnMount();\n }, [isLibrary]);\n\n // The apply effect (design 2.5): after every commit, once nothing is in flight, the DOM\n // follows committed state. Opens on the user's dispatch, `defaultOpen`, a controlled parent,\n // the re-open of a refused or failed close; closes after a controlled close or a refused\n // pre-hydration open.\n useLayoutEffect(() => {\n if (!isLibrary) return;\n const element = dialogRef.current;\n if (element === null || !element.isConnected) return;\n if (ctx.pending || ctx.latestInFlight() !== null) return;\n if (ctx.committed && !element.open) {\n if (awaitingClose.current) return;\n openDialog(element);\n } else if (!ctx.committed && element.open) {\n // An echo: the `close` listener finds nothing intended and skips the dispatch. `close()`,\n // not `requestClose()`: a parent's close is not vetoable.\n ctx.recordCloseIntent(undefined);\n element.close();\n }\n });\n\n // JS-free mode: there is no \"right before showModal()\" (the browser runs the command), so the\n // `autofocus` attribute stays on the target while it is mounted.\n useEffect(() => {\n if (isLibrary || initialFocus === undefined || dialog === null) return;\n const target = resolveInitialFocus(dialog, initialFocus);\n if (target === null || !dialog.contains(target) || target.hasAttribute(\"autofocus\")) return;\n target.setAttribute(\"autofocus\", \"\");\n return () => target.removeAttribute(\"autofocus\");\n }, [isLibrary, initialFocus, dialog]);\n\n // --- Native listeners (conventions 7.4) ---------------------------------------------------\n\n const onCommand = useEffectEvent((event: Event) => {\n if (event.target !== dialogRef.current || event.defaultPrevented) return;\n const command = String((event as Event & { command?: string }).command ?? \"\").toLowerCase();\n if (command === \"show-modal\") {\n // A foreign invoker (design 2.2): opening goes through the library in library mode.\n if (!isLibrary) return;\n event.preventDefault();\n ctx.requestOpen(event, \"activate\");\n } else if (command === \"request-close\" || command === \"close\") {\n ctx.recordCloseIntent({ reason: \"activate\", event });\n }\n });\n\n const onSubmit = useEffectEvent((event: Event) => {\n const element = dialogRef.current;\n const form = event.target;\n if (element === null || !(form instanceof HTMLFormElement)) return;\n if (form.closest(\"dialog\") !== element) return;\n const submitter = (event as SubmitEvent).submitter;\n const formMethod = submitter?.getAttribute(\"formmethod\");\n const method = formMethod != null ? formMethod.toLowerCase() : form.method;\n if (method !== \"dialog\") return;\n ctx.recordCloseIntent({ reason: \"activate\", event });\n });\n\n const onClose = useEffectEvent((event: Event) => {\n const element = dialogRef.current;\n if (element === null || event.target !== element) return;\n awaitingClose.current = false;\n\n // Focus return is the platform's; it leaves focus inside the hidden dialog only when the\n // opener is gone (design 6.2).\n const doc = element.ownerDocument;\n const active = doc.activeElement;\n if (active !== null && active !== doc.body && element.contains(active)) {\n const trigger = triggerRef.current;\n if (trigger !== null && trigger.isConnected) trigger.focus();\n else if (active instanceof HTMLElement) active.blur();\n }\n\n const intent = ctx.takeCloseIntent();\n if (!isLibrary) return;\n\n // Keep the children while the exit animation runs (design 4.4). Firefox has none.\n if (element.isConnected && typeof element.getAnimations === \"function\") {\n void getComputedStyle(element).opacity; // flush style so the exit transitions exist\n const animations = element.getAnimations({ subtree: true }).filter(ends);\n if (animations.length > 0) {\n const generation = ++exitGeneration.current;\n setExiting(true);\n void Promise.allSettled(animations.map((animation) => animation.finished)).then(() => {\n if (exitGeneration.current === generation) startTransition(() => setExiting(false));\n });\n }\n }\n\n ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? \"dismiss\", element.returnValue);\n });\n\n const onBeforeToggle = useEffectEvent((event: Event) => {\n if (event.target !== dialogRef.current) return;\n const newState = (event as ToggleEvent).newState;\n awaitingClose.current = newState === \"closed\";\n });\n\n useEffect(() => {\n if (dialog === null) return;\n const doc = dialog.ownerDocument;\n // Feature detection only with the flag on, inside an effect (conventions 8).\n const nativeClosedBy = experimental_closedBy && \"closedBy\" in dialog;\n\n const listeners: [EventTarget, string, EventListener, boolean][] = [\n [dialog, \"command\", onCommand, false],\n [dialog, \"submit\", onSubmit, false],\n [dialog, \"close\", onClose, false],\n [dialog, \"beforetoggle\", onBeforeToggle, false],\n ];\n\n if (!modal && !nativeClosedBy) {\n // Non-modal dialogs get no close watcher Escape from the platform (design 2.6).\n listeners.push([\n dialog,\n \"keydown\",\n (event) => {\n const keyboardEvent = event as KeyboardEvent;\n if (keyboardEvent.key !== \"Escape\" || keyboardEvent.defaultPrevented) return;\n if (!dialog.open || closestDialog(keyboardEvent.target) !== dialog) return;\n // A popover or nested dialog inside takes this Escape (its close watcher); leave the\n // event alone so that it can.\n if (escapeBelongsToInnerLayer(dialog, keyboardEvent.target)) return;\n keyboardEvent.preventDefault();\n dialog.requestClose();\n },\n false,\n ]);\n }\n\n // Light dismiss needs both the press and the click outside, like native `closedby=\"any\"`: a\n // drag that starts inside (selecting text in an input) and ends on the backdrop or outside\n // fires `click` on the common ancestor and must not close (it would lose a half-filled form).\n let pressedOutside = false;\n\n const isOutside = (target: EventTarget | null): boolean => {\n if (!(target instanceof Node)) return true;\n if (dialog.contains(target)) return false;\n // The Trigger re-opening a non-modal dialog is not a click outside.\n return !(triggerRef.current?.contains(target) ?? false);\n };\n let outsideAttached = false;\n const onOutsidePointerDown = (event: Event) => {\n pressedOutside = dialog.open && isOutside(event.target);\n };\n const onOutsideClick = (event: Event) => {\n const pressed = pressedOutside;\n pressedOutside = false;\n if (!dialog.open || !pressed || !isOutside(event.target)) return;\n dialog.requestClose();\n };\n const syncOutside = () => {\n const want = dialog.open;\n if (want === outsideAttached) return;\n outsideAttached = want;\n pressedOutside = false;\n if (want) {\n doc.addEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.addEventListener(\"click\", onOutsideClick, true);\n } else {\n doc.removeEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.removeEventListener(\"click\", onOutsideClick, true);\n }\n };\n\n if (lightDismiss && !nativeClosedBy) {\n if (modal) {\n // A press or click on the ::backdrop targets the dialog itself, outside its border box.\n listeners.push(\n [\n dialog,\n \"pointerdown\",\n (event) => {\n pressedOutside = dialog.open && onBackdrop(dialog, event);\n },\n false,\n ],\n [\n dialog,\n \"click\",\n (event) => {\n const pressed = pressedOutside;\n pressedOutside = false;\n if (!dialog.open || !pressed || !onBackdrop(dialog, event)) return;\n dialog.requestClose();\n },\n false,\n ],\n );\n } else {\n // A document listener only while open, following the native `toggle` (the React\n // `onToggle` prop never fires on a hydrated <dialog>); reads `dialog.open`, not\n // `newState` (coalescing).\n listeners.push([dialog, \"toggle\", syncOutside, false]);\n syncOutside();\n }\n }\n\n for (const [target, type, listener, capture] of listeners) {\n target.addEventListener(type, listener, capture);\n }\n return () => {\n for (const [target, type, listener, capture] of listeners) {\n target.removeEventListener(type, listener, capture);\n }\n if (outsideAttached) {\n doc.removeEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.removeEventListener(\"click\", onOutsideClick, true);\n }\n };\n }, [dialog, modal, lightDismiss, experimental_closedBy, triggerRef]);\n\n // Development check: the dialog has an accessible name when it is (or can be) shown.\n const shown = !isLibrary || ctx.committed;\n useEffect(() => {\n if (!isDev || dialog === null || !shown) return;\n if (dialog.hasAttribute(\"aria-label\")) return;\n const labelledBy = (dialog.getAttribute(\"aria-labelledby\") ?? \"\").split(/\\s+/);\n const doc = dialog.ownerDocument;\n if (labelledBy.some((id) => id !== \"\" && doc.getElementById(id) !== null)) return;\n console.error(\n \"Dialog.Popup: the dialog has no accessible name. Render a Dialog.Title, or give the \" +\n \"Popup an `aria-label`.\",\n );\n }, [dialog, shown]);\n\n const state: DialogPopupState = { open: ctx.committed, modal };\n const visible = ctx.committed || exiting;\n\n const content = (s: DialogPopupState): ReactNode => {\n if (!isLibrary) return resolveStateValue(children, s);\n if (keepMounted === true) {\n // The mode changes with committed state, inside the Root's transition (conventions 5.5);\n // it stays visible while an exit animation runs.\n return (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>{resolveStateValue(children, s)}</Activity>\n );\n }\n return visible ? resolveStateValue(children, s) : null;\n };\n\n const libraryProps: ComponentPropsWithRef<\"dialog\"> & DataAttributes = {\n id: ids.dialog,\n // Never rendered: removing `open` by hand hides a modal dialog without `close` and leaves\n // `:modal` true (research). Owned so a `render` element cannot add it.\n open: undefined,\n closedby: experimental_closedBy ? (lightDismiss ? \"any\" : \"closerequest\") : undefined,\n \"aria-labelledby\": ids.title,\n \"aria-describedby\": ids.description,\n \"data-modal\": dataAttr(modal),\n // A pre-hydration open leaves an `open` attribute the server did not render (design 2.4).\n suppressHydrationWarning: true,\n };\n\n const element = useRender<ComponentPropsWithRef<\"dialog\">, DialogPopupState>({\n defaultElement: \"dialog\",\n render,\n state,\n className,\n style,\n children: content,\n props: rest,\n libraryProps,\n ref: libraryRef,\n });\n\n return useTransitionBoundary(\"Dialog.Popup\", isLibrary ? transition : undefined, element);\n}\n\nDialogPopup.displayName = \"DialogPopup\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dialog-popup.mjs","names":[],"sources":["../../src/dialog/dialog-popup.tsx"],"sourcesContent":["\"use client\";\nimport {\n Activity,\n startTransition,\n useCallback,\n useEffect,\n useEffectEvent,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n ReactNode,\n RefObject,\n ViewTransitionClass,\n} from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogPopupState {\n /** Committed open state (library mode); `false` in JS-free mode. Read `:open` for the DOM truth. */\n readonly open: boolean;\n readonly modal: boolean;\n}\n\nexport interface DialogPopupProps extends Omit<\n ComponentPropsWithRef<\"dialog\">,\n \"id\" | \"open\" | \"closedby\" | \"onToggle\" | \"onBeforeToggle\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Library mode: what happens to the children while the dialog is closed.\n * - `false` (default): not rendered. Mounting them is part of the opening transition, so\n * content that suspends holds the open until it is ready. Unmount waits for the dialog's\n * exit animations.\n * - `true`: rendered inside `<Activity mode=\"hidden\">`: DOM and state preserved, effects\n * stopped, pre-rendered at low priority. Not in the server HTML.\n *\n * Ignored in JS-free mode (always mounted).\n */\n keepMounted?: boolean;\n /**\n * Element to focus when the dialog opens: a ref, or a selector resolved inside the dialog.\n * The library writes `autofocus` on it right before `showModal()` / `show()` and the platform\n * focuses it. Default: the platform's choice (first focusable descendant; the dialog itself if\n * none). Use `tabIndex={-1}` on a heading or wrapper for long or destructive dialogs (APG).\n *\n * In JS-free mode the browser opens the dialog itself, so the attribute is written after\n * hydration and kept while mounted; before hydration only `autoFocus` in the markup works.\n */\n initialFocus?: RefObject<HTMLElement | null> | string;\n /**\n * Close on a click outside the dialog (on the backdrop for modal dialogs). Default `false`.\n * Baseline: JS `pointerdown` + `click` listeners, which close only when both the press and the\n * click land outside (a drag that starts inside, such as selecting text, never closes); with\n * `experimental_closedBy`, native `closedby=\"any\"`, which behaves the same.\n */\n lightDismiss?: boolean;\n /**\n * Experimental (Chrome 134+, Firefox 141+; Safari 27.0 ignores it). Renders `closedby`\n * (`\"any\"` when `lightDismiss`, else `\"closerequest\"`) and skips the JS dismiss listeners where\n * `HTMLDialogElement.prototype.closedBy` exists. Elsewhere the JS baseline stays active.\n * Default `false`.\n */\n experimental_closedBy?: boolean;\n /**\n * Library mode only. Forwarded to the `<ViewTransition default>` around the dialog. Animates\n * the opening commit and content changes while open; the close is the browser's and animates\n * with CSS. Ignored (dev warning) in JS-free mode.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, DialogPopupState>;\n style?: StateValue<CSSProperties | undefined, DialogPopupState>;\n children?: StateValue<ReactNode, DialogPopupState>;\n /** Must render a `<dialog>`. */\n render?: RenderProp<ComponentPropsWithRef<\"dialog\">, DialogPopupState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** Resolves `initialFocus` against the dialog (a ref, or a selector inside the dialog). */\nfunction resolveInitialFocus(\n dialog: HTMLDialogElement,\n initialFocus: RefObject<HTMLElement | null> | string,\n): HTMLElement | null {\n if (typeof initialFocus === \"string\") return dialog.querySelector<HTMLElement>(initialFocus);\n return initialFocus.current;\n}\n\n/** The nearest `<dialog>` around a node (the dialog itself counts). */\nfunction closestDialog(target: EventTarget | null): Element | null {\n return target instanceof Element ? target.closest(\"dialog\") : null;\n}\n\n/**\n * An open popover that takes Escape before the dialog: `auto` or `hint` (they have a close\n * watcher). A `manual` popover, or `hint` where the engine maps it to `manual`, does not.\n */\nfunction isOpenLightPopover(element: Element): boolean {\n if (!element.hasAttribute(\"popover\")) return false;\n // The IDL attribute reflects the state the engine applies; jsdom has none.\n const reflected: unknown = (element as HTMLElement).popover;\n const state =\n typeof reflected === \"string\" ? reflected : element.getAttribute(\"popover\")!.toLowerCase();\n if (state === \"manual\") return false;\n try {\n return element.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\n/**\n * Whether Escape belongs to a layer inside `dialog` that sits above it (design 9: \"closes the\n * innermost one only\"): the target is inside an open nested `<dialog>` or popover, or an open\n * `auto` / `hint` popover inside the dialog would take the close request (close watchers are\n * per document, not per focus).\n */\nfunction escapeBelongsToInnerLayer(dialog: HTMLDialogElement, target: EventTarget | null): boolean {\n for (\n let node = target instanceof Element ? target : null;\n node !== null && node !== dialog;\n node = node.parentElement\n ) {\n if (node instanceof HTMLDialogElement && node.open) return true;\n if (isOpenLightPopover(node)) return true;\n }\n for (const element of dialog.querySelectorAll(\"[popover]\")) {\n if (isOpenLightPopover(element)) return true;\n }\n return false;\n}\n\n/** Whether a pointer event on the dialog itself lies outside its border box (the `::backdrop`). */\nfunction onBackdrop(dialog: HTMLDialogElement, event: Event): boolean {\n if (event.target !== dialog) return false;\n const { clientX, clientY } = event as MouseEvent;\n const rect = dialog.getBoundingClientRect();\n return !(\n rect.top <= clientY &&\n clientY <= rect.bottom &&\n rect.left <= clientX &&\n clientX <= rect.right\n );\n}\n\n/** An animation that ends: infinite ones (a spinner) would hold the content forever. */\nfunction ends(animation: Animation): boolean {\n const end = animation.effect?.getComputedTiming().endTime;\n return typeof end === \"number\" && Number.isFinite(end);\n}\n\n/**\n * The dialog itself: a `<dialog>` with the id the Trigger's `commandfor` names, wired to Title\n * and Description through `aria-labelledby` / `aria-describedby`. Never renders `open`: modal\n * opening and closing go through `showModal()` / `close()` only. Style it with `:open` and\n * `:modal`; it emits `data-modal` but no `data-open`.\n *\n * In library mode the children render from committed state, so content that suspends keeps the\n * dialog closed (and the Trigger pending) until it is ready; `showModal()` runs in the layout\n * effect of the commit that mounted them. Closing is the browser's: the native `close` event\n * becomes `openChangeAction(false, { reason, returnValue })`.\n *\n * Owned keys (conventions 1.2): `id`, `open`, `closedby`, `aria-labelledby` and\n * `aria-describedby` (token-merged), `data-modal`, `suppressHydrationWarning`.\n */\nexport function DialogPopup({\n keepMounted,\n initialFocus,\n lightDismiss = false,\n experimental_closedBy = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogPopupProps) {\n const ctx = useDialogContext(\"Dialog.Popup\");\n const isLibrary = ctx.mode === \"library\";\n const { modal, ids, dialogRef, triggerRef } = ctx;\n const [dialog, setDialog] = useState<HTMLDialogElement | null>(null);\n // Children stay mounted while the dialog's exit animation runs (design 4.4).\n const [exiting, setExiting] = useState(false);\n const exitGeneration = useRef(0);\n // Set by the synchronous `beforetoggle` of a close, cleared by the `close` event a task later:\n // the dialog is closed but its notification is not dispatched yet, so the apply effect must\n // not re-open it from a commit that lands in between.\n const awaitingClose = useRef(false);\n const warnedFocus = useRef(false);\n\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Dialog.Popup: `transition` has no effect in JS-free mode; the browser opens and closes the \" +\n \"dialog outside any React transition. Animate it with CSS (`@starting-style`).\",\n );\n useDevWarning(\n !isLibrary && keepMounted === false,\n \"Dialog.Popup: `keepMounted={false}` cannot be honoured in JS-free mode; the content is \" +\n \"always mounted so that the server HTML is complete.\",\n );\n\n const libraryRef = useCallback(\n (element: HTMLDialogElement) => {\n if (isDev && element.tagName !== \"DIALOG\") {\n console.error(\n `Dialog.Popup: expected to render a <dialog> element but got <${element.tagName.toLowerCase()}>. ` +\n \"Top layer, backdrop, focus and Escape all come from a real <dialog>.\",\n );\n }\n dialogRef.current = element;\n setDialog(element);\n return () => {\n if (dialogRef.current === element) dialogRef.current = null;\n setDialog(null);\n };\n },\n [dialogRef],\n );\n\n // --- Opening -----------------------------------------------------------------------------\n\n const openDialog = (element: HTMLDialogElement): void => {\n let target: HTMLElement | null = null;\n if (initialFocus !== undefined) {\n target = resolveInitialFocus(element, initialFocus);\n if (target === null || !element.contains(target)) {\n if (isDev && !warnedFocus.current) {\n warnedFocus.current = true;\n console.error(\n target === null\n ? \"Dialog.Popup: `initialFocus` resolved to no element; the platform picks the focus target.\"\n : \"Dialog.Popup: `initialFocus` is outside the dialog; the platform picks the focus target.\",\n );\n }\n target = null;\n }\n }\n // The platform's dialog focusing steps honour `autofocus`; React drops `autoFocus` on\n // client-rendered content (react/react#23301). Never `.focus()` (design 6.1).\n const added = target !== null && !target.hasAttribute(\"autofocus\");\n if (added) target!.setAttribute(\"autofocus\", \"\");\n try {\n if (modal) element.showModal();\n else element.show();\n } finally {\n if (added) target!.removeAttribute(\"autofocus\");\n }\n };\n\n // Mount reconciliation (design 2.4): a click before hydration opened the dialog natively.\n // Runs before the apply effect; the dispatch makes the apply effect wait for it.\n const reconcileOnMount = useEffectEvent(() => {\n const element = dialogRef.current;\n if (element === null || !element.open) return;\n ctx.requestOpen(undefined, \"platform\");\n });\n useLayoutEffect(() => {\n if (isLibrary) reconcileOnMount();\n }, [isLibrary]);\n\n // The apply effect (design 2.5): after every commit, once nothing is in flight, the DOM\n // follows committed state. Opens on the user's dispatch, `defaultOpen`, a controlled parent,\n // the re-open of a refused or failed close; closes after a controlled close or a refused\n // pre-hydration open.\n useLayoutEffect(() => {\n if (!isLibrary) return;\n const element = dialogRef.current;\n if (element === null || !element.isConnected) return;\n if (ctx.pending || ctx.latestInFlight() !== null) return;\n if (ctx.committed && !element.open) {\n if (awaitingClose.current) return;\n openDialog(element);\n } else if (!ctx.committed && element.open) {\n // An echo: the `close` listener finds nothing intended and skips the dispatch. `close()`,\n // not `requestClose()`: a parent's close is not vetoable.\n ctx.recordCloseIntent(undefined);\n element.close();\n }\n });\n\n // JS-free mode: there is no \"right before showModal()\" (the browser runs the command), so the\n // `autofocus` attribute stays on the target while it is mounted.\n useEffect(() => {\n if (isLibrary || initialFocus === undefined || dialog === null) return;\n const target = resolveInitialFocus(dialog, initialFocus);\n if (target === null || !dialog.contains(target) || target.hasAttribute(\"autofocus\")) return;\n target.setAttribute(\"autofocus\", \"\");\n return () => target.removeAttribute(\"autofocus\");\n }, [isLibrary, initialFocus, dialog]);\n\n // --- Native listeners (conventions 7.4) ---------------------------------------------------\n\n const onCommand = useEffectEvent((event: Event) => {\n if (event.target !== dialogRef.current || event.defaultPrevented) return;\n const command = String((event as Event & { command?: string }).command ?? \"\").toLowerCase();\n if (command === \"show-modal\") {\n // A foreign invoker (design 2.2): opening goes through the library in library mode.\n if (!isLibrary) return;\n event.preventDefault();\n ctx.requestOpen(event, \"activate\");\n } else if (command === \"request-close\" || command === \"close\") {\n ctx.recordCloseIntent({ reason: \"activate\", event });\n }\n });\n\n const onSubmit = useEffectEvent((event: Event) => {\n const element = dialogRef.current;\n const form = event.target;\n if (element === null || !(form instanceof HTMLFormElement)) return;\n if (form.closest(\"dialog\") !== element) return;\n const submitter = (event as SubmitEvent).submitter;\n const formMethod = submitter?.getAttribute(\"formmethod\");\n const method = formMethod != null ? formMethod.toLowerCase() : form.method;\n if (method !== \"dialog\") return;\n ctx.recordCloseIntent({ reason: \"activate\", event });\n });\n\n const onClose = useEffectEvent((event: Event) => {\n const element = dialogRef.current;\n if (element === null || event.target !== element) return;\n awaitingClose.current = false;\n\n // Focus return is the platform's; it leaves focus inside the hidden dialog only when the\n // opener is gone (design 6.2).\n const doc = element.ownerDocument;\n const active = doc.activeElement;\n if (active !== null && active !== doc.body && element.contains(active)) {\n const trigger = triggerRef.current;\n if (trigger !== null && trigger.isConnected) trigger.focus();\n else if (active instanceof HTMLElement) active.blur();\n }\n\n const intent = ctx.takeCloseIntent();\n if (!isLibrary) return;\n\n // Keep the children while the exit animation runs (design 4.4). Firefox has none.\n if (element.isConnected && typeof element.getAnimations === \"function\") {\n void getComputedStyle(element).opacity; // flush style so the exit transitions exist\n const animations = element.getAnimations({ subtree: true }).filter(ends);\n if (animations.length > 0) {\n const generation = ++exitGeneration.current;\n setExiting(true);\n void Promise.allSettled(animations.map((animation) => animation.finished)).then(() => {\n if (exitGeneration.current === generation) startTransition(() => setExiting(false));\n });\n }\n }\n\n ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? \"dismiss\", element.returnValue);\n });\n\n const onBeforeToggle = useEffectEvent((event: Event) => {\n if (event.target !== dialogRef.current) return;\n const newState = (event as ToggleEvent).newState;\n awaitingClose.current = newState === \"closed\";\n // Every native open counts as a new opening (design 3.4, as Popover 14.1): foreign,\n // pre-hydration and JS-free opens too, so a Close action from an earlier opening skips its\n // close. Counted here, synchronously inside `showModal()` / `show()`, not at the queued\n // `toggle`: an action settling between the open and that task would otherwise still see the\n // old generation and close the new open.\n if (newState === \"open\") ctx.nextOpenGeneration();\n });\n\n useEffect(() => {\n if (dialog === null) return;\n const doc = dialog.ownerDocument;\n // Feature detection only with the flag on, inside an effect (conventions 8).\n const nativeClosedBy = experimental_closedBy && \"closedBy\" in dialog;\n\n const listeners: [EventTarget, string, EventListener, boolean][] = [\n [dialog, \"command\", onCommand, false],\n [dialog, \"submit\", onSubmit, false],\n [dialog, \"close\", onClose, false],\n [dialog, \"beforetoggle\", onBeforeToggle, false],\n ];\n\n if (!modal && !nativeClosedBy) {\n // Non-modal dialogs get no close watcher Escape from the platform (design 2.6).\n listeners.push([\n dialog,\n \"keydown\",\n (event) => {\n const keyboardEvent = event as KeyboardEvent;\n if (keyboardEvent.key !== \"Escape\" || keyboardEvent.defaultPrevented) return;\n if (!dialog.open || closestDialog(keyboardEvent.target) !== dialog) return;\n // A popover or nested dialog inside takes this Escape (its close watcher); leave the\n // event alone so that it can.\n if (escapeBelongsToInnerLayer(dialog, keyboardEvent.target)) return;\n keyboardEvent.preventDefault();\n dialog.requestClose();\n },\n false,\n ]);\n }\n\n // Light dismiss needs both the press and the click outside, like native `closedby=\"any\"`: a\n // drag that starts inside (selecting text in an input) and ends on the backdrop or outside\n // fires `click` on the common ancestor and must not close (it would lose a half-filled form).\n let pressedOutside = false;\n\n const isOutside = (target: EventTarget | null): boolean => {\n if (!(target instanceof Node)) return true;\n if (dialog.contains(target)) return false;\n // The Trigger re-opening a non-modal dialog is not a click outside.\n return !(triggerRef.current?.contains(target) ?? false);\n };\n let outsideAttached = false;\n const onOutsidePointerDown = (event: Event) => {\n pressedOutside = dialog.open && isOutside(event.target);\n };\n const onOutsideClick = (event: Event) => {\n const pressed = pressedOutside;\n pressedOutside = false;\n if (!dialog.open || !pressed || !isOutside(event.target)) return;\n dialog.requestClose();\n };\n const syncOutside = () => {\n const want = dialog.open;\n if (want === outsideAttached) return;\n outsideAttached = want;\n pressedOutside = false;\n if (want) {\n doc.addEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.addEventListener(\"click\", onOutsideClick, true);\n } else {\n doc.removeEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.removeEventListener(\"click\", onOutsideClick, true);\n }\n };\n\n if (lightDismiss && !nativeClosedBy) {\n if (modal) {\n // A press or click on the ::backdrop targets the dialog itself, outside its border box.\n listeners.push(\n [\n dialog,\n \"pointerdown\",\n (event) => {\n pressedOutside = dialog.open && onBackdrop(dialog, event);\n },\n false,\n ],\n [\n dialog,\n \"click\",\n (event) => {\n const pressed = pressedOutside;\n pressedOutside = false;\n if (!dialog.open || !pressed || !onBackdrop(dialog, event)) return;\n dialog.requestClose();\n },\n false,\n ],\n );\n } else {\n // A document listener only while open, following the native `toggle` (the React\n // `onToggle` prop never fires on a hydrated <dialog>); reads `dialog.open`, not\n // `newState` (coalescing).\n listeners.push([dialog, \"toggle\", syncOutside, false]);\n syncOutside();\n }\n }\n\n for (const [target, type, listener, capture] of listeners) {\n target.addEventListener(type, listener, capture);\n }\n return () => {\n for (const [target, type, listener, capture] of listeners) {\n target.removeEventListener(type, listener, capture);\n }\n if (outsideAttached) {\n doc.removeEventListener(\"pointerdown\", onOutsidePointerDown, true);\n doc.removeEventListener(\"click\", onOutsideClick, true);\n }\n };\n }, [dialog, modal, lightDismiss, experimental_closedBy, triggerRef]);\n\n // Development check: the dialog has an accessible name when it is (or can be) shown.\n const shown = !isLibrary || ctx.committed;\n useEffect(() => {\n if (!isDev || dialog === null || !shown) return;\n if (dialog.hasAttribute(\"aria-label\")) return;\n const labelledBy = (dialog.getAttribute(\"aria-labelledby\") ?? \"\").split(/\\s+/);\n const doc = dialog.ownerDocument;\n if (labelledBy.some((id) => id !== \"\" && doc.getElementById(id) !== null)) return;\n console.error(\n \"Dialog.Popup: the dialog has no accessible name. Render a Dialog.Title, or give the \" +\n \"Popup an `aria-label`.\",\n );\n }, [dialog, shown]);\n\n const state: DialogPopupState = { open: ctx.committed, modal };\n const visible = ctx.committed || exiting;\n\n const content = (s: DialogPopupState): ReactNode => {\n if (!isLibrary) return resolveStateValue(children, s);\n if (keepMounted === true) {\n // The mode changes with committed state, inside the Root's transition (conventions 5.5);\n // it stays visible while an exit animation runs.\n return (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>{resolveStateValue(children, s)}</Activity>\n );\n }\n return visible ? resolveStateValue(children, s) : null;\n };\n\n const libraryProps: ComponentPropsWithRef<\"dialog\"> & DataAttributes = {\n id: ids.dialog,\n // Never rendered: removing `open` by hand hides a modal dialog without `close` and leaves\n // `:modal` true (research). Owned so a `render` element cannot add it.\n open: undefined,\n closedby: experimental_closedBy ? (lightDismiss ? \"any\" : \"closerequest\") : undefined,\n \"aria-labelledby\": ids.title,\n \"aria-describedby\": ids.description,\n \"data-modal\": dataAttr(modal),\n // A pre-hydration open leaves an `open` attribute the server did not render (design 2.4).\n suppressHydrationWarning: true,\n };\n\n const element = useRender<ComponentPropsWithRef<\"dialog\">, DialogPopupState>({\n defaultElement: \"dialog\",\n render,\n state,\n className,\n style,\n children: content,\n props: rest,\n libraryProps,\n ref: libraryRef,\n });\n\n return useTransitionBoundary(\"Dialog.Popup\", isLibrary ? transition : undefined, element);\n}\n\nDialogPopup.displayName = \"DialogPopup\";\n"],"mappings":"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"}
|
|
@@ -53,9 +53,15 @@ function DialogRoot({ open, defaultOpen, openChangeAction, modal = true, childre
|
|
|
53
53
|
closeIntent.current = void 0;
|
|
54
54
|
return intent;
|
|
55
55
|
}, []);
|
|
56
|
+
const generation = useRef(0);
|
|
57
|
+
const openGeneration = useCallback(() => generation.current, []);
|
|
58
|
+
const nextOpenGeneration = useCallback(() => {
|
|
59
|
+
generation.current++;
|
|
60
|
+
}, []);
|
|
56
61
|
const intended = () => latestInFlight()?.next ?? optimistic;
|
|
57
62
|
const requestOpen = (event, reason) => {
|
|
58
63
|
if (!isLibrary || intended()) return;
|
|
64
|
+
nextOpenGeneration();
|
|
59
65
|
dispatch(true, createActionDetails(event, {
|
|
60
66
|
reason,
|
|
61
67
|
returnValue: void 0
|
|
@@ -87,7 +93,9 @@ function DialogRoot({ open, defaultOpen, openChangeAction, modal = true, childre
|
|
|
87
93
|
triggerRef,
|
|
88
94
|
dialogRef,
|
|
89
95
|
recordCloseIntent,
|
|
90
|
-
takeCloseIntent
|
|
96
|
+
takeCloseIntent,
|
|
97
|
+
openGeneration,
|
|
98
|
+
nextOpenGeneration
|
|
91
99
|
};
|
|
92
100
|
return /* @__PURE__ */ jsx(DialogContext, {
|
|
93
101
|
value: context,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-root.mjs","names":[],"sources":["../../src/dialog/dialog-root.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useId, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, ChangeReason, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport { DialogContext } from \"./dialog-context.js\";\nimport type { DialogCloseIntent, DialogContextValue, DialogMode } from \"./dialog-context.js\";\n\n/** Details passed as the second argument of `openChangeAction`. */\nexport interface DialogOpenChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user acted on a part: Trigger (open), Close or Form submit (close).\n * `\"dismiss\"`: the user closed the dialog without a part: Escape, Android back, an assistive-\n * technology dismiss gesture, or a click outside (`lightDismiss` / `closedby=\"any\"`).\n * `\"platform\"`: the dialog was found open on mount (opened before hydration); `event` is\n * `undefined`.\n */\n readonly reason: ChangeReason;\n /**\n * On close: `dialog.returnValue`: the `value` of the Close button or the form submitter that\n * closed it, `\"\"` for a dismiss. On open: `undefined`.\n */\n readonly returnValue: string | undefined;\n}\n\nexport interface DialogRootState {\n /** Optimistic open state (`false` in JS-free mode). Content rendered from it must not suspend. */\n readonly open: boolean;\n /** An `openChangeAction` dispatched through this Dialog is in flight (library mode only). */\n readonly pending: boolean;\n readonly modal: boolean;\n}\n\nexport interface DialogRootProps {\n /**\n * Controlled open state. `undefined` means uncontrolled (and, with `defaultOpen` and\n * `openChangeAction` also absent, JS-free mode).\n */\n open?: boolean;\n /**\n * Initial open state in uncontrolled library mode. The dialog opens after hydration, never in\n * the server HTML.\n */\n defaultOpen?: boolean;\n /**\n * Awaited for `true` (the dialog opens when it resolves and the content has rendered); a\n * notification for `false` (the dialog is already closed; the action runs inside the Root's\n * transition). May be a Server Function. Rejections reach the nearest Error Boundary above\n * Root.\n *\n * A controlled parent that does not adopt `false` gets the dialog re-opened once the action\n * settles: closing is the browser's, this is not a veto.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation).\n */\n openChangeAction?: ChangeAction<boolean, DialogOpenChangeDetails>;\n /** `showModal()` (top layer, backdrop, inert page, Escape) or `show()`. Default `true`. */\n modal?: boolean;\n children?: StateValue<ReactNode, DialogRootState>;\n}\n\n/** How long a recorded close intent survives when the dialog did not close (design 2.3). */\nfunction clearLater(\n intentRef: { current: DialogCloseIntent | undefined },\n intent: DialogCloseIntent,\n dialogRef: { current: HTMLDialogElement | null },\n): void {\n setTimeout(() => {\n // The dialog closed: its `close` event (queued as a task) consumes the intent. Still open:\n // the close was cancelled (a prevented `cancel` or `command`), so the intent is stale.\n if (intentRef.current === intent && dialogRef.current?.open === true) {\n intentRef.current = undefined;\n }\n }, 0);\n}\n\n/**\n * Owns the ids, the mode, the open state and the context. Renders no DOM, so it has no `ref`,\n * `className` or `style`; `pending` is exposed through `children(state)` and the Trigger.\n *\n * Without `open`, `defaultOpen` and `openChangeAction` the Dialog runs in **JS-free mode**: the\n * Trigger is a native `command=\"show-modal\"` invoker and the browser owns everything. With any of\n * them it runs in **library mode**: opening is a transition (the Trigger shows `data-pending`,\n * content renders from committed state, `showModal()` runs when it has committed); closing is\n * the browser's and `openChangeAction(false)` is a notification.\n */\nexport function DialogRoot({\n open,\n defaultOpen,\n openChangeAction,\n modal = true,\n children,\n}: DialogRootProps) {\n // Mode is decided once, from the first render's props (design 14).\n const isLibraryNow =\n open !== undefined || defaultOpen !== undefined || openChangeAction !== undefined;\n const [mode] = useState<DialogMode>(isLibraryNow ? \"library\" : \"static\");\n const isLibrary = mode === \"library\";\n useDevWarning(\n isLibrary !== isLibraryNow,\n \"Dialog.Root: the mode changed after mount (`open`, `defaultOpen` and `openChangeAction` \" +\n \"were all absent on one render and present on another). Switching between JS-free and \" +\n \"library mode is not supported.\",\n );\n\n // Hooks cannot be conditional: static mode calls the hook too and never dispatches.\n const { committed, optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n boolean,\n DialogOpenChangeDetails\n >({\n controlled: isLibrary ? open : undefined,\n defaultValue: isLibrary ? defaultOpen : undefined,\n initialValue: false,\n action: isLibrary ? openChangeAction : undefined,\n transitionTypes: (next) => transitionTypes(\"dialog\", next ? \"open\" : \"close\"),\n warn: { component: \"Dialog.Root\", prop: \"open\" },\n });\n\n const id = useId();\n const triggerRef = useRef<HTMLElement | null>(null);\n const dialogRef = useRef<HTMLDialogElement | null>(null);\n const closeIntent = useRef<DialogCloseIntent | undefined>(undefined);\n\n const recordCloseIntent = useCallback((intent: DialogCloseIntent | undefined) => {\n closeIntent.current = intent;\n if (intent !== undefined) clearLater(closeIntent, intent, dialogRef);\n }, []);\n const takeCloseIntent = useCallback(() => {\n const intent = closeIntent.current;\n closeIntent.current = undefined;\n return intent;\n }, []);\n\n // What the user already intends: the latest in-flight dispatch, else the optimistic value\n // (conventions 3.8 rule 1). Judging no-ops against it also deduplicates the StrictMode re-run\n // of the mount reconciliation and recognises the library's own `close()` echoing back.\n const intended = (): boolean => latestInFlight()?.next ?? optimistic;\n\n const requestOpen = (event: Event | undefined, reason: ChangeReason): void => {\n if (!isLibrary || intended()) return;\n dispatch(true, createActionDetails(event, { reason, returnValue: undefined }));\n };\n\n const notifyClosed = (event: Event, reason: ChangeReason, returnValue: string): void => {\n if (!isLibrary || !intended()) return;\n dispatch(false, createActionDetails(event, { reason, returnValue }));\n };\n\n const openState = isLibrary && optimistic;\n const pendingState = isLibrary && pending;\n\n // Not memoised: `dispatch` is a new function on every render of the Root (Accordion does the\n // same), so parts re-render with the Root, which they do anyway when its state changes.\n const context: DialogContextValue = {\n mode,\n ids: { dialog: id, title: `${id}-title`, description: `${id}-description` },\n modal,\n committed: isLibrary && committed,\n optimistic: openState,\n pending: pendingState,\n requestOpen,\n notifyClosed,\n latestInFlight,\n triggerRef,\n dialogRef,\n recordCloseIntent,\n takeCloseIntent,\n };\n\n const state: DialogRootState = {\n open: openState,\n pending: pendingState,\n modal,\n };\n\n return <DialogContext value={context}>{resolveStateValue(children, state)}</DialogContext>;\n}\n\nDialogRoot.displayName = \"DialogRoot\";\n"],"mappings":";;;;;;;;;;;AAmEA,SAAS,WACP,WACA,QACA,WACM;CACN,iBAAiB;EAGf,IAAI,UAAU,YAAY,UAAU,UAAU,SAAS,SAAS,MAC9D,UAAU,UAAU,KAAA;CAExB,GAAG,CAAC;AACN;;;;;;;;;;;AAYA,SAAgB,WAAW,EACzB,MACA,aACA,kBACA,QAAQ,MACR,YACkB;CAElB,MAAM,eACJ,SAAS,KAAA,KAAa,gBAAgB,KAAA,KAAa,qBAAqB,KAAA;CAC1E,MAAM,CAAC,QAAQ,SAAqB,eAAe,YAAY,QAAQ;CACvE,MAAM,YAAY,SAAS;CAC3B,cACE,cAAc,cACd,6MAGF;CAGA,MAAM,EAAE,WAAW,YAAY,SAAS,UAAU,mBAAmB,sBAGnE;EACA,YAAY,YAAY,OAAO,KAAA;EAC/B,cAAc,YAAY,cAAc,KAAA;EACxC,cAAc;EACd,QAAQ,YAAY,mBAAmB,KAAA;EACvC,kBAAkB,SAAS,gBAAgB,UAAU,OAAO,SAAS,OAAO;EAC5E,MAAM;GAAE,WAAW;GAAe,MAAM;EAAO;CACjD,CAAC;CAED,MAAM,KAAK,MAAM;CACjB,MAAM,aAAa,OAA2B,IAAI;CAClD,MAAM,YAAY,OAAiC,IAAI;CACvD,MAAM,cAAc,OAAsC,KAAA,CAAS;CAEnE,MAAM,oBAAoB,aAAa,WAA0C;EAC/E,YAAY,UAAU;EACtB,IAAI,WAAW,KAAA,GAAW,WAAW,aAAa,QAAQ,SAAS;CACrE,GAAG,CAAC,CAAC;CACL,MAAM,kBAAkB,kBAAkB;EACxC,MAAM,SAAS,YAAY;EAC3B,YAAY,UAAU,KAAA;EACtB,OAAO;CACT,GAAG,CAAC,CAAC;CAKL,MAAM,iBAA0B,eAAe,CAAC,EAAE,QAAQ;CAE1D,MAAM,eAAe,OAA0B,WAA+B;EAC5E,IAAI,CAAC,aAAa,SAAS,GAAG;EAC9B,SAAS,MAAM,oBAAoB,OAAO;GAAE;GAAQ,aAAa,KAAA;EAAU,CAAC,CAAC;CAC/E;CAEA,MAAM,gBAAgB,OAAc,QAAsB,gBAA8B;EACtF,IAAI,CAAC,aAAa,CAAC,SAAS,GAAG;EAC/B,SAAS,OAAO,oBAAoB,OAAO;GAAE;GAAQ;EAAY,CAAC,CAAC;CACrE;CAEA,MAAM,YAAY,aAAa;CAC/B,MAAM,eAAe,aAAa;CAIlC,MAAM,UAA8B;EAClC;EACA,KAAK;GAAE,QAAQ;GAAI,OAAO,GAAG,GAAG;GAAS,aAAa,GAAG,GAAG;EAAc;EAC1E;EACA,WAAW,aAAa;EACxB,YAAY;EACZ,SAAS;EACT;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CAQA,OAAO,oBAAC,eAAD;EAAe,OAAO;EAAU,UAAA,kBAAkB,UAAU;GALjE,MAAM;GACN,SAAS;GACT;EAGqE,CAAC;CAAiB,CAAA;AAC3F;AAEA,WAAW,cAAc"}
|
|
1
|
+
{"version":3,"file":"dialog-root.mjs","names":[],"sources":["../../src/dialog/dialog-root.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useId, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, ChangeReason, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport { DialogContext } from \"./dialog-context.js\";\nimport type { DialogCloseIntent, DialogContextValue, DialogMode } from \"./dialog-context.js\";\n\n/** Details passed as the second argument of `openChangeAction`. */\nexport interface DialogOpenChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user acted on a part: Trigger (open), Close or Form submit (close).\n * `\"dismiss\"`: the user closed the dialog without a part: Escape, Android back, an assistive-\n * technology dismiss gesture, or a click outside (`lightDismiss` / `closedby=\"any\"`).\n * `\"platform\"`: the dialog was found open on mount (opened before hydration); `event` is\n * `undefined`.\n */\n readonly reason: ChangeReason;\n /**\n * On close: `dialog.returnValue`: the `value` of the Close button or the form submitter that\n * closed it, `\"\"` for a dismiss. On open: `undefined`.\n */\n readonly returnValue: string | undefined;\n}\n\nexport interface DialogRootState {\n /** Optimistic open state (`false` in JS-free mode). Content rendered from it must not suspend. */\n readonly open: boolean;\n /** An `openChangeAction` dispatched through this Dialog is in flight (library mode only). */\n readonly pending: boolean;\n readonly modal: boolean;\n}\n\nexport interface DialogRootProps {\n /**\n * Controlled open state. `undefined` means uncontrolled (and, with `defaultOpen` and\n * `openChangeAction` also absent, JS-free mode).\n */\n open?: boolean;\n /**\n * Initial open state in uncontrolled library mode. The dialog opens after hydration, never in\n * the server HTML.\n */\n defaultOpen?: boolean;\n /**\n * Awaited for `true` (the dialog opens when it resolves and the content has rendered); a\n * notification for `false` (the dialog is already closed; the action runs inside the Root's\n * transition). May be a Server Function. Rejections reach the nearest Error Boundary above\n * Root.\n *\n * A controlled parent that does not adopt `false` gets the dialog re-opened once the action\n * settles: closing is the browser's, this is not a veto.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation).\n */\n openChangeAction?: ChangeAction<boolean, DialogOpenChangeDetails>;\n /** `showModal()` (top layer, backdrop, inert page, Escape) or `show()`. Default `true`. */\n modal?: boolean;\n children?: StateValue<ReactNode, DialogRootState>;\n}\n\n/** How long a recorded close intent survives when the dialog did not close (design 2.3). */\nfunction clearLater(\n intentRef: { current: DialogCloseIntent | undefined },\n intent: DialogCloseIntent,\n dialogRef: { current: HTMLDialogElement | null },\n): void {\n setTimeout(() => {\n // The dialog closed: its `close` event (queued as a task) consumes the intent. Still open:\n // the close was cancelled (a prevented `cancel` or `command`), so the intent is stale.\n if (intentRef.current === intent && dialogRef.current?.open === true) {\n intentRef.current = undefined;\n }\n }, 0);\n}\n\n/**\n * Owns the ids, the mode, the open state and the context. Renders no DOM, so it has no `ref`,\n * `className` or `style`; `pending` is exposed through `children(state)` and the Trigger.\n *\n * Without `open`, `defaultOpen` and `openChangeAction` the Dialog runs in **JS-free mode**: the\n * Trigger is a native `command=\"show-modal\"` invoker and the browser owns everything. With any of\n * them it runs in **library mode**: opening is a transition (the Trigger shows `data-pending`,\n * content renders from committed state, `showModal()` runs when it has committed); closing is\n * the browser's and `openChangeAction(false)` is a notification.\n */\nexport function DialogRoot({\n open,\n defaultOpen,\n openChangeAction,\n modal = true,\n children,\n}: DialogRootProps) {\n // Mode is decided once, from the first render's props (design 14).\n const isLibraryNow =\n open !== undefined || defaultOpen !== undefined || openChangeAction !== undefined;\n const [mode] = useState<DialogMode>(isLibraryNow ? \"library\" : \"static\");\n const isLibrary = mode === \"library\";\n useDevWarning(\n isLibrary !== isLibraryNow,\n \"Dialog.Root: the mode changed after mount (`open`, `defaultOpen` and `openChangeAction` \" +\n \"were all absent on one render and present on another). Switching between JS-free and \" +\n \"library mode is not supported.\",\n );\n\n // Hooks cannot be conditional: static mode calls the hook too and never dispatches.\n const { committed, optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n boolean,\n DialogOpenChangeDetails\n >({\n controlled: isLibrary ? open : undefined,\n defaultValue: isLibrary ? defaultOpen : undefined,\n initialValue: false,\n action: isLibrary ? openChangeAction : undefined,\n transitionTypes: (next) => transitionTypes(\"dialog\", next ? \"open\" : \"close\"),\n warn: { component: \"Dialog.Root\", prop: \"open\" },\n });\n\n const id = useId();\n const triggerRef = useRef<HTMLElement | null>(null);\n const dialogRef = useRef<HTMLDialogElement | null>(null);\n const closeIntent = useRef<DialogCloseIntent | undefined>(undefined);\n\n const recordCloseIntent = useCallback((intent: DialogCloseIntent | undefined) => {\n closeIntent.current = intent;\n if (intent !== undefined) clearLater(closeIntent, intent, dialogRef);\n }, []);\n const takeCloseIntent = useCallback(() => {\n const intent = closeIntent.current;\n closeIntent.current = undefined;\n return intent;\n }, []);\n // Advanced on every open (the open dispatch and the Popup's native `beforetoggle` to open), so a Close action that\n // settles after the user dismissed and re-opened the dialog does not close the new open (the\n // PO's open-generation rule, design/components/popover.md 3.4; review M2).\n const generation = useRef(0);\n const openGeneration = useCallback(() => generation.current, []);\n const nextOpenGeneration = useCallback(() => {\n generation.current++;\n }, []);\n\n // What the user already intends: the latest in-flight dispatch, else the optimistic value\n // (conventions 3.8 rule 1). Judging no-ops against it also deduplicates the StrictMode re-run\n // of the mount reconciliation and recognises the library's own `close()` echoing back.\n const intended = (): boolean => latestInFlight()?.next ?? optimistic;\n\n const requestOpen = (event: Event | undefined, reason: ChangeReason): void => {\n if (!isLibrary || intended()) return;\n nextOpenGeneration();\n dispatch(true, createActionDetails(event, { reason, returnValue: undefined }));\n };\n\n const notifyClosed = (event: Event, reason: ChangeReason, returnValue: string): void => {\n if (!isLibrary || !intended()) return;\n dispatch(false, createActionDetails(event, { reason, returnValue }));\n };\n\n const openState = isLibrary && optimistic;\n const pendingState = isLibrary && pending;\n\n // Not memoised: `dispatch` is a new function on every render of the Root (Accordion does the\n // same), so parts re-render with the Root, which they do anyway when its state changes.\n const context: DialogContextValue = {\n mode,\n ids: { dialog: id, title: `${id}-title`, description: `${id}-description` },\n modal,\n committed: isLibrary && committed,\n optimistic: openState,\n pending: pendingState,\n requestOpen,\n notifyClosed,\n latestInFlight,\n triggerRef,\n dialogRef,\n recordCloseIntent,\n takeCloseIntent,\n openGeneration,\n nextOpenGeneration,\n };\n\n const state: DialogRootState = {\n open: openState,\n pending: pendingState,\n modal,\n };\n\n return <DialogContext value={context}>{resolveStateValue(children, state)}</DialogContext>;\n}\n\nDialogRoot.displayName = \"DialogRoot\";\n"],"mappings":";;;;;;;;;;;AAmEA,SAAS,WACP,WACA,QACA,WACM;CACN,iBAAiB;EAGf,IAAI,UAAU,YAAY,UAAU,UAAU,SAAS,SAAS,MAC9D,UAAU,UAAU,KAAA;CAExB,GAAG,CAAC;AACN;;;;;;;;;;;AAYA,SAAgB,WAAW,EACzB,MACA,aACA,kBACA,QAAQ,MACR,YACkB;CAElB,MAAM,eACJ,SAAS,KAAA,KAAa,gBAAgB,KAAA,KAAa,qBAAqB,KAAA;CAC1E,MAAM,CAAC,QAAQ,SAAqB,eAAe,YAAY,QAAQ;CACvE,MAAM,YAAY,SAAS;CAC3B,cACE,cAAc,cACd,6MAGF;CAGA,MAAM,EAAE,WAAW,YAAY,SAAS,UAAU,mBAAmB,sBAGnE;EACA,YAAY,YAAY,OAAO,KAAA;EAC/B,cAAc,YAAY,cAAc,KAAA;EACxC,cAAc;EACd,QAAQ,YAAY,mBAAmB,KAAA;EACvC,kBAAkB,SAAS,gBAAgB,UAAU,OAAO,SAAS,OAAO;EAC5E,MAAM;GAAE,WAAW;GAAe,MAAM;EAAO;CACjD,CAAC;CAED,MAAM,KAAK,MAAM;CACjB,MAAM,aAAa,OAA2B,IAAI;CAClD,MAAM,YAAY,OAAiC,IAAI;CACvD,MAAM,cAAc,OAAsC,KAAA,CAAS;CAEnE,MAAM,oBAAoB,aAAa,WAA0C;EAC/E,YAAY,UAAU;EACtB,IAAI,WAAW,KAAA,GAAW,WAAW,aAAa,QAAQ,SAAS;CACrE,GAAG,CAAC,CAAC;CACL,MAAM,kBAAkB,kBAAkB;EACxC,MAAM,SAAS,YAAY;EAC3B,YAAY,UAAU,KAAA;EACtB,OAAO;CACT,GAAG,CAAC,CAAC;CAIL,MAAM,aAAa,OAAO,CAAC;CAC3B,MAAM,iBAAiB,kBAAkB,WAAW,SAAS,CAAC,CAAC;CAC/D,MAAM,qBAAqB,kBAAkB;EAC3C,WAAW;CACb,GAAG,CAAC,CAAC;CAKL,MAAM,iBAA0B,eAAe,CAAC,EAAE,QAAQ;CAE1D,MAAM,eAAe,OAA0B,WAA+B;EAC5E,IAAI,CAAC,aAAa,SAAS,GAAG;EAC9B,mBAAmB;EACnB,SAAS,MAAM,oBAAoB,OAAO;GAAE;GAAQ,aAAa,KAAA;EAAU,CAAC,CAAC;CAC/E;CAEA,MAAM,gBAAgB,OAAc,QAAsB,gBAA8B;EACtF,IAAI,CAAC,aAAa,CAAC,SAAS,GAAG;EAC/B,SAAS,OAAO,oBAAoB,OAAO;GAAE;GAAQ;EAAY,CAAC,CAAC;CACrE;CAEA,MAAM,YAAY,aAAa;CAC/B,MAAM,eAAe,aAAa;CAIlC,MAAM,UAA8B;EAClC;EACA,KAAK;GAAE,QAAQ;GAAI,OAAO,GAAG,GAAG;GAAS,aAAa,GAAG,GAAG;EAAc;EAC1E;EACA,WAAW,aAAa;EACxB,YAAY;EACZ,SAAS;EACT;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CAQA,OAAO,oBAAC,eAAD;EAAe,OAAO;EAAU,UAAA,kBAAkB,UAAU;GALjE,MAAM;GACN,SAAS;GACT;EAGqE,CAAC;CAAiB,CAAA;AAC3F;AAEA,WAAW,cAAc"}
|