@funstack/ui 0.1.0-alpha.0 → 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.
Files changed (154) hide show
  1. package/README.md +1 -1
  2. package/dist/checkbox-group/checkbox-group-context.mjs +24 -0
  3. package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -0
  4. package/dist/checkbox-group/checkbox-group-item.d.mts +55 -0
  5. package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -0
  6. package/dist/checkbox-group/checkbox-group-item.mjs +90 -0
  7. package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -0
  8. package/dist/checkbox-group/checkbox-group-label.d.mts +21 -0
  9. package/dist/checkbox-group/checkbox-group-label.d.mts.map +1 -0
  10. package/dist/checkbox-group/checkbox-group-label.mjs +30 -0
  11. package/dist/checkbox-group/checkbox-group-label.mjs.map +1 -0
  12. package/dist/checkbox-group/checkbox-group-parent.d.mts +68 -0
  13. package/dist/checkbox-group/checkbox-group-parent.d.mts.map +1 -0
  14. package/dist/checkbox-group/checkbox-group-parent.mjs +145 -0
  15. package/dist/checkbox-group/checkbox-group-parent.mjs.map +1 -0
  16. package/dist/checkbox-group/checkbox-group-root.d.mts +100 -0
  17. package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -0
  18. package/dist/checkbox-group/checkbox-group-root.mjs +291 -0
  19. package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -0
  20. package/dist/checkbox-group/index.d.mts +23 -0
  21. package/dist/checkbox-group/index.d.mts.map +1 -0
  22. package/dist/checkbox-group/index.mjs +23 -0
  23. package/dist/checkbox-group/index.mjs.map +1 -0
  24. package/dist/dialog/dialog-close.d.mts.map +1 -1
  25. package/dist/dialog/dialog-close.mjs +4 -4
  26. package/dist/dialog/dialog-close.mjs.map +1 -1
  27. package/dist/dialog/dialog-context.mjs.map +1 -1
  28. package/dist/dialog/dialog-popup.mjs +1 -0
  29. package/dist/dialog/dialog-popup.mjs.map +1 -1
  30. package/dist/dialog/dialog-root.mjs +9 -1
  31. package/dist/dialog/dialog-root.mjs.map +1 -1
  32. package/dist/index.d.mts +23 -1
  33. package/dist/index.mjs +4 -1
  34. package/dist/internal/anchor-position.d.mts +7 -0
  35. package/dist/internal/anchor-position.d.mts.map +1 -0
  36. package/dist/internal/anchor-position.mjs +16 -0
  37. package/dist/internal/anchor-position.mjs.map +1 -0
  38. package/dist/internal/platform-attributes.d.mts +6 -0
  39. package/dist/internal/platform-attributes.d.mts.map +1 -1
  40. package/dist/internal/sync-presence.mjs +19 -0
  41. package/dist/internal/sync-presence.mjs.map +1 -0
  42. package/dist/internal/use-action-trigger.mjs +58 -0
  43. package/dist/internal/use-action-trigger.mjs.map +1 -0
  44. package/dist/internal/use-delayed-pair.mjs +40 -0
  45. package/dist/internal/use-delayed-pair.mjs.map +1 -0
  46. package/dist/internal/use-exit-animation.mjs +55 -0
  47. package/dist/internal/use-exit-animation.mjs.map +1 -0
  48. package/dist/internal/use-native-group-reconciliation.mjs +2 -2
  49. package/dist/internal/use-native-group-reconciliation.mjs.map +1 -1
  50. package/dist/internal/use-popover-popup.mjs +274 -0
  51. package/dist/internal/use-popover-popup.mjs.map +1 -0
  52. package/dist/internal/use-popover-root.d.mts +2 -0
  53. package/dist/internal/use-popover-root.mjs +112 -0
  54. package/dist/internal/use-popover-root.mjs.map +1 -0
  55. package/dist/internal/use-popover-trigger.mjs +86 -0
  56. package/dist/internal/use-popover-trigger.mjs.map +1 -0
  57. package/dist/menu/index.d.mts +34 -0
  58. package/dist/menu/index.d.mts.map +1 -0
  59. package/dist/menu/index.mjs +34 -0
  60. package/dist/menu/index.mjs.map +1 -0
  61. package/dist/menu/menu-checkbox-item.d.mts +42 -0
  62. package/dist/menu/menu-checkbox-item.d.mts.map +1 -0
  63. package/dist/menu/menu-checkbox-item.mjs +71 -0
  64. package/dist/menu/menu-checkbox-item.mjs.map +1 -0
  65. package/dist/menu/menu-context.d.mts +1 -0
  66. package/dist/menu/menu-context.mjs +28 -0
  67. package/dist/menu/menu-context.mjs.map +1 -0
  68. package/dist/menu/menu-dom.mjs +37 -0
  69. package/dist/menu/menu-dom.mjs.map +1 -0
  70. package/dist/menu/menu-group.d.mts +48 -0
  71. package/dist/menu/menu-group.d.mts.map +1 -0
  72. package/dist/menu/menu-group.mjs +69 -0
  73. package/dist/menu/menu-group.mjs.map +1 -0
  74. package/dist/menu/menu-hover.mjs +29 -0
  75. package/dist/menu/menu-hover.mjs.map +1 -0
  76. package/dist/menu/menu-item.d.mts +45 -0
  77. package/dist/menu/menu-item.d.mts.map +1 -0
  78. package/dist/menu/menu-item.mjs +54 -0
  79. package/dist/menu/menu-item.mjs.map +1 -0
  80. package/dist/menu/menu-popup.d.mts +69 -0
  81. package/dist/menu/menu-popup.d.mts.map +1 -0
  82. package/dist/menu/menu-popup.mjs +207 -0
  83. package/dist/menu/menu-popup.mjs.map +1 -0
  84. package/dist/menu/menu-radio-group.d.mts +37 -0
  85. package/dist/menu/menu-radio-group.d.mts.map +1 -0
  86. package/dist/menu/menu-radio-group.mjs +72 -0
  87. package/dist/menu/menu-radio-group.mjs.map +1 -0
  88. package/dist/menu/menu-radio-item.d.mts +33 -0
  89. package/dist/menu/menu-radio-item.d.mts.map +1 -0
  90. package/dist/menu/menu-radio-item.mjs +62 -0
  91. package/dist/menu/menu-radio-item.mjs.map +1 -0
  92. package/dist/menu/menu-root.d.mts +54 -0
  93. package/dist/menu/menu-root.d.mts.map +1 -0
  94. package/dist/menu/menu-root.mjs +102 -0
  95. package/dist/menu/menu-root.mjs.map +1 -0
  96. package/dist/menu/menu-submenu-trigger.d.mts +31 -0
  97. package/dist/menu/menu-submenu-trigger.d.mts.map +1 -0
  98. package/dist/menu/menu-submenu-trigger.mjs +92 -0
  99. package/dist/menu/menu-submenu-trigger.mjs.map +1 -0
  100. package/dist/menu/menu-trigger.d.mts +36 -0
  101. package/dist/menu/menu-trigger.d.mts.map +1 -0
  102. package/dist/menu/menu-trigger.mjs +81 -0
  103. package/dist/menu/menu-trigger.mjs.map +1 -0
  104. package/dist/menu/use-menu-item.mjs +54 -0
  105. package/dist/menu/use-menu-item.mjs.map +1 -0
  106. package/dist/popover/index.d.mts +28 -0
  107. package/dist/popover/index.d.mts.map +1 -0
  108. package/dist/popover/index.mjs +28 -0
  109. package/dist/popover/index.mjs.map +1 -0
  110. package/dist/popover/popover-close.d.mts +41 -0
  111. package/dist/popover/popover-close.d.mts.map +1 -0
  112. package/dist/popover/popover-close.mjs +90 -0
  113. package/dist/popover/popover-close.mjs.map +1 -0
  114. package/dist/popover/popover-context.mjs +26 -0
  115. package/dist/popover/popover-context.mjs.map +1 -0
  116. package/dist/popover/popover-description.d.mts +22 -0
  117. package/dist/popover/popover-description.d.mts.map +1 -0
  118. package/dist/popover/popover-description.mjs +28 -0
  119. package/dist/popover/popover-description.mjs.map +1 -0
  120. package/dist/popover/popover-popup.d.mts +82 -0
  121. package/dist/popover/popover-popup.d.mts.map +1 -0
  122. package/dist/popover/popover-popup.mjs +93 -0
  123. package/dist/popover/popover-popup.mjs.map +1 -0
  124. package/dist/popover/popover-root.d.mts +64 -0
  125. package/dist/popover/popover-root.d.mts.map +1 -0
  126. package/dist/popover/popover-root.mjs +39 -0
  127. package/dist/popover/popover-root.mjs.map +1 -0
  128. package/dist/popover/popover-title.d.mts +23 -0
  129. package/dist/popover/popover-title.d.mts.map +1 -0
  130. package/dist/popover/popover-title.mjs +28 -0
  131. package/dist/popover/popover-title.mjs.map +1 -0
  132. package/dist/popover/popover-trigger.d.mts +37 -0
  133. package/dist/popover/popover-trigger.d.mts.map +1 -0
  134. package/dist/popover/popover-trigger.mjs +40 -0
  135. package/dist/popover/popover-trigger.mjs.map +1 -0
  136. package/dist/radio-group/radio-group-item.d.mts.map +1 -1
  137. package/dist/radio-group/radio-group-item.mjs +1 -6
  138. package/dist/radio-group/radio-group-item.mjs.map +1 -1
  139. package/dist/tabs/tabs-context.mjs +2 -1
  140. package/dist/tabs/tabs-context.mjs.map +1 -1
  141. package/dist/tabs/tabs-list.d.mts +1 -1
  142. package/dist/tabs/tabs-list.d.mts.map +1 -1
  143. package/dist/tabs/tabs-list.mjs +17 -9
  144. package/dist/tabs/tabs-list.mjs.map +1 -1
  145. package/dist/tabs/tabs-tab.d.mts +1 -1
  146. package/dist/tabs/tabs-tab.d.mts.map +1 -1
  147. package/dist/tabs/tabs-tab.mjs +6 -3
  148. package/dist/tabs/tabs-tab.mjs.map +1 -1
  149. package/dist/tooltip/tooltip-popup.d.mts.map +1 -1
  150. package/dist/tooltip/tooltip-popup.mjs +2 -7
  151. package/dist/tooltip/tooltip-popup.mjs.map +1 -1
  152. package/dist/tooltip/use-tooltip-interaction.mjs +5 -17
  153. package/dist/tooltip/use-tooltip-interaction.mjs.map +1 -1
  154. package/package.json +4 -1
@@ -0,0 +1,291 @@
1
+ "use client";
2
+ import { createActionDetails } from "../internal/action-details.mjs";
3
+ import { dataAttr } from "../internal/data-attr.mjs";
4
+ import { transitionTypes } from "../internal/transition-types.mjs";
5
+ import { useControllableAction } from "../internal/use-controllable-action.mjs";
6
+ import { useRender } from "../internal/use-render.mjs";
7
+ import { useNativeGroupReconciliation } from "../internal/use-native-group-reconciliation.mjs";
8
+ import { CheckboxGroupContext, enabledValues } from "./checkbox-group-context.mjs";
9
+ import { useCallback, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
10
+ import { jsx } from "react/jsx-runtime";
11
+ //#region src/checkbox-group/checkbox-group-root.tsx
12
+ const isDev = process.env.NODE_ENV !== "production";
13
+ const EMPTY = /* @__PURE__ */ new Set();
14
+ function toSet(value) {
15
+ return new Set(value);
16
+ }
17
+ function setsEqual(a, b) {
18
+ if (a.size !== b.size) return false;
19
+ for (const v of a) if (!b.has(v)) return false;
20
+ return true;
21
+ }
22
+ /** The optimistic reducer (design 5.2): apply a patch, so stacked dispatches re-base. */
23
+ function reduce(set, patch) {
24
+ const next = new Set(set);
25
+ for (const v of patch.removed) next.delete(v);
26
+ for (const v of patch.added) next.add(v);
27
+ return next;
28
+ }
29
+ /**
30
+ * `values` sorted by `order` (the Items' values in document order); values not in `order` keep
31
+ * their iteration order, after the others.
32
+ */
33
+ function sortByOrder(values, order) {
34
+ const wanted = new Set(values);
35
+ const sorted = [];
36
+ for (const v of order) if (wanted.delete(v)) sorted.push(v);
37
+ for (const v of wanted) sorted.push(v);
38
+ return sorted;
39
+ }
40
+ function summarize(registry) {
41
+ const items = /* @__PURE__ */ new Map();
42
+ for (const { value, disabled } of registry.values()) items.set(value, (items.get(value) ?? false) || !disabled);
43
+ return items;
44
+ }
45
+ function disabledOf(items) {
46
+ if (items === null) return EMPTY;
47
+ const disabled = /* @__PURE__ */ new Set();
48
+ for (const [value, enabled] of items) if (!enabled) disabled.add(value);
49
+ return disabled.size === 0 ? EMPTY : disabled;
50
+ }
51
+ function mapsEqual(a, b) {
52
+ if (a.size !== b.size) return false;
53
+ for (const [k, v] of a) if (b.get(k) !== v) return false;
54
+ return true;
55
+ }
56
+ function arraysEqual(a, b) {
57
+ return a.length === b.length && a.every((v, i) => v === b[i]);
58
+ }
59
+ /** The group's Items: checkboxes of this `name` in the fieldset (Parents have none). */
60
+ function readItems(fieldset, name) {
61
+ if (fieldset === null) return [];
62
+ return [...fieldset.querySelectorAll("input[type=\"checkbox\"]")].filter((box) => box.name === name);
63
+ }
64
+ /**
65
+ * `<fieldset>` that owns the group's state (a set of values), the transition, the shared `name`,
66
+ * the form-reset listener, the mount reconciliation and the group-level `required`
67
+ * (design/components/checkbox-group.md 14.3). Carries `data-pending` while `valueChangeAction`
68
+ * is in flight. Its implicit role is `group`; no `role` is written.
69
+ *
70
+ * Owned keys (conventions 1.2): `disabled`, `form`, `data-pending`, `data-disabled`.
71
+ *
72
+ * Unlike RadioGroup, Items may hydrate after their Root (a Suspense boundary between them): a
73
+ * click before an Item hydrated is reconciled when it does.
74
+ */
75
+ function CheckboxGroupRoot({ value, defaultValue, valueChangeAction, name: nameProp, form, required = false, disabled = false, className, style, children, render, ...rest }) {
76
+ const fallbackName = useId();
77
+ const name = nameProp ?? fallbackName;
78
+ const fieldsetRef = useRef(null);
79
+ const warnedElement = useRef(false);
80
+ const warnedDuplicate = useRef(false);
81
+ /** Parent reports waiting for the next mount read (design 8.1). */
82
+ const parentReports = useRef([]);
83
+ const itemRegistry = useRef(/* @__PURE__ */ new Map());
84
+ const [items, setItems] = useState(null);
85
+ const registerItem = useCallback((value, disabled) => {
86
+ const publish = () => {
87
+ const next = summarize(itemRegistry.current);
88
+ setItems((prev) => prev !== null && mapsEqual(prev, next) ? prev : next);
89
+ };
90
+ const token = {};
91
+ itemRegistry.current.set(token, {
92
+ value,
93
+ disabled
94
+ });
95
+ publish();
96
+ return () => {
97
+ itemRegistry.current.delete(token);
98
+ publish();
99
+ };
100
+ }, []);
101
+ const disabledItems = useMemo(() => disabledOf(items), [items]);
102
+ const controlledSet = useMemo(() => value === void 0 ? void 0 : toSet(value), [value]);
103
+ const defaultSet = useMemo(() => defaultValue === void 0 ? void 0 : toSet(defaultValue), [defaultValue]);
104
+ /** Sorts by the Items' current document order. Reads the DOM: handlers and effects only. */
105
+ const sortByDocument = (values) => sortByOrder(values, readItems(fieldsetRef.current, name).map((item) => item.value));
106
+ const { optimistic, committed, pending, dispatch, latestInFlight } = useControllableAction({
107
+ controlled: controlledSet,
108
+ defaultValue: defaultSet,
109
+ initialValue: EMPTY,
110
+ action: valueChangeAction && ((next, details) => valueChangeAction(sortByDocument(next), details)),
111
+ transitionTypes: (_next, _prev, details) => [
112
+ ...transitionTypes("checkbox-group"),
113
+ ...details.added.length > 0 ? ["fsui-checkbox-group-check"] : [],
114
+ ...details.removed.length > 0 ? ["fsui-checkbox-group-uncheck"] : []
115
+ ],
116
+ reducer: (prev, _next, details) => reduce(prev, details),
117
+ warn: {
118
+ component: "CheckboxGroup.Root",
119
+ prop: "value"
120
+ }
121
+ });
122
+ /** What the user intends: the latest in-flight dispatch, else optimistic (conventions 3.8 rule 1). */
123
+ const base = () => {
124
+ const inFlight = latestInFlight();
125
+ return inFlight === null ? optimistic : inFlight.next;
126
+ };
127
+ /** Dispatches `patch` on top of `from`, with the details of design 3.1. */
128
+ const dispatchPatch = (from, patch, reason, event) => {
129
+ const details = createActionDetails(event, {
130
+ reason,
131
+ added: sortByDocument(patch.added),
132
+ removed: sortByDocument(patch.removed)
133
+ });
134
+ dispatch(reduce(from, details), details);
135
+ };
136
+ /**
137
+ * The group as the DOM shows it (`checked`) or as a form reset leaves it (`defaultChecked`),
138
+ * laid over `base()` so that values with no Item are kept (they have no DOM to read).
139
+ */
140
+ const readDom = (prop) => new Map(readItems(fieldsetRef.current, name).map((item) => [item.value, item[prop]]));
141
+ const readGroup = (prop, from, dom = readDom(prop)) => {
142
+ const next = new Set(from);
143
+ for (const [v, isChecked] of dom) if (isChecked) next.add(v);
144
+ else next.delete(v);
145
+ return next;
146
+ };
147
+ /**
148
+ * The Root's own mount reconciliation ran: later member mounts are "late" (8.3). Set after the
149
+ * adapter's mount effect; reset by the cleanup (StrictMode included).
150
+ */
151
+ const rootMounted = useRef(false);
152
+ /**
153
+ * Late mounts: the members' `checked` read in the first late member's mount layout effect. The
154
+ * adapter reads the group in a microtask, but the Items' registration re-renders the group
155
+ * right after the layout effects, and that re-render re-applies each Item's `checked`, erasing a
156
+ * click made before the Item hydrated. Every member of the commit is in the DOM by then.
157
+ */
158
+ const lateSnapshot = useRef(null);
159
+ const { onItemMounted: onMemberMounted } = useNativeGroupReconciliation({
160
+ element: fieldsetRef,
161
+ readCurrent: () => {
162
+ const next = readGroup("checked", base(), lateSnapshot.current ?? void 0);
163
+ lateSnapshot.current = null;
164
+ const disabledNow = disabledOf(summarize(itemRegistry.current));
165
+ for (const report of parentReports.current) for (const v of enabledValues(report.values, disabledNow)) if (report.checked) next.add(v);
166
+ else next.delete(v);
167
+ parentReports.current = [];
168
+ return next;
169
+ },
170
+ readDefault: () => readGroup("defaultChecked", value === void 0 ? defaultSet ?? EMPTY : base()),
171
+ base,
172
+ equals: setsEqual,
173
+ onPlatformChange: (next, event) => {
174
+ const from = base();
175
+ const added = [...next].filter((v) => !from.has(v));
176
+ const removed = [...from].filter((v) => !next.has(v));
177
+ dispatchPatch(from, {
178
+ added,
179
+ removed
180
+ }, "platform", event);
181
+ }
182
+ });
183
+ useLayoutEffect(() => {
184
+ rootMounted.current = true;
185
+ return () => {
186
+ rootMounted.current = false;
187
+ };
188
+ }, []);
189
+ const onItemMounted = (el) => {
190
+ const late = rootMounted.current;
191
+ if (late && lateSnapshot.current === null) lateSnapshot.current = readDom("checked");
192
+ onMemberMounted(el);
193
+ if (late) queueMicrotask(() => {
194
+ lateSnapshot.current = null;
195
+ });
196
+ };
197
+ const [itemOrder, setItemOrder] = useState([]);
198
+ const renderedValue = sortByOrder(optimistic, itemOrder);
199
+ const syncItemOrder = useEffectEvent(() => {
200
+ const order = readItems(fieldsetRef.current, name).map((item) => item.value);
201
+ if (!arraysEqual(sortByOrder(optimistic, order), renderedValue)) setItemOrder(order);
202
+ if (!isDev || warnedDuplicate.current) return;
203
+ const duplicate = order.find((v, i) => order.indexOf(v) !== i);
204
+ if (duplicate !== void 0) {
205
+ warnedDuplicate.current = true;
206
+ console.error(`CheckboxGroup.Item: two Items share the value "${duplicate}" in one CheckboxGroup.Root. Item values must be unique; both boxes follow the same value.`);
207
+ }
208
+ });
209
+ useLayoutEffect(() => {
210
+ syncItemOrder();
211
+ });
212
+ const libraryRef = useCallback((el) => {
213
+ fieldsetRef.current = el;
214
+ if (isDev && !warnedElement.current && el.tagName !== "FIELDSET") {
215
+ warnedElement.current = true;
216
+ console.error(`CheckboxGroup.Root: expected to render a <fieldset> element but got <${el.tagName.toLowerCase()}>. The platform semantics (legend naming, disabled inheritance, form reset) need a real <fieldset>.`);
217
+ }
218
+ return () => {
219
+ fieldsetRef.current = null;
220
+ };
221
+ }, []);
222
+ const context = {
223
+ name,
224
+ form,
225
+ required: required && (items === null ? optimistic.size === 0 : ![...optimistic].some((v) => items.get(v) === true)),
226
+ disabledItems,
227
+ registerItem,
228
+ optimistic,
229
+ committed,
230
+ pending,
231
+ disabled,
232
+ defaultValue: value === void 0 ? defaultSet ?? EMPTY : void 0,
233
+ onItemMounted,
234
+ reportParent: (values, checked) => {
235
+ parentReports.current.push({
236
+ values,
237
+ checked
238
+ });
239
+ },
240
+ value: renderedValue,
241
+ sortByDocument,
242
+ toggle: (item, checked, event) => {
243
+ if (optimistic.has(item) === checked) return;
244
+ dispatchPatch(optimistic, checked ? {
245
+ added: [item],
246
+ removed: []
247
+ } : {
248
+ added: [],
249
+ removed: [item]
250
+ }, "activate", event);
251
+ },
252
+ apply: (patch, event) => {
253
+ const added = patch.added.filter((v) => !optimistic.has(v));
254
+ const removed = patch.removed.filter((v) => optimistic.has(v));
255
+ if (added.length === 0 && removed.length === 0) return;
256
+ dispatchPatch(optimistic, {
257
+ added,
258
+ removed
259
+ }, "activate", event);
260
+ }
261
+ };
262
+ const element = useRender({
263
+ defaultElement: "fieldset",
264
+ render,
265
+ state: {
266
+ value: renderedValue,
267
+ pending,
268
+ disabled
269
+ },
270
+ className,
271
+ style,
272
+ children,
273
+ props: rest,
274
+ libraryProps: {
275
+ disabled,
276
+ form,
277
+ "data-pending": dataAttr(pending),
278
+ "data-disabled": dataAttr(disabled)
279
+ },
280
+ ref: libraryRef
281
+ });
282
+ return /* @__PURE__ */ jsx(CheckboxGroupContext, {
283
+ value: context,
284
+ children: element
285
+ });
286
+ }
287
+ CheckboxGroupRoot.displayName = "CheckboxGroupRoot";
288
+ //#endregion
289
+ export { CheckboxGroupRoot };
290
+
291
+ //# sourceMappingURL=checkbox-group-root.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkbox-group-root.mjs","names":[],"sources":["../../src/checkbox-group/checkbox-group-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useEffectEvent,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type {\n ActionDetails,\n ChangeAction,\n ChangeReason,\n RenderProp,\n StateValue,\n} from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useNativeGroupReconciliation } from \"../internal/use-native-group-reconciliation.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { CheckboxGroupContext, enabledValues } from \"./checkbox-group-context.js\";\nimport type { CheckboxGroupContextValue, CheckboxGroupPatch } from \"./checkbox-group-context.js\";\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** Details passed as the second argument of `valueChangeAction`. */\nexport interface CheckboxGroupValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user toggled an Item or a Parent (click, Space, label click; `event` is the\n * native `click`). `\"platform\"`: the browser changed the checked boxes itself: a form reset\n * (`event` is the `reset` event), or clicks before hydration / Firefox form-state restoration\n * found at mount (`event` is `undefined`).\n */\n readonly reason: ChangeReason;\n /** Values this dispatch checked, in document order of their Items (values with no Item last). */\n readonly added: readonly string[];\n /** Values this dispatch unchecked, same order. */\n readonly removed: readonly string[];\n}\n\nexport interface CheckboxGroupRootState {\n /**\n * Optimistic checked values, in document order of the Items as of the Root's last render\n * (values with no Item last). Items reordered by a component that re-renders without the Root\n * keep the previous order here until the Root renders again; `next` and `details` of\n * `valueChangeAction` always read the current DOM order. The server render and the first client\n * render use the value's own order. Content rendered from it must not suspend.\n */\n readonly value: readonly string[];\n /** An action dispatched through this group is in flight. */\n readonly pending: boolean;\n /** The `disabled` prop. */\n readonly disabled: boolean;\n}\n\nexport interface CheckboxGroupRootProps extends Omit<\n ComponentPropsWithRef<\"fieldset\">,\n | \"className\"\n | \"style\"\n | \"children\"\n | \"defaultValue\"\n | \"defaultChecked\"\n | \"name\"\n | \"disabled\"\n | \"form\"\n> {\n /**\n * Controlled checked values. `[]` checks nothing; `undefined` means uncontrolled. Order is not\n * significant and duplicates are ignored: the value is a set.\n *\n * A form reset returns each box to the `checked` it had when *that box* mounted (React sets\n * `defaultChecked` once, from the initial `checked`). The reset is offered to\n * `valueChangeAction` as a `\"platform\"` change; for a stable target use `defaultValue`.\n */\n value?: string[];\n /**\n * Initial values in uncontrolled mode. Defaults to `[]`. Also the values a form reset returns\n * to: the latest prop value is mirrored into the boxes' `defaultChecked`.\n */\n defaultValue?: string[];\n /**\n * Runs inside the Root's transition whenever the set of checked values changes. May be sync\n * or async (also a Server Function); rejections reach the nearest Error Boundary and roll the\n * change back.\n *\n * `next` is the whole set as a fresh array in document order of the Items; treat it as a set.\n * `details.added` / `details.removed` say what changed. When controlled and updating after\n * your own `await`, prefer a functional update keyed by `added` / `removed` over\n * `setValue(next)`: two overlapping actions completing out of order would otherwise land the\n * older whole set last. A `setState` placed after your own `await` is not a Transition update;\n * wrap it in `startTransition` (React limitation). `details.event.preventDefault()` is not a\n * veto; use controlled mode to veto a change.\n */\n valueChangeAction?: ChangeAction<string[], CheckboxGroupValueChangeDetails>;\n /**\n * The `name` shared by every Item (form data key: `formData.getAll(name)`). Defaults to a\n * `useId()` value; pass a real name when the group is inside a `<form>` you submit.\n */\n name?: string;\n /** The `form` attribute, applied to the fieldset and to every Item. */\n form?: string;\n /**\n * \"At least one Item must be checked.\" Rendered as native `required` on every Item while no\n * enabled Item is checked, and on none once one is: values with no Item and checked disabled\n * Items (neither is submitted) do not satisfy it. Until the Items have mounted (server HTML,\n * before hydration) any checked value satisfies it. Not exposed on the fieldset (ARIA does not\n * allow `aria-required` on role `group`): say it in the `Label` or through `aria-describedby`.\n */\n required?: boolean;\n /** Renders `<fieldset disabled>`: every Item and Parent is disabled and unfocusable. */\n disabled?: boolean;\n className?: StateValue<string | undefined, CheckboxGroupRootState>;\n style?: StateValue<CSSProperties | undefined, CheckboxGroupRootState>;\n children?: StateValue<ReactNode, CheckboxGroupRootState>;\n /** Replace the rendered element. Must keep a `<fieldset>`. */\n render?: RenderProp<ComponentPropsWithRef<\"fieldset\">, CheckboxGroupRootState>;\n}\n\ntype ValueSet = ReadonlySet<string>;\n\nconst EMPTY: ValueSet = new Set();\n\nfunction toSet(value: readonly string[]): ValueSet {\n return new Set(value);\n}\n\nfunction setsEqual(a: ValueSet, b: ValueSet): boolean {\n if (a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n}\n\n/** The optimistic reducer (design 5.2): apply a patch, so stacked dispatches re-base. */\nfunction reduce(set: ValueSet, patch: CheckboxGroupPatch): ValueSet {\n const next = new Set(set);\n for (const v of patch.removed) next.delete(v);\n for (const v of patch.added) next.add(v);\n return next;\n}\n\n/**\n * `values` sorted by `order` (the Items' values in document order); values not in `order` keep\n * their iteration order, after the others.\n */\nfunction sortByOrder(values: Iterable<string>, order: readonly string[]): string[] {\n const wanted = new Set(values);\n const sorted: string[] = [];\n for (const v of order) if (wanted.delete(v)) sorted.push(v);\n for (const v of wanted) sorted.push(v);\n return sorted;\n}\n\n/** Value → \"has an Item without its own `disabled`\", over the registered Items. */\ntype ItemRegistry = Map<object, { readonly value: string; readonly disabled: boolean }>;\n\nfunction summarize(registry: ItemRegistry): Map<string, boolean> {\n const items = new Map<string, boolean>();\n for (const { value, disabled } of registry.values()) {\n items.set(value, (items.get(value) ?? false) || !disabled);\n }\n return items;\n}\n\nfunction disabledOf(items: ReadonlyMap<string, boolean> | null): ValueSet {\n if (items === null) return EMPTY;\n const disabled = new Set<string>();\n for (const [value, enabled] of items) if (!enabled) disabled.add(value);\n return disabled.size === 0 ? EMPTY : disabled;\n}\n\nfunction mapsEqual(a: ReadonlyMap<string, boolean>, b: ReadonlyMap<string, boolean>): boolean {\n if (a.size !== b.size) return false;\n for (const [k, v] of a) if (b.get(k) !== v) return false;\n return true;\n}\n\nfunction arraysEqual(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((v, i) => v === b[i]);\n}\n\n/** The group's Items: checkboxes of this `name` in the fieldset (Parents have none). */\nfunction readItems(fieldset: HTMLFieldSetElement | null, name: string): HTMLInputElement[] {\n if (fieldset === null) return [];\n return [...fieldset.querySelectorAll<HTMLInputElement>('input[type=\"checkbox\"]')].filter(\n (box) => box.name === name,\n );\n}\n\n/**\n * `<fieldset>` that owns the group's state (a set of values), the transition, the shared `name`,\n * the form-reset listener, the mount reconciliation and the group-level `required`\n * (design/components/checkbox-group.md 14.3). Carries `data-pending` while `valueChangeAction`\n * is in flight. Its implicit role is `group`; no `role` is written.\n *\n * Owned keys (conventions 1.2): `disabled`, `form`, `data-pending`, `data-disabled`.\n *\n * Unlike RadioGroup, Items may hydrate after their Root (a Suspense boundary between them): a\n * click before an Item hydrated is reconciled when it does.\n */\nexport function CheckboxGroupRoot({\n value,\n defaultValue,\n valueChangeAction,\n name: nameProp,\n form,\n required = false,\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: CheckboxGroupRootProps) {\n const fallbackName = useId();\n const name = nameProp ?? fallbackName;\n const fieldsetRef = useRef<HTMLFieldSetElement | null>(null);\n const warnedElement = useRef(false);\n const warnedDuplicate = useRef(false);\n /** Parent reports waiting for the next mount read (design 8.1). */\n const parentReports = useRef<{ values: readonly string[]; checked: boolean }[]>([]);\n\n // Item registration (design 6.2, 7): each Item reports its value and own `disabled` from a\n // layout effect. The ref is current for the residual-path reads; the state, set only when the\n // summary changes, is what render uses. `null` until an Item registered, so the server render\n // and the hydration render (which cannot know) agree.\n const itemRegistry = useRef<ItemRegistry>(new Map());\n const [items, setItems] = useState<ReadonlyMap<string, boolean> | null>(null);\n const registerItem = useCallback((value: string, disabled: boolean) => {\n const publish = () => {\n const next = summarize(itemRegistry.current);\n setItems((prev) => (prev !== null && mapsEqual(prev, next) ? prev : next));\n };\n const token = {};\n itemRegistry.current.set(token, { value, disabled });\n publish();\n return () => {\n itemRegistry.current.delete(token);\n publish();\n };\n }, []);\n const disabledItems = useMemo(() => disabledOf(items), [items]);\n\n const controlledSet = useMemo(() => (value === undefined ? undefined : toSet(value)), [value]);\n const defaultSet = useMemo(\n () => (defaultValue === undefined ? undefined : toSet(defaultValue)),\n [defaultValue],\n );\n\n /** Sorts by the Items' current document order. Reads the DOM: handlers and effects only. */\n const sortByDocument = (values: Iterable<string>): string[] =>\n sortByOrder(\n values,\n readItems(fieldsetRef.current, name).map((item) => item.value),\n );\n\n const { optimistic, committed, pending, dispatch, latestInFlight } = useControllableAction<\n ValueSet,\n CheckboxGroupValueChangeDetails\n >({\n controlled: controlledSet,\n defaultValue: defaultSet,\n initialValue: EMPTY,\n action:\n valueChangeAction && ((next, details) => valueChangeAction(sortByDocument(next), details)),\n transitionTypes: (_next, _prev, details) => [\n ...transitionTypes(\"checkbox-group\"),\n ...(details.added.length > 0 ? [\"fsui-checkbox-group-check\"] : []),\n ...(details.removed.length > 0 ? [\"fsui-checkbox-group-uncheck\"] : []),\n ],\n reducer: (prev, _next, details) => reduce(prev, details),\n warn: { component: \"CheckboxGroup.Root\", prop: \"value\" },\n });\n // Deliberately no warning when neither `value` nor `defaultValue` is given: an empty group is\n // the HTML default (design 3.1).\n\n /** What the user intends: the latest in-flight dispatch, else optimistic (conventions 3.8 rule 1). */\n const base = (): ValueSet => {\n const inFlight = latestInFlight();\n return inFlight === null ? optimistic : inFlight.next;\n };\n\n /** Dispatches `patch` on top of `from`, with the details of design 3.1. */\n const dispatchPatch = (\n from: ValueSet,\n patch: CheckboxGroupPatch,\n reason: ChangeReason,\n event: Event | undefined,\n ): void => {\n const details = createActionDetails(event, {\n reason,\n added: sortByDocument(patch.added),\n removed: sortByDocument(patch.removed),\n });\n dispatch(reduce(from, details), details);\n };\n\n /**\n * The group as the DOM shows it (`checked`) or as a form reset leaves it (`defaultChecked`),\n * laid over `base()` so that values with no Item are kept (they have no DOM to read).\n */\n const readDom = (prop: \"checked\" | \"defaultChecked\"): Map<string, boolean> =>\n new Map(readItems(fieldsetRef.current, name).map((item) => [item.value, item[prop]]));\n const readGroup = (\n prop: \"checked\" | \"defaultChecked\",\n from: ValueSet,\n dom: ReadonlyMap<string, boolean> = readDom(prop),\n ): Set<string> => {\n const next = new Set(from);\n for (const [v, isChecked] of dom) {\n if (isChecked) next.add(v);\n else next.delete(v);\n }\n return next;\n };\n\n /**\n * The Root's own mount reconciliation ran: later member mounts are \"late\" (8.3). Set after the\n * adapter's mount effect; reset by the cleanup (StrictMode included).\n */\n const rootMounted = useRef(false);\n /**\n * Late mounts: the members' `checked` read in the first late member's mount layout effect. The\n * adapter reads the group in a microtask, but the Items' registration re-renders the group\n * right after the layout effects, and that re-render re-applies each Item's `checked`, erasing a\n * click made before the Item hydrated. Every member of the commit is in the DOM by then.\n */\n const lateSnapshot = useRef<Map<string, boolean> | null>(null);\n\n // The residual browser paths (design section 8): one dispatch per group, from here. Items and\n // Parents that mount after the Root report through `onItemMounted`.\n const { onItemMounted: onMemberMounted } = useNativeGroupReconciliation<ValueSet>({\n element: fieldsetRef,\n readCurrent: () => {\n const next = readGroup(\"checked\", base(), lateSnapshot.current ?? undefined);\n lateSnapshot.current = null;\n // A Parent clicked before hydration means what its click means: check / uncheck all of its\n // values whose Items are enabled (8.1, 6.2). Read from the registry ref: the Items of this\n // commit registered before this runs, and render has not caught up yet.\n const disabledNow = disabledOf(summarize(itemRegistry.current));\n for (const report of parentReports.current) {\n for (const v of enabledValues(report.values, disabledNow)) {\n if (report.checked) next.add(v);\n else next.delete(v);\n }\n }\n parentReports.current = [];\n return next;\n },\n // Uncontrolled: start from `defaultValue`, so values with no Item return to it too (they have\n // no `defaultChecked` to read). Controlled: from `base()`, as for the mount read; the parent\n // decides.\n readDefault: () =>\n readGroup(\"defaultChecked\", value === undefined ? (defaultSet ?? EMPTY) : base()),\n base,\n equals: setsEqual,\n onPlatformChange: (next, event) => {\n const from = base();\n const added = [...next].filter((v) => !from.has(v));\n const removed = [...from].filter((v) => !next.has(v));\n dispatchPatch(from, { added, removed }, \"platform\", event);\n },\n });\n\n useLayoutEffect(() => {\n rootMounted.current = true;\n return () => {\n rootMounted.current = false;\n };\n }, []);\n const onItemMounted = (el: HTMLInputElement | null) => {\n const late = rootMounted.current;\n if (late && lateSnapshot.current === null) lateSnapshot.current = readDom(\"checked\");\n onMemberMounted(el);\n // A snapshot the adapter did not consume (it queues one read per commit, before this).\n if (late) {\n queueMicrotask(() => {\n lateSnapshot.current = null;\n });\n }\n };\n\n // The state's `value` is in document order. Render cannot read the DOM, so the Items' order is\n // kept in state, refreshed after a commit only when the rendered `value` would change (a value\n // given out of document order, Items reordered or added). The first render, and the server,\n // use the value's own order.\n const [itemOrder, setItemOrder] = useState<readonly string[]>([]);\n const renderedValue = sortByOrder(optimistic, itemOrder);\n // After every commit of the Root (a deps-less layout effect runs only when the Root itself\n // committed: Items moved by a child that re-renders alone are picked up at the Root's next\n // render). The comparison ends the chain: the re-render it causes renders exactly the value\n // just compared against.\n const syncItemOrder = useEffectEvent(() => {\n const order = readItems(fieldsetRef.current, name).map((item) => item.value);\n if (!arraysEqual(sortByOrder(optimistic, order), renderedValue)) setItemOrder(order);\n\n // Dev only: Item values must be unique within the Root (design 14.6).\n if (!isDev || warnedDuplicate.current) return;\n const duplicate = order.find((v, i) => order.indexOf(v) !== i);\n if (duplicate !== undefined) {\n warnedDuplicate.current = true;\n console.error(\n `CheckboxGroup.Item: two Items share the value \"${duplicate}\" in one ` +\n `CheckboxGroup.Root. Item values must be unique; both boxes follow the same value.`,\n );\n }\n });\n useLayoutEffect(() => {\n syncItemOrder();\n });\n\n const libraryRef = useCallback((el: HTMLFieldSetElement) => {\n fieldsetRef.current = el;\n if (isDev && !warnedElement.current && el.tagName !== \"FIELDSET\") {\n warnedElement.current = true;\n console.error(\n `CheckboxGroup.Root: expected to render a <fieldset> element but got ` +\n `<${el.tagName.toLowerCase()}>. The platform semantics (legend naming, disabled ` +\n `inheritance, form reset) need a real <fieldset>.`,\n );\n }\n return () => {\n fieldsetRef.current = null;\n };\n }, []);\n\n const context: CheckboxGroupContextValue = {\n name,\n form,\n required:\n required &&\n (items === null\n ? optimistic.size === 0\n : ![...optimistic].some((v) => items.get(v) === true)),\n disabledItems,\n registerItem,\n optimistic,\n committed,\n pending,\n disabled,\n defaultValue: value === undefined ? (defaultSet ?? EMPTY) : undefined,\n onItemMounted,\n reportParent: (values, checked) => {\n parentReports.current.push({ values, checked });\n },\n value: renderedValue,\n sortByDocument,\n toggle: (item, checked, event) => {\n // No-op rule against the optimistic set (conventions 2.3 item 8).\n if (optimistic.has(item) === checked) return;\n const patch = checked ? { added: [item], removed: [] } : { added: [], removed: [item] };\n dispatchPatch(optimistic, patch, \"activate\", event);\n },\n apply: (patch, event) => {\n const added = patch.added.filter((v) => !optimistic.has(v));\n const removed = patch.removed.filter((v) => optimistic.has(v));\n if (added.length === 0 && removed.length === 0) return;\n dispatchPatch(optimistic, { added, removed }, \"activate\", event);\n },\n };\n\n const state: CheckboxGroupRootState = {\n value: renderedValue,\n pending,\n disabled,\n };\n\n const element = useRender<ComponentPropsWithRef<\"fieldset\">, CheckboxGroupRootState>({\n defaultElement: \"fieldset\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n disabled,\n form,\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n },\n ref: libraryRef,\n });\n\n return <CheckboxGroupContext value={context}>{element}</CheckboxGroupContext>;\n}\n\nCheckboxGroupRoot.displayName = \"CheckboxGroupRoot\";\n"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import "../internal/platform-attributes.mjs";
2
+ import { CheckboxGroupItem, CheckboxGroupItemProps, CheckboxGroupItemState } from "./checkbox-group-item.mjs";
3
+ import { CheckboxGroupRoot, CheckboxGroupRootProps, CheckboxGroupRootState, CheckboxGroupValueChangeDetails } from "./checkbox-group-root.mjs";
4
+ import { CheckboxGroupLabel, CheckboxGroupLabelProps } from "./checkbox-group-label.mjs";
5
+ import { CheckboxGroupParent, CheckboxGroupParentProps, CheckboxGroupParentState } from "./checkbox-group-parent.mjs";
6
+ //#region src/checkbox-group/index.d.ts
7
+ /**
8
+ * CheckboxGroup (WAI-ARIA APG Checkbox pattern, native HTML variant): `Root` (`fieldset`, role
9
+ * `group`), an optional `Label` (`legend`, first child), one `Item` (`input[type=checkbox]`) per
10
+ * value, and optional `Parent` boxes (the mixed-state "select all" over a subset of values). The
11
+ * value is a set of strings; the keyboard is entirely native. Each box's DOM `checked` is
12
+ * optimistic; the Root and the boxes whose state is changing carry `data-pending` while
13
+ * `valueChangeAction` is in flight.
14
+ */
15
+ export declare const CheckboxGroup: {
16
+ readonly Root: typeof CheckboxGroupRoot;
17
+ readonly Label: typeof CheckboxGroupLabel;
18
+ readonly Item: typeof CheckboxGroupItem;
19
+ readonly Parent: typeof CheckboxGroupParent;
20
+ };
21
+ //#endregion
22
+ export type { CheckboxGroupItemProps, CheckboxGroupItemState, CheckboxGroupLabelProps, CheckboxGroupParentProps, CheckboxGroupParentState, CheckboxGroupRootProps, CheckboxGroupRootState, CheckboxGroupValueChangeDetails };
23
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/checkbox-group/index.ts"],"mappings":";;;;;;;;;;;;;;qBAgBa;WACX,aAAI;WACJ,cAAK;WACL,aAAI;WACJ,eAAM"}
@@ -0,0 +1,23 @@
1
+ import { CheckboxGroupItem } from "./checkbox-group-item.mjs";
2
+ import { CheckboxGroupLabel } from "./checkbox-group-label.mjs";
3
+ import { CheckboxGroupParent } from "./checkbox-group-parent.mjs";
4
+ import { CheckboxGroupRoot } from "./checkbox-group-root.mjs";
5
+ //#region src/checkbox-group/index.ts
6
+ /**
7
+ * CheckboxGroup (WAI-ARIA APG Checkbox pattern, native HTML variant): `Root` (`fieldset`, role
8
+ * `group`), an optional `Label` (`legend`, first child), one `Item` (`input[type=checkbox]`) per
9
+ * value, and optional `Parent` boxes (the mixed-state "select all" over a subset of values). The
10
+ * value is a set of strings; the keyboard is entirely native. Each box's DOM `checked` is
11
+ * optimistic; the Root and the boxes whose state is changing carry `data-pending` while
12
+ * `valueChangeAction` is in flight.
13
+ */
14
+ const CheckboxGroup = {
15
+ Root: CheckboxGroupRoot,
16
+ Label: CheckboxGroupLabel,
17
+ Item: CheckboxGroupItem,
18
+ Parent: CheckboxGroupParent
19
+ };
20
+ //#endregion
21
+ export { CheckboxGroup };
22
+
23
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/checkbox-group/index.ts"],"sourcesContent":["// Ships the platform attribute typings with this subpath (conventions 7.4). Type-only: the\n// built JS has no import of it.\nimport \"../internal/platform-attributes.js\";\nimport { CheckboxGroupItem } from \"./checkbox-group-item.js\";\nimport { CheckboxGroupLabel } from \"./checkbox-group-label.js\";\nimport { CheckboxGroupParent } from \"./checkbox-group-parent.js\";\nimport { CheckboxGroupRoot } from \"./checkbox-group-root.js\";\n\n/**\n * CheckboxGroup (WAI-ARIA APG Checkbox pattern, native HTML variant): `Root` (`fieldset`, role\n * `group`), an optional `Label` (`legend`, first child), one `Item` (`input[type=checkbox]`) per\n * value, and optional `Parent` boxes (the mixed-state \"select all\" over a subset of values). The\n * value is a set of strings; the keyboard is entirely native. Each box's DOM `checked` is\n * optimistic; the Root and the boxes whose state is changing carry `data-pending` while\n * `valueChangeAction` is in flight.\n */\nexport const CheckboxGroup = {\n Root: CheckboxGroupRoot,\n Label: CheckboxGroupLabel,\n Item: CheckboxGroupItem,\n Parent: CheckboxGroupParent,\n} as const;\n\nexport type {\n CheckboxGroupRootProps,\n CheckboxGroupRootState,\n CheckboxGroupValueChangeDetails,\n} from \"./checkbox-group-root.js\";\nexport type { CheckboxGroupLabelProps } from \"./checkbox-group-label.js\";\nexport type { CheckboxGroupItemProps, CheckboxGroupItemState } from \"./checkbox-group-item.js\";\nexport type {\n CheckboxGroupParentProps,\n CheckboxGroupParentState,\n} from \"./checkbox-group-parent.js\";\n"],"mappings":";;;;;;;;;;;;;AAgBA,MAAa,gBAAgB;CAC3B,MAAM;CACN,OAAO;CACP,MAAM;CACN,QAAQ;AACV"}
@@ -1 +1 @@
1
- {"version":3,"file":"dialog-close.d.mts","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"mappings":";;;iBAUiB;;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"}
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, useTransition } from "react";
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 [pending, startOwnTransition] = useTransition();
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
- startOwnTransition(async () => {
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, useTransition } 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 { 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 const [pending, startOwnTransition] = useTransition();\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 startOwnTransition(async () => {\n await action(details);\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 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":";;;;;;;AAyCA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;AAYvC,SAAgB,YAAY,EAC1B,QACA,OACA,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACgB;CACnB,MAAM,MAAM,iBAAiB,cAAc;CAC3C,MAAM,CAAC,SAAS,sBAAsB,cAAc;CACpD,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,mBAAmB,YAAY;GAC7B,MAAM,OAAO,OAAO;GAEpB,MAAM,SAAS,UAAU;GACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;GACrC,kBAAkB;IAAE,QAAQ;IAAY,OAAO;GAAO,CAAC;GACvD,OAAO,MAAM,WAAW;EAC1B,CAAC;CACH;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
+ {"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":";;;AAsDA,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"}
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;