@funstack/ui 0.0.0-stage → 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +42 -2
- package/dist/accordion/accordion-context.d.mts +5 -0
- package/dist/accordion/accordion-context.d.mts.map +1 -0
- package/dist/accordion/accordion-context.mjs +9 -0
- package/dist/accordion/accordion-context.mjs.map +1 -0
- package/dist/accordion/accordion-item.d.mts +53 -0
- package/dist/accordion/accordion-item.d.mts.map +1 -0
- package/dist/accordion/accordion-item.mjs +85 -0
- package/dist/accordion/accordion-item.mjs.map +1 -0
- package/dist/accordion/accordion-panel.d.mts +40 -0
- package/dist/accordion/accordion-panel.d.mts.map +1 -0
- package/dist/accordion/accordion-panel.mjs +40 -0
- package/dist/accordion/accordion-panel.mjs.map +1 -0
- package/dist/accordion/accordion-root.d.mts +90 -0
- package/dist/accordion/accordion-root.d.mts.map +1 -0
- package/dist/accordion/accordion-root.mjs +229 -0
- package/dist/accordion/accordion-root.mjs.map +1 -0
- package/dist/accordion/accordion-trigger.d.mts +37 -0
- package/dist/accordion/accordion-trigger.d.mts.map +1 -0
- package/dist/accordion/accordion-trigger.mjs +81 -0
- package/dist/accordion/accordion-trigger.mjs.map +1 -0
- package/dist/accordion/index.d.mts +20 -0
- package/dist/accordion/index.d.mts.map +1 -0
- package/dist/accordion/index.mjs +20 -0
- package/dist/accordion/index.mjs.map +1 -0
- package/dist/checkbox/checkbox.d.mts +92 -0
- package/dist/checkbox/checkbox.d.mts.map +1 -0
- package/dist/checkbox/checkbox.mjs +82 -0
- package/dist/checkbox/checkbox.mjs.map +1 -0
- package/dist/checkbox/index.d.mts +3 -0
- package/dist/checkbox/index.mjs +1 -0
- package/dist/checkbox-group/checkbox-group-context.mjs +24 -0
- package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-item.d.mts +55 -0
- package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-item.mjs +90 -0
- package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-label.d.mts +21 -0
- package/dist/checkbox-group/checkbox-group-label.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-label.mjs +30 -0
- package/dist/checkbox-group/checkbox-group-label.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-parent.d.mts +68 -0
- package/dist/checkbox-group/checkbox-group-parent.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-parent.mjs +145 -0
- package/dist/checkbox-group/checkbox-group-parent.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-root.d.mts +100 -0
- package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-root.mjs +291 -0
- package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -0
- package/dist/checkbox-group/index.d.mts +23 -0
- package/dist/checkbox-group/index.d.mts.map +1 -0
- package/dist/checkbox-group/index.mjs +23 -0
- package/dist/checkbox-group/index.mjs.map +1 -0
- package/dist/dialog/dialog-close.d.mts +44 -0
- package/dist/dialog/dialog-close.d.mts.map +1 -0
- package/dist/dialog/dialog-close.mjs +76 -0
- package/dist/dialog/dialog-close.mjs.map +1 -0
- package/dist/dialog/dialog-context.mjs +13 -0
- package/dist/dialog/dialog-context.mjs.map +1 -0
- package/dist/dialog/dialog-description.d.mts +22 -0
- package/dist/dialog/dialog-description.d.mts.map +1 -0
- package/dist/dialog/dialog-description.mjs +31 -0
- package/dist/dialog/dialog-description.mjs.map +1 -0
- package/dist/dialog/dialog-form.d.mts +58 -0
- package/dist/dialog/dialog-form.d.mts.map +1 -0
- package/dist/dialog/dialog-form.mjs +108 -0
- package/dist/dialog/dialog-form.mjs.map +1 -0
- package/dist/dialog/dialog-popup.d.mts +81 -0
- package/dist/dialog/dialog-popup.d.mts.map +1 -0
- package/dist/dialog/dialog-popup.mjs +367 -0
- package/dist/dialog/dialog-popup.mjs.map +1 -0
- package/dist/dialog/dialog-root.d.mts +70 -0
- package/dist/dialog/dialog-root.d.mts.map +1 -0
- package/dist/dialog/dialog-root.mjs +113 -0
- package/dist/dialog/dialog-root.mjs.map +1 -0
- package/dist/dialog/dialog-title.d.mts +23 -0
- package/dist/dialog/dialog-title.d.mts.map +1 -0
- package/dist/dialog/dialog-title.mjs +31 -0
- package/dist/dialog/dialog-title.mjs.map +1 -0
- package/dist/dialog/dialog-trigger.d.mts +39 -0
- package/dist/dialog/dialog-trigger.d.mts.map +1 -0
- package/dist/dialog/dialog-trigger.mjs +80 -0
- package/dist/dialog/dialog-trigger.mjs.map +1 -0
- package/dist/dialog/index.d.mts +28 -0
- package/dist/dialog/index.d.mts.map +1 -0
- package/dist/dialog/index.mjs +28 -0
- package/dist/dialog/index.mjs.map +1 -0
- package/dist/index.d.mts +60 -0
- package/dist/index.mjs +12 -0
- package/dist/internal/action-details.mjs +47 -0
- package/dist/internal/action-details.mjs.map +1 -0
- package/dist/internal/anchor-position.d.mts +7 -0
- package/dist/internal/anchor-position.d.mts.map +1 -0
- package/dist/internal/anchor-position.mjs +16 -0
- package/dist/internal/anchor-position.mjs.map +1 -0
- package/dist/internal/data-attr.mjs +13 -0
- package/dist/internal/data-attr.mjs.map +1 -0
- package/dist/internal/merge-props.mjs +119 -0
- package/dist/internal/merge-props.mjs.map +1 -0
- package/dist/internal/platform-attributes.d.mts +55 -0
- package/dist/internal/platform-attributes.d.mts.map +1 -0
- package/dist/internal/sync-presence.mjs +19 -0
- package/dist/internal/sync-presence.mjs.map +1 -0
- package/dist/internal/transition-boundary.mjs +52 -0
- package/dist/internal/transition-boundary.mjs.map +1 -0
- package/dist/internal/transition-types.mjs +15 -0
- package/dist/internal/transition-types.mjs.map +1 -0
- package/dist/internal/types.d.mts +38 -0
- package/dist/internal/types.d.mts.map +1 -0
- package/dist/internal/use-action-trigger.mjs +58 -0
- package/dist/internal/use-action-trigger.mjs.map +1 -0
- package/dist/internal/use-controllable-action.mjs +115 -0
- package/dist/internal/use-controllable-action.mjs.map +1 -0
- package/dist/internal/use-delayed-pair.mjs +40 -0
- package/dist/internal/use-delayed-pair.mjs.map +1 -0
- package/dist/internal/use-dev-warning.mjs +21 -0
- package/dist/internal/use-dev-warning.mjs.map +1 -0
- package/dist/internal/use-exit-animation.mjs +55 -0
- package/dist/internal/use-exit-animation.mjs.map +1 -0
- package/dist/internal/use-form-reset.mjs +50 -0
- package/dist/internal/use-form-reset.mjs.map +1 -0
- package/dist/internal/use-native-checked.mjs +113 -0
- package/dist/internal/use-native-checked.mjs.map +1 -0
- package/dist/internal/use-native-group-reconciliation.mjs +64 -0
- package/dist/internal/use-native-group-reconciliation.mjs.map +1 -0
- package/dist/internal/use-popover-popup.mjs +274 -0
- package/dist/internal/use-popover-popup.mjs.map +1 -0
- package/dist/internal/use-popover-root.d.mts +2 -0
- package/dist/internal/use-popover-root.mjs +112 -0
- package/dist/internal/use-popover-root.mjs.map +1 -0
- package/dist/internal/use-popover-trigger.mjs +86 -0
- package/dist/internal/use-popover-trigger.mjs.map +1 -0
- package/dist/internal/use-render.mjs +51 -0
- package/dist/internal/use-render.mjs.map +1 -0
- package/dist/internal/use-until-found.mjs +40 -0
- package/dist/internal/use-until-found.mjs.map +1 -0
- package/dist/menu/index.d.mts +34 -0
- package/dist/menu/index.d.mts.map +1 -0
- package/dist/menu/index.mjs +34 -0
- package/dist/menu/index.mjs.map +1 -0
- package/dist/menu/menu-checkbox-item.d.mts +42 -0
- package/dist/menu/menu-checkbox-item.d.mts.map +1 -0
- package/dist/menu/menu-checkbox-item.mjs +71 -0
- package/dist/menu/menu-checkbox-item.mjs.map +1 -0
- package/dist/menu/menu-context.d.mts +1 -0
- package/dist/menu/menu-context.mjs +28 -0
- package/dist/menu/menu-context.mjs.map +1 -0
- package/dist/menu/menu-dom.mjs +37 -0
- package/dist/menu/menu-dom.mjs.map +1 -0
- package/dist/menu/menu-group.d.mts +48 -0
- package/dist/menu/menu-group.d.mts.map +1 -0
- package/dist/menu/menu-group.mjs +69 -0
- package/dist/menu/menu-group.mjs.map +1 -0
- package/dist/menu/menu-hover.mjs +29 -0
- package/dist/menu/menu-hover.mjs.map +1 -0
- package/dist/menu/menu-item.d.mts +45 -0
- package/dist/menu/menu-item.d.mts.map +1 -0
- package/dist/menu/menu-item.mjs +54 -0
- package/dist/menu/menu-item.mjs.map +1 -0
- package/dist/menu/menu-popup.d.mts +69 -0
- package/dist/menu/menu-popup.d.mts.map +1 -0
- package/dist/menu/menu-popup.mjs +207 -0
- package/dist/menu/menu-popup.mjs.map +1 -0
- package/dist/menu/menu-radio-group.d.mts +37 -0
- package/dist/menu/menu-radio-group.d.mts.map +1 -0
- package/dist/menu/menu-radio-group.mjs +72 -0
- package/dist/menu/menu-radio-group.mjs.map +1 -0
- package/dist/menu/menu-radio-item.d.mts +33 -0
- package/dist/menu/menu-radio-item.d.mts.map +1 -0
- package/dist/menu/menu-radio-item.mjs +62 -0
- package/dist/menu/menu-radio-item.mjs.map +1 -0
- package/dist/menu/menu-root.d.mts +54 -0
- package/dist/menu/menu-root.d.mts.map +1 -0
- package/dist/menu/menu-root.mjs +102 -0
- package/dist/menu/menu-root.mjs.map +1 -0
- package/dist/menu/menu-submenu-trigger.d.mts +31 -0
- package/dist/menu/menu-submenu-trigger.d.mts.map +1 -0
- package/dist/menu/menu-submenu-trigger.mjs +92 -0
- package/dist/menu/menu-submenu-trigger.mjs.map +1 -0
- package/dist/menu/menu-trigger.d.mts +36 -0
- package/dist/menu/menu-trigger.d.mts.map +1 -0
- package/dist/menu/menu-trigger.mjs +81 -0
- package/dist/menu/menu-trigger.mjs.map +1 -0
- package/dist/menu/use-menu-item.mjs +54 -0
- package/dist/menu/use-menu-item.mjs.map +1 -0
- package/dist/popover/index.d.mts +28 -0
- package/dist/popover/index.d.mts.map +1 -0
- package/dist/popover/index.mjs +28 -0
- package/dist/popover/index.mjs.map +1 -0
- package/dist/popover/popover-close.d.mts +41 -0
- package/dist/popover/popover-close.d.mts.map +1 -0
- package/dist/popover/popover-close.mjs +90 -0
- package/dist/popover/popover-close.mjs.map +1 -0
- package/dist/popover/popover-context.mjs +26 -0
- package/dist/popover/popover-context.mjs.map +1 -0
- package/dist/popover/popover-description.d.mts +22 -0
- package/dist/popover/popover-description.d.mts.map +1 -0
- package/dist/popover/popover-description.mjs +28 -0
- package/dist/popover/popover-description.mjs.map +1 -0
- package/dist/popover/popover-popup.d.mts +82 -0
- package/dist/popover/popover-popup.d.mts.map +1 -0
- package/dist/popover/popover-popup.mjs +93 -0
- package/dist/popover/popover-popup.mjs.map +1 -0
- package/dist/popover/popover-root.d.mts +64 -0
- package/dist/popover/popover-root.d.mts.map +1 -0
- package/dist/popover/popover-root.mjs +39 -0
- package/dist/popover/popover-root.mjs.map +1 -0
- package/dist/popover/popover-title.d.mts +23 -0
- package/dist/popover/popover-title.d.mts.map +1 -0
- package/dist/popover/popover-title.mjs +28 -0
- package/dist/popover/popover-title.mjs.map +1 -0
- package/dist/popover/popover-trigger.d.mts +37 -0
- package/dist/popover/popover-trigger.d.mts.map +1 -0
- package/dist/popover/popover-trigger.mjs +40 -0
- package/dist/popover/popover-trigger.mjs.map +1 -0
- package/dist/radio-group/index.d.mts +20 -0
- package/dist/radio-group/index.d.mts.map +1 -0
- package/dist/radio-group/index.mjs +20 -0
- package/dist/radio-group/index.mjs.map +1 -0
- package/dist/radio-group/radio-group-context.mjs +13 -0
- package/dist/radio-group/radio-group-context.mjs.map +1 -0
- package/dist/radio-group/radio-group-item.d.mts +54 -0
- package/dist/radio-group/radio-group-item.d.mts.map +1 -0
- package/dist/radio-group/radio-group-item.mjs +83 -0
- package/dist/radio-group/radio-group-item.mjs.map +1 -0
- package/dist/radio-group/radio-group-label.d.mts +21 -0
- package/dist/radio-group/radio-group-label.d.mts.map +1 -0
- package/dist/radio-group/radio-group-label.mjs +30 -0
- package/dist/radio-group/radio-group-label.mjs.map +1 -0
- package/dist/radio-group/radio-group-root.d.mts +92 -0
- package/dist/radio-group/radio-group-root.d.mts.map +1 -0
- package/dist/radio-group/radio-group-root.mjs +137 -0
- package/dist/radio-group/radio-group-root.mjs.map +1 -0
- package/dist/switch/index.d.mts +3 -0
- package/dist/switch/index.mjs +1 -0
- package/dist/switch/switch.d.mts +78 -0
- package/dist/switch/switch.d.mts.map +1 -0
- package/dist/switch/switch.mjs +77 -0
- package/dist/switch/switch.mjs.map +1 -0
- package/dist/tabs/index.d.mts +24 -0
- package/dist/tabs/index.d.mts.map +1 -0
- package/dist/tabs/index.mjs +23 -0
- package/dist/tabs/index.mjs.map +1 -0
- package/dist/tabs/tabs-context.d.mts +7 -0
- package/dist/tabs/tabs-context.d.mts.map +1 -0
- package/dist/tabs/tabs-context.mjs +14 -0
- package/dist/tabs/tabs-context.mjs.map +1 -0
- package/dist/tabs/tabs-ids.mjs +39 -0
- package/dist/tabs/tabs-ids.mjs.map +1 -0
- package/dist/tabs/tabs-indicator.d.mts +32 -0
- package/dist/tabs/tabs-indicator.d.mts.map +1 -0
- package/dist/tabs/tabs-indicator.mjs +49 -0
- package/dist/tabs/tabs-indicator.mjs.map +1 -0
- package/dist/tabs/tabs-list.d.mts +34 -0
- package/dist/tabs/tabs-list.d.mts.map +1 -0
- package/dist/tabs/tabs-list.mjs +130 -0
- package/dist/tabs/tabs-list.mjs.map +1 -0
- package/dist/tabs/tabs-panel.d.mts +50 -0
- package/dist/tabs/tabs-panel.d.mts.map +1 -0
- package/dist/tabs/tabs-panel.mjs +81 -0
- package/dist/tabs/tabs-panel.mjs.map +1 -0
- package/dist/tabs/tabs-root.d.mts +64 -0
- package/dist/tabs/tabs-root.d.mts.map +1 -0
- package/dist/tabs/tabs-root.mjs +90 -0
- package/dist/tabs/tabs-root.mjs.map +1 -0
- package/dist/tabs/tabs-tab.d.mts +49 -0
- package/dist/tabs/tabs-tab.d.mts.map +1 -0
- package/dist/tabs/tabs-tab.mjs +89 -0
- package/dist/tabs/tabs-tab.mjs.map +1 -0
- package/dist/toggle/index.d.mts +3 -0
- package/dist/toggle/index.mjs +1 -0
- package/dist/toggle/toggle.d.mts +57 -0
- package/dist/toggle/toggle.d.mts.map +1 -0
- package/dist/toggle/toggle.mjs +59 -0
- package/dist/toggle/toggle.mjs.map +1 -0
- package/dist/tooltip/index.d.mts +20 -0
- package/dist/tooltip/index.d.mts.map +1 -0
- package/dist/tooltip/index.mjs +19 -0
- package/dist/tooltip/index.mjs.map +1 -0
- package/dist/tooltip/tooltip-context.d.mts +5 -0
- package/dist/tooltip/tooltip-context.d.mts.map +1 -0
- package/dist/tooltip/tooltip-context.mjs +13 -0
- package/dist/tooltip/tooltip-context.mjs.map +1 -0
- package/dist/tooltip/tooltip-popup.d.mts +49 -0
- package/dist/tooltip/tooltip-popup.d.mts.map +1 -0
- package/dist/tooltip/tooltip-popup.mjs +52 -0
- package/dist/tooltip/tooltip-popup.mjs.map +1 -0
- package/dist/tooltip/tooltip-root.d.mts +41 -0
- package/dist/tooltip/tooltip-root.d.mts.map +1 -0
- package/dist/tooltip/tooltip-root.mjs +86 -0
- package/dist/tooltip/tooltip-root.mjs.map +1 -0
- package/dist/tooltip/tooltip-trigger.d.mts +38 -0
- package/dist/tooltip/tooltip-trigger.d.mts.map +1 -0
- package/dist/tooltip/tooltip-trigger.mjs +55 -0
- package/dist/tooltip/tooltip-trigger.mjs.map +1 -0
- package/dist/tooltip/use-tooltip-interaction.mjs +119 -0
- package/dist/tooltip/use-tooltip-interaction.mjs.map +1 -0
- package/package.json +73 -3
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createActionDetails } from "../internal/action-details.mjs";
|
|
3
|
+
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
|
+
import { rendersButton, useRender } from "../internal/use-render.mjs";
|
|
5
|
+
import { useActionTrigger } from "../internal/use-action-trigger.mjs";
|
|
6
|
+
import { useDialogContext } from "./dialog-context.mjs";
|
|
7
|
+
import { useCallback } from "react";
|
|
8
|
+
//#region src/dialog/dialog-close.tsx
|
|
9
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
10
|
+
/**
|
|
11
|
+
* A close button: `<button type="button" commandfor command="request-close">`. Without `action`
|
|
12
|
+
* the browser closes the dialog (a close request: `cancel`, then `close`), with `value` as the
|
|
13
|
+
* `returnValue`. With `action` the click is cancelled, the action runs in this part's own
|
|
14
|
+
* transition (`data-pending` on the button) and the dialog closes with `close(value)` once it
|
|
15
|
+
* resolves; a rejection leaves the dialog open.
|
|
16
|
+
*
|
|
17
|
+
* Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,
|
|
18
|
+
* `command`, `value`, `onClick` (with `action`), `data-pending`, `data-disabled`.
|
|
19
|
+
*/
|
|
20
|
+
function DialogClose({ action, value, disabled = false, className, style, children, render, ...rest }) {
|
|
21
|
+
const ctx = useDialogContext("Dialog.Close");
|
|
22
|
+
const { pending, run } = useActionTrigger(ctx.openGeneration);
|
|
23
|
+
const { ids, dialogRef, recordCloseIntent } = ctx;
|
|
24
|
+
const hasAction = action !== void 0;
|
|
25
|
+
const state = {
|
|
26
|
+
pending,
|
|
27
|
+
disabled
|
|
28
|
+
};
|
|
29
|
+
const devRef = useCallback((element) => {
|
|
30
|
+
if (isDev && !hasAction && element.tagName !== "BUTTON") console.error(`Dialog.Close: rendered a <${element.tagName.toLowerCase()}> without \`action\`. Only a <button> runs \`command="request-close"\`; this element will not close the dialog.`);
|
|
31
|
+
return () => {};
|
|
32
|
+
}, [hasAction]);
|
|
33
|
+
const onClick = (event) => {
|
|
34
|
+
if (action === void 0) return;
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
if (disabled) return;
|
|
37
|
+
const native = event.nativeEvent;
|
|
38
|
+
const returnValue = value ?? "";
|
|
39
|
+
const details = createActionDetails(native, { returnValue });
|
|
40
|
+
run(() => action(details), () => {
|
|
41
|
+
const dialog = dialogRef.current;
|
|
42
|
+
if (dialog === null || !dialog.open) return;
|
|
43
|
+
recordCloseIntent({
|
|
44
|
+
reason: "activate",
|
|
45
|
+
event: native
|
|
46
|
+
});
|
|
47
|
+
dialog.close(returnValue);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const libraryProps = {
|
|
51
|
+
...rendersButton(render) && { type: "button" },
|
|
52
|
+
disabled,
|
|
53
|
+
commandfor: ids.dialog,
|
|
54
|
+
command: "request-close",
|
|
55
|
+
value,
|
|
56
|
+
...hasAction && { onClick },
|
|
57
|
+
"data-pending": dataAttr(pending),
|
|
58
|
+
"data-disabled": dataAttr(disabled)
|
|
59
|
+
};
|
|
60
|
+
return useRender({
|
|
61
|
+
defaultElement: "button",
|
|
62
|
+
render,
|
|
63
|
+
state,
|
|
64
|
+
className,
|
|
65
|
+
style,
|
|
66
|
+
children,
|
|
67
|
+
props: rest,
|
|
68
|
+
libraryProps,
|
|
69
|
+
ref: devRef
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
DialogClose.displayName = "DialogClose";
|
|
73
|
+
//#endregion
|
|
74
|
+
export { DialogClose };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=dialog-close.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, use } from "react";
|
|
3
|
+
//#region src/dialog/dialog-context.ts
|
|
4
|
+
const DialogContext = createContext(null);
|
|
5
|
+
function useDialogContext(part) {
|
|
6
|
+
const context = use(DialogContext);
|
|
7
|
+
if (context === null) throw new Error(`${part} must be rendered inside a Dialog.Root.`);
|
|
8
|
+
return context;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { DialogContext, useDialogContext };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=dialog-context.mjs.map
|
|
@@ -0,0 +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 * 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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { DialogPopupState } from "./dialog-popup.mjs";
|
|
3
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
|
|
4
|
+
//#region src/dialog/dialog-description.d.ts
|
|
5
|
+
export type DialogDescriptionState = DialogPopupState;
|
|
6
|
+
export interface DialogDescriptionProps extends Omit<ComponentPropsWithRef<"p">, "id" | "className" | "style" | "children"> {
|
|
7
|
+
className?: StateValue<string | undefined, DialogDescriptionState>;
|
|
8
|
+
style?: StateValue<CSSProperties | undefined, DialogDescriptionState>;
|
|
9
|
+
children?: StateValue<ReactNode, DialogDescriptionState>;
|
|
10
|
+
render?: RenderProp<ComponentPropsWithRef<"p">, DialogDescriptionState>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The dialog's description: a `<p>` whose id the Popup references with `aria-describedby`.
|
|
14
|
+
*
|
|
15
|
+
* Owned key (conventions 1.2): `id`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function DialogDescription({ className, style, children, render, ...rest }: DialogDescriptionProps): ReactNode;
|
|
18
|
+
export declare namespace DialogDescription {
|
|
19
|
+
var displayName: string;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//# sourceMappingURL=dialog-description.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog-description.d.mts","names":[],"sources":["../../src/dialog/dialog-description.tsx"],"mappings":";;;;YAOY,yBAAyB;iBAEpB,+BAA+B,KAC9C;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,4BAA4B;;;;;;;wBAQlC,oBACd,WACA,OACA,UACA,WACG,QACF,yBAAsB;yBANT"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
3
|
+
import { useDialogContext } from "./dialog-context.mjs";
|
|
4
|
+
//#region src/dialog/dialog-description.tsx
|
|
5
|
+
/**
|
|
6
|
+
* The dialog's description: a `<p>` whose id the Popup references with `aria-describedby`.
|
|
7
|
+
*
|
|
8
|
+
* Owned key (conventions 1.2): `id`.
|
|
9
|
+
*/
|
|
10
|
+
function DialogDescription({ className, style, children, render, ...rest }) {
|
|
11
|
+
const ctx = useDialogContext("Dialog.Description");
|
|
12
|
+
const state = {
|
|
13
|
+
open: ctx.committed,
|
|
14
|
+
modal: ctx.modal
|
|
15
|
+
};
|
|
16
|
+
return useRender({
|
|
17
|
+
defaultElement: "p",
|
|
18
|
+
render,
|
|
19
|
+
state,
|
|
20
|
+
className,
|
|
21
|
+
style,
|
|
22
|
+
children,
|
|
23
|
+
props: rest,
|
|
24
|
+
libraryProps: { id: ctx.ids.description }
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
DialogDescription.displayName = "DialogDescription";
|
|
28
|
+
//#endregion
|
|
29
|
+
export { DialogDescription };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=dialog-description.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog-description.mjs","names":[],"sources":["../../src/dialog/dialog-description.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\nimport type { DialogPopupState } from \"./dialog-popup.js\";\n\nexport type DialogDescriptionState = DialogPopupState;\n\nexport interface DialogDescriptionProps extends Omit<\n ComponentPropsWithRef<\"p\">,\n \"id\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, DialogDescriptionState>;\n style?: StateValue<CSSProperties | undefined, DialogDescriptionState>;\n children?: StateValue<ReactNode, DialogDescriptionState>;\n render?: RenderProp<ComponentPropsWithRef<\"p\">, DialogDescriptionState>;\n}\n\n/**\n * The dialog's description: a `<p>` whose id the Popup references with `aria-describedby`.\n *\n * Owned key (conventions 1.2): `id`.\n */\nexport function DialogDescription({\n className,\n style,\n children,\n render,\n ...rest\n}: DialogDescriptionProps) {\n const ctx = useDialogContext(\"Dialog.Description\");\n const state: DialogDescriptionState = { open: ctx.committed, modal: ctx.modal };\n return useRender<ComponentPropsWithRef<\"p\">, DialogDescriptionState>({\n defaultElement: \"p\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: { id: ctx.ids.description },\n });\n}\n\nDialogDescription.displayName = \"DialogDescription\";\n"],"mappings":";;;;;;;;;AAwBA,SAAgB,kBAAkB,EAChC,WACA,OACA,UACA,QACA,GAAG,QACsB;CACzB,MAAM,MAAM,iBAAiB,oBAAoB;CACjD,MAAM,QAAgC;EAAE,MAAM,IAAI;EAAW,OAAO,IAAI;CAAM;CAC9E,OAAO,UAA8D;EACnE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP,cAAc,EAAE,IAAI,IAAI,IAAI,YAAY;CAC1C,CAAC;AACH;AAEA,kBAAkB,cAAc"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
|
|
3
|
+
//#region src/dialog/dialog-form.d.ts
|
|
4
|
+
export interface DialogFormState {
|
|
5
|
+
/** `useFormStatus().pending` of this form, exposed for `className` / `children` functions. */
|
|
6
|
+
readonly pending: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface DialogFormProps extends Omit<ComponentPropsWithRef<"form">, "method" | "action" | "className" | "style" | "children"> {
|
|
9
|
+
/**
|
|
10
|
+
* React form action (`(formData) => unknown`, may be a Server Function). Passed to the form
|
|
11
|
+
* unwrapped so React's progressive enhancement is kept; the dialog closes with the submitter's
|
|
12
|
+
* `value` as `returnValue` once the action's transition settles (see `closeOnSettle`). A
|
|
13
|
+
* rejection leaves the dialog open. Without `action` the form renders `method="dialog"`:
|
|
14
|
+
* submitting closes the dialog natively. A string `action` (a URL) is a plain navigating form.
|
|
15
|
+
*/
|
|
16
|
+
action?: string | ((formData: FormData) => unknown);
|
|
17
|
+
/**
|
|
18
|
+
* With a function `action`: close the dialog once the action's transition settles without
|
|
19
|
+
* throwing. Default `true`.
|
|
20
|
+
*
|
|
21
|
+
* Any result that does not throw counts as success, including a `useActionState`-style result
|
|
22
|
+
* that reports validation errors: with the default the dialog closes and those errors are never
|
|
23
|
+
* seen. Throw to keep the dialog open, or set `closeOnSettle={false}` and close it yourself:
|
|
24
|
+
* a `Dialog.Close`, a submitter with `formMethod="dialog"`, or a controlled `open`.
|
|
25
|
+
*
|
|
26
|
+
* The dialog is not closed either when it was closed (Escape, a Close part) while the action
|
|
27
|
+
* ran, also if the user re-opened it meanwhile.
|
|
28
|
+
*/
|
|
29
|
+
closeOnSettle?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Only with a string `action` (a navigating form, for example `"post"`). Owned otherwise:
|
|
32
|
+
* `"dialog"` without `action`, React's own with a function `action`.
|
|
33
|
+
*/
|
|
34
|
+
method?: ComponentPropsWithRef<"form">["method"];
|
|
35
|
+
className?: StateValue<string | undefined, DialogFormState>;
|
|
36
|
+
style?: StateValue<CSSProperties | undefined, DialogFormState>;
|
|
37
|
+
children?: StateValue<ReactNode, DialogFormState>;
|
|
38
|
+
render?: RenderProp<ComponentPropsWithRef<"form">, DialogFormState>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A form that completes the dialog. Without `action`: `<form method="dialog">`, so a submit
|
|
42
|
+
* closes the dialog natively (JS-free) with the submitter's `value` as `returnValue`. With a
|
|
43
|
+
* function `action`: `<form action={action}>` (React overrides the method) and a status watcher
|
|
44
|
+
* that closes the dialog once the action has settled, with the submitter's `value`, unless
|
|
45
|
+
* `closeOnSettle` is `false` or the dialog was closed while the action ran.
|
|
46
|
+
*
|
|
47
|
+
* `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and
|
|
48
|
+
* is not folded into any control's `pending` (conventions 2.7). Submit buttons read
|
|
49
|
+
* `useFormStatus()` themselves.
|
|
50
|
+
*
|
|
51
|
+
* Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function DialogForm({ action, closeOnSettle, method, className, style, children, render, ...rest }: DialogFormProps): ReactNode;
|
|
54
|
+
export declare namespace DialogForm {
|
|
55
|
+
var displayName: string;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//# sourceMappingURL=dialog-form.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog-form.d.mts","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"mappings":";;;iBAQiB;;WAEN;;iBAGM,wBAAwB,KACvC;;;;;;;;EAUA,oBAAoB,UAAU;;;;;;;;;;;;;EAa9B;;;;;EAKA,SAAS;EACT,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,+BAA+B;;;;;;;;;;;;;;;wBAwCrC,aACd,QACA,eACA,QACA,WACA,OACA,UACA,WACG,QACF,kBAAe;yBATF"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { resolveStateValue, useRender } from "../internal/use-render.mjs";
|
|
3
|
+
import { useDialogContext } from "./dialog-context.mjs";
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { useFormStatus } from "react-dom";
|
|
7
|
+
//#region src/dialog/dialog-form.tsx
|
|
8
|
+
/**
|
|
9
|
+
* Observes React's form channel (design 8.1): `useFormStatus().pending` covers the action's
|
|
10
|
+
* whole transition, including the Suspense wait of what it re-renders. On `true → false` the
|
|
11
|
+
* submission succeeded (a rejection unmounts the form through the nearest Error Boundary, so the
|
|
12
|
+
* edge is never seen).
|
|
13
|
+
*/
|
|
14
|
+
function FormStatusWatcher({ onPendingChange, onSettled }) {
|
|
15
|
+
const { pending } = useFormStatus();
|
|
16
|
+
const previous = useRef(false);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const was = previous.current;
|
|
19
|
+
previous.current = pending;
|
|
20
|
+
if (was === pending) return;
|
|
21
|
+
onPendingChange(pending);
|
|
22
|
+
if (was && !pending) onSettled();
|
|
23
|
+
}, [
|
|
24
|
+
pending,
|
|
25
|
+
onPendingChange,
|
|
26
|
+
onSettled
|
|
27
|
+
]);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A form that completes the dialog. Without `action`: `<form method="dialog">`, so a submit
|
|
32
|
+
* closes the dialog natively (JS-free) with the submitter's `value` as `returnValue`. With a
|
|
33
|
+
* function `action`: `<form action={action}>` (React overrides the method) and a status watcher
|
|
34
|
+
* that closes the dialog once the action has settled, with the submitter's `value`, unless
|
|
35
|
+
* `closeOnSettle` is `false` or the dialog was closed while the action ran.
|
|
36
|
+
*
|
|
37
|
+
* `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and
|
|
38
|
+
* is not folded into any control's `pending` (conventions 2.7). Submit buttons read
|
|
39
|
+
* `useFormStatus()` themselves.
|
|
40
|
+
*
|
|
41
|
+
* Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.
|
|
42
|
+
*/
|
|
43
|
+
function DialogForm({ action, closeOnSettle = true, method, className, style, children, render, ...rest }) {
|
|
44
|
+
const { dialogRef, recordCloseIntent } = useDialogContext("Dialog.Form");
|
|
45
|
+
const [pending, setPending] = useState(false);
|
|
46
|
+
const submission = useRef(null);
|
|
47
|
+
const closedSinceSubmit = useRef(false);
|
|
48
|
+
const watches = typeof action === "function";
|
|
49
|
+
const libraryRef = useCallback((form) => {
|
|
50
|
+
const onSubmit = (event) => {
|
|
51
|
+
if (event.target !== form) return;
|
|
52
|
+
const submitter = event.submitter;
|
|
53
|
+
submission.current = {
|
|
54
|
+
event,
|
|
55
|
+
returnValue: submitter?.value ?? ""
|
|
56
|
+
};
|
|
57
|
+
closedSinceSubmit.current = false;
|
|
58
|
+
};
|
|
59
|
+
const dialog = form.closest("dialog");
|
|
60
|
+
const onClose = (event) => {
|
|
61
|
+
if (event.target === dialog) closedSinceSubmit.current = true;
|
|
62
|
+
};
|
|
63
|
+
form.addEventListener("submit", onSubmit, true);
|
|
64
|
+
dialog?.addEventListener("close", onClose);
|
|
65
|
+
return () => {
|
|
66
|
+
form.removeEventListener("submit", onSubmit, true);
|
|
67
|
+
dialog?.removeEventListener("close", onClose);
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
const onSettled = useCallback(() => {
|
|
71
|
+
const dialog = dialogRef.current;
|
|
72
|
+
const submitted = submission.current;
|
|
73
|
+
submission.current = null;
|
|
74
|
+
if (!closeOnSettle || closedSinceSubmit.current) return;
|
|
75
|
+
if (dialog === null || !dialog.open) return;
|
|
76
|
+
recordCloseIntent({
|
|
77
|
+
reason: "activate",
|
|
78
|
+
event: submitted?.event
|
|
79
|
+
});
|
|
80
|
+
dialog.close(submitted?.returnValue ?? "");
|
|
81
|
+
}, [
|
|
82
|
+
closeOnSettle,
|
|
83
|
+
dialogRef,
|
|
84
|
+
recordCloseIntent
|
|
85
|
+
]);
|
|
86
|
+
return useRender({
|
|
87
|
+
defaultElement: "form",
|
|
88
|
+
render,
|
|
89
|
+
state: { pending },
|
|
90
|
+
className,
|
|
91
|
+
style,
|
|
92
|
+
children: (s) => /* @__PURE__ */ jsxs(Fragment, { children: [resolveStateValue(children, s), watches && /* @__PURE__ */ jsx(FormStatusWatcher, {
|
|
93
|
+
onPendingChange: setPending,
|
|
94
|
+
onSettled
|
|
95
|
+
})] }),
|
|
96
|
+
props: rest,
|
|
97
|
+
libraryProps: {
|
|
98
|
+
method: action === void 0 ? "dialog" : typeof action === "string" ? method : void 0,
|
|
99
|
+
action
|
|
100
|
+
},
|
|
101
|
+
ref: libraryRef
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
DialogForm.displayName = "DialogForm";
|
|
105
|
+
//#endregion
|
|
106
|
+
export { DialogForm };
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=dialog-form.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog-form.mjs","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { useFormStatus } from \"react-dom\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogFormState {\n /** `useFormStatus().pending` of this form, exposed for `className` / `children` functions. */\n readonly pending: boolean;\n}\n\nexport interface DialogFormProps extends Omit<\n ComponentPropsWithRef<\"form\">,\n \"method\" | \"action\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * React form action (`(formData) => unknown`, may be a Server Function). Passed to the form\n * unwrapped so React's progressive enhancement is kept; the dialog closes with the submitter's\n * `value` as `returnValue` once the action's transition settles (see `closeOnSettle`). A\n * rejection leaves the dialog open. Without `action` the form renders `method=\"dialog\"`:\n * submitting closes the dialog natively. A string `action` (a URL) is a plain navigating form.\n */\n action?: string | ((formData: FormData) => unknown);\n /**\n * With a function `action`: close the dialog once the action's transition settles without\n * throwing. Default `true`.\n *\n * Any result that does not throw counts as success, including a `useActionState`-style result\n * that reports validation errors: with the default the dialog closes and those errors are never\n * seen. Throw to keep the dialog open, or set `closeOnSettle={false}` and close it yourself:\n * a `Dialog.Close`, a submitter with `formMethod=\"dialog\"`, or a controlled `open`.\n *\n * The dialog is not closed either when it was closed (Escape, a Close part) while the action\n * ran, also if the user re-opened it meanwhile.\n */\n closeOnSettle?: boolean;\n /**\n * Only with a string `action` (a navigating form, for example `\"post\"`). Owned otherwise:\n * `\"dialog\"` without `action`, React's own with a function `action`.\n */\n method?: ComponentPropsWithRef<\"form\">[\"method\"];\n className?: StateValue<string | undefined, DialogFormState>;\n style?: StateValue<CSSProperties | undefined, DialogFormState>;\n children?: StateValue<ReactNode, DialogFormState>;\n render?: RenderProp<ComponentPropsWithRef<\"form\">, DialogFormState>;\n}\n\ninterface FormStatusWatcherProps {\n readonly onPendingChange: (pending: boolean) => void;\n readonly onSettled: () => void;\n}\n\n/**\n * Observes React's form channel (design 8.1): `useFormStatus().pending` covers the action's\n * whole transition, including the Suspense wait of what it re-renders. On `true → false` the\n * submission succeeded (a rejection unmounts the form through the nearest Error Boundary, so the\n * edge is never seen).\n */\nfunction FormStatusWatcher({ onPendingChange, onSettled }: FormStatusWatcherProps) {\n const { pending } = useFormStatus();\n const previous = useRef(false);\n useEffect(() => {\n const was = previous.current;\n previous.current = pending;\n if (was === pending) return;\n onPendingChange(pending);\n if (was && !pending) onSettled();\n }, [pending, onPendingChange, onSettled]);\n return null;\n}\n\n/**\n * A form that completes the dialog. Without `action`: `<form method=\"dialog\">`, so a submit\n * closes the dialog natively (JS-free) with the submitter's `value` as `returnValue`. With a\n * function `action`: `<form action={action}>` (React overrides the method) and a status watcher\n * that closes the dialog once the action has settled, with the submitter's `value`, unless\n * `closeOnSettle` is `false` or the dialog was closed while the action ran.\n *\n * `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and\n * is not folded into any control's `pending` (conventions 2.7). Submit buttons read\n * `useFormStatus()` themselves.\n *\n * Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.\n */\nexport function DialogForm({\n action,\n closeOnSettle = true,\n method,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogFormProps) {\n const ctx = useDialogContext(\"Dialog.Form\");\n const { dialogRef, recordCloseIntent } = ctx;\n const [pending, setPending] = useState(false);\n const submission = useRef<{ event: Event; returnValue: string } | null>(null);\n // The dialog closed after the last submit (Escape, a Close part): the settle must not close it\n // again, also when the user re-opened it meanwhile (the review's walkthrough 11.8 variant).\n const closedSinceSubmit = useRef(false);\n const watches = typeof action === \"function\";\n\n // The submitter's `value` comes from the native `submit` event (capture, before React's\n // handler runs the action).\n const libraryRef = useCallback((form: HTMLFormElement) => {\n const onSubmit = (event: Event) => {\n if (event.target !== form) return;\n const submitter = (event as SubmitEvent).submitter as\n | HTMLButtonElement\n | HTMLInputElement\n | null;\n submission.current = { event, returnValue: submitter?.value ?? \"\" };\n closedSinceSubmit.current = false;\n };\n const dialog = form.closest(\"dialog\");\n const onClose = (event: Event) => {\n if (event.target === dialog) closedSinceSubmit.current = true;\n };\n form.addEventListener(\"submit\", onSubmit, true);\n dialog?.addEventListener(\"close\", onClose);\n return () => {\n form.removeEventListener(\"submit\", onSubmit, true);\n dialog?.removeEventListener(\"close\", onClose);\n };\n }, []);\n\n const onSettled = useCallback(() => {\n const dialog = dialogRef.current;\n const submitted = submission.current;\n submission.current = null;\n if (!closeOnSettle || closedSinceSubmit.current) return;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event: submitted?.event });\n dialog.close(submitted?.returnValue ?? \"\");\n }, [closeOnSettle, dialogRef, recordCloseIntent]);\n\n const state: DialogFormState = { pending };\n\n return useRender<ComponentPropsWithRef<\"form\">, DialogFormState>({\n defaultElement: \"form\",\n render,\n state,\n className,\n style,\n children: (s) => (\n <>\n {resolveStateValue(children, s)}\n {watches && <FormStatusWatcher onPendingChange={setPending} onSettled={onSettled} />}\n </>\n ),\n props: rest,\n libraryProps: {\n method: action === undefined ? \"dialog\" : typeof action === \"string\" ? method : undefined,\n // React's own type wants `void | Promise<void>`; the library's actions return `unknown`\n // (conventions 2.2) and React ignores the result.\n action: action as ComponentPropsWithRef<\"form\">[\"action\"],\n },\n ref: libraryRef,\n });\n}\n\nDialogForm.displayName = \"DialogForm\";\n"],"mappings":";;;;;;;;;;;;;AA4DA,SAAS,kBAAkB,EAAE,iBAAiB,aAAqC;CACjF,MAAM,EAAE,YAAY,cAAc;CAClC,MAAM,WAAW,OAAO,KAAK;CAC7B,gBAAgB;EACd,MAAM,MAAM,SAAS;EACrB,SAAS,UAAU;EACnB,IAAI,QAAQ,SAAS;EACrB,gBAAgB,OAAO;EACvB,IAAI,OAAO,CAAC,SAAS,UAAU;CACjC,GAAG;EAAC;EAAS;EAAiB;CAAS,CAAC;CACxC,OAAO;AACT;;;;;;;;;;;;;;AAeA,SAAgB,WAAW,EACzB,QACA,gBAAgB,MAChB,QACA,WACA,OACA,UACA,QACA,GAAG,QACe;CAElB,MAAM,EAAE,WAAW,sBADP,iBAAiB,aACc;CAC3C,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,aAAa,OAAqD,IAAI;CAG5E,MAAM,oBAAoB,OAAO,KAAK;CACtC,MAAM,UAAU,OAAO,WAAW;CAIlC,MAAM,aAAa,aAAa,SAA0B;EACxD,MAAM,YAAY,UAAiB;GACjC,IAAI,MAAM,WAAW,MAAM;GAC3B,MAAM,YAAa,MAAsB;GAIzC,WAAW,UAAU;IAAE;IAAO,aAAa,WAAW,SAAS;GAAG;GAClE,kBAAkB,UAAU;EAC9B;EACA,MAAM,SAAS,KAAK,QAAQ,QAAQ;EACpC,MAAM,WAAW,UAAiB;GAChC,IAAI,MAAM,WAAW,QAAQ,kBAAkB,UAAU;EAC3D;EACA,KAAK,iBAAiB,UAAU,UAAU,IAAI;EAC9C,QAAQ,iBAAiB,SAAS,OAAO;EACzC,aAAa;GACX,KAAK,oBAAoB,UAAU,UAAU,IAAI;GACjD,QAAQ,oBAAoB,SAAS,OAAO;EAC9C;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,YAAY,kBAAkB;EAClC,MAAM,SAAS,UAAU;EACzB,MAAM,YAAY,WAAW;EAC7B,WAAW,UAAU;EACrB,IAAI,CAAC,iBAAiB,kBAAkB,SAAS;EACjD,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,kBAAkB;GAAE,QAAQ;GAAY,OAAO,WAAW;EAAM,CAAC;EACjE,OAAO,MAAM,WAAW,eAAe,EAAE;CAC3C,GAAG;EAAC;EAAe;EAAW;CAAiB,CAAC;CAIhD,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA,EAL+B,QAK3B;EACJ;EACA;EACA,WAAW,MACT,qBAAA,UAAA,EAAA,UAAA,CACG,kBAAkB,UAAU,CAAC,GAC7B,WAAW,oBAAC,mBAAD;GAAmB,iBAAiB;GAAuB;EAAY,CAAA,CACnF,EAAA,CAAA;EAEJ,OAAO;EACP,cAAc;GACZ,QAAQ,WAAW,KAAA,IAAY,WAAW,OAAO,WAAW,WAAW,SAAS,KAAA;GAGxE;EACV;EACA,KAAK;CACP,CAAC;AACH;AAEA,WAAW,cAAc"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { RenderProp, StateValue } from "../internal/types.mjs";
|
|
2
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode, RefObject, ViewTransitionClass } from "react";
|
|
3
|
+
//#region src/dialog/dialog-popup.d.ts
|
|
4
|
+
export interface DialogPopupState {
|
|
5
|
+
/** Committed open state (library mode); `false` in JS-free mode. Read `:open` for the DOM truth. */
|
|
6
|
+
readonly open: boolean;
|
|
7
|
+
readonly modal: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface DialogPopupProps extends Omit<ComponentPropsWithRef<"dialog">, "id" | "open" | "closedby" | "onToggle" | "onBeforeToggle" | "className" | "style" | "children"> {
|
|
10
|
+
/**
|
|
11
|
+
* Library mode: what happens to the children while the dialog is closed.
|
|
12
|
+
* - `false` (default): not rendered. Mounting them is part of the opening transition, so
|
|
13
|
+
* content that suspends holds the open until it is ready. Unmount waits for the dialog's
|
|
14
|
+
* exit animations.
|
|
15
|
+
* - `true`: rendered inside `<Activity mode="hidden">`: DOM and state preserved, effects
|
|
16
|
+
* stopped, pre-rendered at low priority. Not in the server HTML.
|
|
17
|
+
*
|
|
18
|
+
* Ignored in JS-free mode (always mounted).
|
|
19
|
+
*/
|
|
20
|
+
keepMounted?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Element to focus when the dialog opens: a ref, or a selector resolved inside the dialog.
|
|
23
|
+
* The library writes `autofocus` on it right before `showModal()` / `show()` and the platform
|
|
24
|
+
* focuses it. Default: the platform's choice (first focusable descendant; the dialog itself if
|
|
25
|
+
* none). Use `tabIndex={-1}` on a heading or wrapper for long or destructive dialogs (APG).
|
|
26
|
+
*
|
|
27
|
+
* In JS-free mode the browser opens the dialog itself, so the attribute is written after
|
|
28
|
+
* hydration and kept while mounted; before hydration only `autoFocus` in the markup works.
|
|
29
|
+
*/
|
|
30
|
+
initialFocus?: RefObject<HTMLElement | null> | string;
|
|
31
|
+
/**
|
|
32
|
+
* Close on a click outside the dialog (on the backdrop for modal dialogs). Default `false`.
|
|
33
|
+
* Baseline: JS `pointerdown` + `click` listeners, which close only when both the press and the
|
|
34
|
+
* click land outside (a drag that starts inside, such as selecting text, never closes); with
|
|
35
|
+
* `experimental_closedBy`, native `closedby="any"`, which behaves the same.
|
|
36
|
+
*/
|
|
37
|
+
lightDismiss?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Experimental (Chrome 134+, Firefox 141+; Safari 27.0 ignores it). Renders `closedby`
|
|
40
|
+
* (`"any"` when `lightDismiss`, else `"closerequest"`) and skips the JS dismiss listeners where
|
|
41
|
+
* `HTMLDialogElement.prototype.closedBy` exists. Elsewhere the JS baseline stays active.
|
|
42
|
+
* Default `false`.
|
|
43
|
+
*/
|
|
44
|
+
experimental_closedBy?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Library mode only. Forwarded to the `<ViewTransition default>` around the dialog. Animates
|
|
47
|
+
* the opening commit and content changes while open; the close is the browser's and animates
|
|
48
|
+
* with CSS. Ignored (dev warning) in JS-free mode.
|
|
49
|
+
*
|
|
50
|
+
* Static: without `transition` the part renders no `<ViewTransition>`; changing it between
|
|
51
|
+
* `undefined` and a value remounts the element. Any value, `"none"` included, makes a mount or
|
|
52
|
+
* `<Activity>` reveal of this part inside a transition start a document view transition: the
|
|
53
|
+
* page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).
|
|
54
|
+
*/
|
|
55
|
+
transition?: ViewTransitionClass;
|
|
56
|
+
className?: StateValue<string | undefined, DialogPopupState>;
|
|
57
|
+
style?: StateValue<CSSProperties | undefined, DialogPopupState>;
|
|
58
|
+
children?: StateValue<ReactNode, DialogPopupState>;
|
|
59
|
+
/** Must render a `<dialog>`. */
|
|
60
|
+
render?: RenderProp<ComponentPropsWithRef<"dialog">, DialogPopupState>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The dialog itself: a `<dialog>` with the id the Trigger's `commandfor` names, wired to Title
|
|
64
|
+
* and Description through `aria-labelledby` / `aria-describedby`. Never renders `open`: modal
|
|
65
|
+
* opening and closing go through `showModal()` / `close()` only. Style it with `:open` and
|
|
66
|
+
* `:modal`; it emits `data-modal` but no `data-open`.
|
|
67
|
+
*
|
|
68
|
+
* In library mode the children render from committed state, so content that suspends keeps the
|
|
69
|
+
* dialog closed (and the Trigger pending) until it is ready; `showModal()` runs in the layout
|
|
70
|
+
* effect of the commit that mounted them. Closing is the browser's: the native `close` event
|
|
71
|
+
* becomes `openChangeAction(false, { reason, returnValue })`.
|
|
72
|
+
*
|
|
73
|
+
* Owned keys (conventions 1.2): `id`, `open`, `closedby`, `aria-labelledby` and
|
|
74
|
+
* `aria-describedby` (token-merged), `data-modal`, `suppressHydrationWarning`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function DialogPopup({ keepMounted, initialFocus, lightDismiss, experimental_closedBy, transition, className, style, children, render, ...rest }: DialogPopupProps): ReactNode;
|
|
77
|
+
export declare namespace DialogPopup {
|
|
78
|
+
var displayName: string;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//# sourceMappingURL=dialog-popup.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog-popup.d.mts","names":[],"sources":["../../src/dialog/dialog-popup.tsx"],"mappings":";;;iBA0BiB;;WAEN;WACA;;iBAGM,yBAAyB,KACxC;;;;;;;;;;;EAaA;;;;;;;;;;EAUA,eAAe,UAAU;;;;;;;EAOzB;;;;;;;EAOA;;;;;;;;;;;EAWA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;;;;;wBA2FvC,cACd,aACA,cACA,cACA,uBACA,YACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBAXH"}
|