@jsswift/ui 1.0.45 → 1.0.47

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/src/70-dialog.js CHANGED
@@ -1,21 +1,21 @@
1
- UI.Dialog = (...args) => {
2
- const { props, children } = JSswift.uiNormalizeArgs(args);
3
- const slots = props.slots || {};
4
- const stateList = ["primary", "secondary", "warning", "danger", "success", "info", "light", "dark"];
5
- const sizeList = ["xs", "sm", "md", "lg", "xl", "full"];
1
+ UI.Dialog = (...args) => {
2
+ const { props, children } = JSswift.uiNormalizeArgs(args);
3
+ const slots = props.slots || {};
4
+ const stateList = ["primary", "secondary", "warning", "danger", "success", "info", "light", "dark"];
5
+ const sizeList = ["xs", "sm", "md", "lg", "xl", "full"];
6
6
  let currentProps = { ...props };
7
7
  let entry = null;
8
8
  let lastActive = null;
9
- const resolveRender = (value, ctx) => typeof value === "function" ? value(ctx) : value;
10
- const splitClasses = (value) => String(value || "").split(/\s+/).filter(Boolean);
11
- const setTrackedClasses = (target, key, classes) => {
12
- if (!target) return;
13
- const prev = target[key] || [];
14
- if (prev.length) target.classList.remove(...prev);
15
- const next = (classes || []).filter(Boolean);
16
- if (next.length) target.classList.add(...next);
17
- target[key] = next;
18
- };
9
+ const resolveRender = (value, ctx) => typeof value === "function" ? value(ctx) : value;
10
+ const splitClasses = (value) => String(value || "").split(/\s+/).filter(Boolean);
11
+ const setTrackedClasses = (target, key, classes) => {
12
+ if (!target) return;
13
+ const prev = target[key] || [];
14
+ if (prev.length) target.classList.remove(...prev);
15
+ const next = (classes || []).filter(Boolean);
16
+ if (next.length) target.classList.add(...next);
17
+ target[key] = next;
18
+ };
19
19
  const setStyleValue = (target, name, value, formatter = (v) => v) => {
20
20
  if (!target) return;
21
21
  if (value == null || value === "") {
@@ -120,141 +120,141 @@
120
120
  };
121
121
  };
122
122
  const getOptions = () => currentProps;
123
- const getStateClass = (opts) => {
124
- const value = uiUnwrap(opts.state ?? opts.color);
125
- if (!value) return "";
126
- return stateList.includes(value) ? value : String(value);
127
- };
128
- const getSizeClass = (opts) => {
129
- const value = uiUnwrap(opts.size);
130
- return (typeof value === "string" && sizeList.includes(value)) ? value : "";
131
- };
132
- const getAlignClass = (opts) => {
133
- const raw = String(uiUnwrap(opts.actionsAlign ?? opts.footerAlign ?? opts.alignActions ?? "end")).toLowerCase();
134
- if (["start", "left"].includes(raw)) return "start";
135
- if (["center", "middle"].includes(raw)) return "center";
136
- if (["between", "space-between"].includes(raw)) return "between";
137
- if (["stretch", "full"].includes(raw)) return "stretch";
138
- return "end";
139
- };
140
- const getVerticalAlignClass = (opts) => {
141
- const raw = String(uiUnwrap(opts.align ?? opts.verticalAlign ?? opts.position ?? "center")).toLowerCase();
142
- if (["top", "start", "flex-start"].includes(raw)) return "cms-dialog-align-top";
143
- if (["bottom", "end", "flex-end"].includes(raw)) return "cms-dialog-align-bottom";
144
- return "";
145
- };
146
- const isClosable = (opts) => {
147
- const value = opts.closable ?? opts.dismissible ?? opts.closeButton;
148
- return value !== false;
149
- };
150
- const buildContent = () => {
151
- const opts = getOptions();
152
- const ctx = {
153
- close,
154
- dismiss: close,
155
- open,
156
- toggle,
157
- update,
158
- isOpen,
159
- entry: () => entry,
160
- props: opts
161
- };
162
- const iconFallback = opts.icon != null
163
- ? (typeof opts.icon === "string"
164
- ? UI.Icon({ name: opts.icon, size: opts.iconSize || "md" })
165
- : JSswift.ui.slot(opts.icon, { as: "icon" }))
166
- : null;
167
- const eyebrowNodes = renderSlotToArray(slots, "eyebrow", ctx, resolveRender(opts.eyebrow, ctx));
168
- const titleNodes = renderSlotToArray(slots, "title", ctx, resolveRender(opts.title ?? opts.heading ?? opts.header, ctx));
169
- const subtitleNodes = renderSlotToArray(slots, "subtitle", ctx, resolveRender(opts.subtitle ?? opts.description, ctx));
170
- const iconNodes = renderSlotToArray(slots, "icon", ctx, iconFallback);
171
- const customHeaderNodes = renderSlotToArray(slots, "header", ctx, resolveRender(opts.headerContent ?? opts.head, ctx));
172
- const closeSlotNodes = isClosable(opts)
173
- ? renderSlotToArray(slots, "close", ctx, UI.Btn({
174
- class: "cms-dialog-close",
175
- size: "sm",
176
- outline: true,
177
- "aria-label": opts.closeLabel || "Chiudi dialog",
178
- "data-dialog-close": true
179
- }, UI.Icon({ name: opts.closeIcon || "close", size: "sm" })))
180
- : [];
181
- const bodyRaw = opts.content ?? opts.body ?? opts.message ?? (children && children.length ? children : null);
182
- let contentNodes = renderSlotToArray(slots, "content", ctx, resolveRender(bodyRaw, ctx));
183
- if (!contentNodes.length) contentNodes = renderSlotToArray(slots, "body", ctx, resolveRender(bodyRaw, ctx));
184
- if (!contentNodes.length) contentNodes = renderSlotToArray(slots, "default", ctx, resolveRender(bodyRaw, ctx));
185
- const footerRaw = resolveRender(opts.footer ?? opts.actions, ctx);
186
- let footerNodes = renderSlotToArray(slots, "footer", ctx, footerRaw);
187
- if (!footerNodes.length) footerNodes = renderSlotToArray(slots, "actions", ctx, footerRaw);
188
-
189
- let headerEl = null;
190
- if (customHeaderNodes.length) {
191
- headerEl = _.div({ class: "cms-dialog-head cms-dialog-head-custom" }, ...customHeaderNodes);
192
- } else if (eyebrowNodes.length || titleNodes.length || subtitleNodes.length || iconNodes.length || closeSlotNodes.length) {
193
- headerEl = _.div(
194
- { class: "cms-dialog-head" },
195
- iconNodes.length ? _.div({ class: "cms-dialog-icon" }, ...iconNodes) : null,
196
- _.div(
197
- { class: "cms-dialog-head-main" },
198
- eyebrowNodes.length ? _.div({ class: "cms-dialog-eyebrow" }, ...eyebrowNodes) : null,
199
- titleNodes.length ? _.div({ class: "cms-dialog-title" }, ...titleNodes) : null,
200
- subtitleNodes.length ? _.div({ class: "cms-dialog-subtitle" }, ...subtitleNodes) : null
201
- ),
202
- closeSlotNodes.length ? _.div({ class: "cms-dialog-close-wrap" }, ...closeSlotNodes) : null
203
- );
204
- }
205
-
206
- const bodyEl = _.div(
207
- { class: uiClass(["cms-dialog-body", opts.bodyClass]) },
208
- ...contentNodes
209
- );
210
- const footerEl = footerNodes.length
211
- ? _.div({
212
- class: uiClass([
213
- "cms-dialog-actions",
214
- `is-${getAlignClass(opts)}`,
215
- uiWhen(opts.stackActions, "is-stacked"),
216
- opts.actionsClass,
217
- opts.footerClass
218
- ])
219
- }, ...footerNodes)
220
- : null;
221
-
222
- return _.div({
223
- class: uiClass([
224
- "cms-dialog-shell",
225
- uiWhen(!!headerEl, "has-head"),
226
- uiWhen(!!footerEl, "has-footer"),
227
- uiWhen(opts.divider !== false, "with-divider")
228
- ])
229
- }, ...[headerEl, bodyEl, footerEl].filter(Boolean));
230
- };
231
- const applyEntryOptions = (currentEntry) => {
232
- if (!currentEntry?.panel) return;
233
- const opts = getOptions();
234
- const stateClass = getStateClass(opts);
235
- const sizeClass = getSizeClass(opts);
236
- setTrackedClasses(currentEntry.panel, "_dialogClassTokens", [
237
- "cms-dialog",
238
- "cms-singularity",
239
- "cms-clear-set",
240
- sizeClass ? `cms-dialog-${sizeClass}` : "",
241
- stateClass,
242
- stateClass ? `cms-state-${stateClass}` : "",
243
- uiUnwrap(opts.fullscreen) ? "fullscreen" : "",
244
- uiUnwrap(opts.scrollable) ? "scrollable" : "",
245
- uiUnwrap(opts.stickyHeader) ? "sticky-head" : "",
246
- uiUnwrap(opts.stickyActions) ? "sticky-actions" : "",
247
- uiUnwrap(opts.borderless) ? "borderless" : "",
248
- ...splitClasses(opts.class),
249
- ...splitClasses(opts.panelClass)
250
- ]);
251
- setTrackedClasses(currentEntry.overlay, "_dialogOverlayClassTokens", [
252
- getVerticalAlignClass(opts),
253
- uiUnwrap(opts.backdropBlur) ? "cms-dialog-overlay-blur" : "",
254
- ...splitClasses(opts.overlayClass)
255
- ]);
256
- setStyleValue(currentEntry.panel, "--cms-dialog-width", uiUnwrap(opts.width), toCssSize);
257
- setStyleValue(currentEntry.panel, "--cms-dialog-min-width", uiUnwrap(opts.minWidth), toCssSize);
123
+ const getStateClass = (opts) => {
124
+ const value = uiUnwrap(opts.state ?? opts.color);
125
+ if (!value) return "";
126
+ return stateList.includes(value) ? value : String(value);
127
+ };
128
+ const getSizeClass = (opts) => {
129
+ const value = uiUnwrap(opts.size);
130
+ return (typeof value === "string" && sizeList.includes(value)) ? value : "";
131
+ };
132
+ const getAlignClass = (opts) => {
133
+ const raw = String(uiUnwrap(opts.actionsAlign ?? opts.footerAlign ?? opts.alignActions ?? "end")).toLowerCase();
134
+ if (["start", "left"].includes(raw)) return "start";
135
+ if (["center", "middle"].includes(raw)) return "center";
136
+ if (["between", "space-between"].includes(raw)) return "between";
137
+ if (["stretch", "full"].includes(raw)) return "stretch";
138
+ return "end";
139
+ };
140
+ const getVerticalAlignClass = (opts) => {
141
+ const raw = String(uiUnwrap(opts.align ?? opts.verticalAlign ?? opts.position ?? "center")).toLowerCase();
142
+ if (["top", "start", "flex-start"].includes(raw)) return "cms-dialog-align-top";
143
+ if (["bottom", "end", "flex-end"].includes(raw)) return "cms-dialog-align-bottom";
144
+ return "";
145
+ };
146
+ const isClosable = (opts) => {
147
+ const value = opts.closable ?? opts.dismissible ?? opts.closeButton;
148
+ return value !== false;
149
+ };
150
+ const buildContent = () => {
151
+ const opts = getOptions();
152
+ const ctx = {
153
+ close,
154
+ dismiss: close,
155
+ open,
156
+ toggle,
157
+ update,
158
+ isOpen,
159
+ entry: () => entry,
160
+ props: opts
161
+ };
162
+ const iconFallback = opts.icon != null
163
+ ? (typeof opts.icon === "string"
164
+ ? UI.Icon({ name: opts.icon, size: opts.iconSize || "md" })
165
+ : JSswift.ui.slot(opts.icon, { as: "icon" }))
166
+ : null;
167
+ const eyebrowNodes = renderSlotToArray(slots, "eyebrow", ctx, resolveRender(opts.eyebrow, ctx));
168
+ const titleNodes = renderSlotToArray(slots, "title", ctx, resolveRender(opts.title ?? opts.heading ?? opts.header, ctx));
169
+ const subtitleNodes = renderSlotToArray(slots, "subtitle", ctx, resolveRender(opts.subtitle ?? opts.description, ctx));
170
+ const iconNodes = renderSlotToArray(slots, "icon", ctx, iconFallback);
171
+ const customHeaderNodes = renderSlotToArray(slots, "header", ctx, resolveRender(opts.headerContent ?? opts.head, ctx));
172
+ const closeSlotNodes = isClosable(opts)
173
+ ? renderSlotToArray(slots, "close", ctx, UI.Btn({
174
+ class: "cms-dialog-close",
175
+ size: "sm",
176
+ outline: true,
177
+ "aria-label": opts.closeLabel || "Chiudi dialog",
178
+ "data-dialog-close": true
179
+ }, UI.Icon({ name: opts.closeIcon || "close", size: "sm" })))
180
+ : [];
181
+ const bodyRaw = opts.content ?? opts.body ?? opts.message ?? (children && children.length ? children : null);
182
+ let contentNodes = renderSlotToArray(slots, "content", ctx, resolveRender(bodyRaw, ctx));
183
+ if (!contentNodes.length) contentNodes = renderSlotToArray(slots, "body", ctx, resolveRender(bodyRaw, ctx));
184
+ if (!contentNodes.length) contentNodes = renderSlotToArray(slots, "default", ctx, resolveRender(bodyRaw, ctx));
185
+ const footerRaw = resolveRender(opts.footer ?? opts.actions, ctx);
186
+ let footerNodes = renderSlotToArray(slots, "footer", ctx, footerRaw);
187
+ if (!footerNodes.length) footerNodes = renderSlotToArray(slots, "actions", ctx, footerRaw);
188
+
189
+ let headerEl = null;
190
+ if (customHeaderNodes.length) {
191
+ headerEl = _.div({ class: "cms-dialog-head cms-dialog-head-custom" }, ...customHeaderNodes);
192
+ } else if (eyebrowNodes.length || titleNodes.length || subtitleNodes.length || iconNodes.length || closeSlotNodes.length) {
193
+ headerEl = _.div(
194
+ { class: "cms-dialog-head" },
195
+ iconNodes.length ? _.div({ class: "cms-dialog-icon" }, ...iconNodes) : null,
196
+ _.div(
197
+ { class: "cms-dialog-head-main" },
198
+ eyebrowNodes.length ? _.div({ class: "cms-dialog-eyebrow" }, ...eyebrowNodes) : null,
199
+ titleNodes.length ? _.div({ class: "cms-dialog-title" }, ...titleNodes) : null,
200
+ subtitleNodes.length ? _.div({ class: "cms-dialog-subtitle" }, ...subtitleNodes) : null
201
+ ),
202
+ closeSlotNodes.length ? _.div({ class: "cms-dialog-close-wrap" }, ...closeSlotNodes) : null
203
+ );
204
+ }
205
+
206
+ const bodyEl = _.div(
207
+ { class: uiClass(["cms-dialog-body", opts.bodyClass]) },
208
+ ...contentNodes
209
+ );
210
+ const footerEl = footerNodes.length
211
+ ? _.div({
212
+ class: uiClass([
213
+ "cms-dialog-actions",
214
+ `is-${getAlignClass(opts)}`,
215
+ uiWhen(opts.stackActions, "is-stacked"),
216
+ opts.actionsClass,
217
+ opts.footerClass
218
+ ])
219
+ }, ...footerNodes)
220
+ : null;
221
+
222
+ return _.div({
223
+ class: uiClass([
224
+ "cms-dialog-shell",
225
+ uiWhen(!!headerEl, "has-head"),
226
+ uiWhen(!!footerEl, "has-footer"),
227
+ uiWhen(opts.divider !== false, "with-divider")
228
+ ])
229
+ }, ...[headerEl, bodyEl, footerEl].filter(Boolean));
230
+ };
231
+ const applyEntryOptions = (currentEntry) => {
232
+ if (!currentEntry?.panel) return;
233
+ const opts = getOptions();
234
+ const stateClass = getStateClass(opts);
235
+ const sizeClass = getSizeClass(opts);
236
+ setTrackedClasses(currentEntry.panel, "_dialogClassTokens", [
237
+ "cms-dialog",
238
+ "cms-singularity",
239
+ "cms-clear-set",
240
+ sizeClass ? `cms-dialog-${sizeClass}` : "",
241
+ stateClass,
242
+ stateClass ? `cms-state-${stateClass}` : "",
243
+ uiUnwrap(opts.fullscreen) ? "fullscreen" : "",
244
+ uiUnwrap(opts.scrollable) ? "scrollable" : "",
245
+ uiUnwrap(opts.stickyHeader) ? "sticky-head" : "",
246
+ uiUnwrap(opts.stickyActions) ? "sticky-actions" : "",
247
+ uiUnwrap(opts.borderless) ? "borderless" : "",
248
+ ...splitClasses(opts.class),
249
+ ...splitClasses(opts.panelClass)
250
+ ]);
251
+ setTrackedClasses(currentEntry.overlay, "_dialogOverlayClassTokens", [
252
+ getVerticalAlignClass(opts),
253
+ uiUnwrap(opts.backdropBlur) ? "cms-dialog-overlay-blur" : "",
254
+ ...splitClasses(opts.overlayClass)
255
+ ]);
256
+ setStyleValue(currentEntry.panel, "--cms-dialog-width", uiUnwrap(opts.width), toCssSize);
257
+ setStyleValue(currentEntry.panel, "--cms-dialog-min-width", uiUnwrap(opts.minWidth), toCssSize);
258
258
  setStyleValue(currentEntry.panel, "--cms-dialog-max-width", uiUnwrap(opts.maxWidth), toCssSize);
259
259
  setStyleValue(currentEntry.panel, "--cms-dialog-max-height", uiUnwrap(opts.maxHeight), toCssSize);
260
260
  setStyleValue(currentEntry.panel, "--cms-dialog-body-max-height", uiUnwrap(opts.bodyMaxHeight ?? opts.contentMaxHeight), toCssSize);
@@ -266,136 +266,68 @@
266
266
  if (opts.ariaLabel) currentEntry.panel.setAttribute("aria-label", opts.ariaLabel);
267
267
  else currentEntry.panel.removeAttribute("aria-label");
268
268
  };
269
- const renderOpenContent = () => {
270
- if (!entry?.panel) return;
271
- entry.panel.replaceChildren(buildContent());
272
- };
273
- const update = (nextProps = {}) => {
274
- if (nextProps && typeof nextProps === "object") currentProps = { ...currentProps, ...nextProps };
275
- if (entry) {
276
- applyEntryOptions(entry);
277
- renderOpenContent();
278
- }
279
- return api;
280
- };
281
- const open = (nextProps = null) => {
282
- if (nextProps && typeof nextProps === "object") currentProps = { ...currentProps, ...nextProps };
283
- if (entry) {
284
- applyEntryOptions(entry);
285
- renderOpenContent();
286
- return entry;
287
- }
288
- lastActive = document.activeElement;
289
- const opts = getOptions();
290
- const persistent = opts.persistent === true;
291
- entry = JSswift.overlay.open(() => buildContent(), {
292
- type: "dialog",
293
- backdrop: opts.backdrop !== false,
294
- lockScroll: opts.lockScroll !== false,
295
- trapFocus: opts.trapFocus !== false,
296
- autoFocus: opts.autoFocus !== false,
297
- closeOnOutside: opts.closeOnOutside ?? !persistent,
298
- closeOnBackdrop: opts.closeOnBackdrop ?? !persistent,
299
- closeOnEsc: opts.closeOnEsc ?? !persistent,
269
+ const renderOpenContent = () => {
270
+ if (!entry?.panel) return;
271
+ entry.panel.replaceChildren(buildContent());
272
+ };
273
+ const update = (nextProps = {}) => {
274
+ if (nextProps && typeof nextProps === "object") currentProps = { ...currentProps, ...nextProps };
275
+ if (entry) {
276
+ applyEntryOptions(entry);
277
+ renderOpenContent();
278
+ }
279
+ return api;
280
+ };
281
+ const open = (nextProps = null) => {
282
+ if (nextProps && typeof nextProps === "object") currentProps = { ...currentProps, ...nextProps };
283
+ if (entry) {
284
+ applyEntryOptions(entry);
285
+ renderOpenContent();
286
+ return entry;
287
+ }
288
+ lastActive = document.activeElement;
289
+ const opts = getOptions();
290
+ const persistent = opts.persistent === true;
291
+ entry = JSswift.overlay.open(() => buildContent(), {
292
+ type: "dialog",
293
+ backdrop: opts.backdrop !== false,
294
+ lockScroll: opts.lockScroll !== false,
295
+ trapFocus: opts.trapFocus !== false,
296
+ autoFocus: opts.autoFocus !== false,
297
+ closeOnOutside: opts.closeOnOutside ?? !persistent,
298
+ closeOnBackdrop: opts.closeOnBackdrop ?? !persistent,
299
+ closeOnEsc: opts.closeOnEsc ?? !persistent,
300
300
  className: uiClassStatic(["cms-dialog"]),
301
301
  onClose: () => {
302
302
  entry?._dialogViewportCleanup?.();
303
303
  entry?.panel?.removeEventListener("click", onPanelClick);
304
304
  entry = null;
305
305
  getOptions().onClose?.();
306
- if (lastActive && typeof lastActive.focus === "function") {
307
- setTimeout(() => lastActive.focus(), 0);
308
- }
309
- }
310
- });
311
- const onPanelClick = (e) => {
312
- const target = e.target;
313
- if (target && target.closest && target.closest("[data-dialog-close]")) close();
314
- };
315
- entry.panel.addEventListener("click", onPanelClick);
316
- applyEntryOptions(entry);
317
- overlayEnter(entry);
318
- getOptions().onOpen?.(entry);
319
- return entry;
320
- };
321
-
322
- const close = () => {
323
- if (!entry) return;
324
- const toClose = entry;
325
- overlayLeave(toClose, () => JSswift.overlay.close(toClose.id));
326
- };
327
-
328
- const isOpen = () => !!entry;
329
- const toggle = (nextProps = null) => isOpen() ? (close(), null) : open(nextProps);
330
- const api = { open, close, toggle, update, isOpen, entry: () => entry, props: () => ({ ...currentProps }) };
331
-
332
- return api;
333
- };
334
- if (JSswift.isDev?.()) {
335
- UI.meta = UI.meta || {};
336
- UI.meta.Dialog = {
337
- signature: "UI.Dialog(props) | UI.Dialog(props, ...children) -> { open, close, toggle, update, isOpen }",
338
- props: {
339
- title: "String|Node|Function|Array|({ close })=>Node",
340
- subtitle: "String|Node|Function|Array|({ close })=>Node",
341
- eyebrow: "String|Node|Function|Array|({ close })=>Node",
342
- icon: "String|Node|Function|Array",
343
- content: "Node|Function|Array|({ close })=>Node",
344
- body: "Alias of content",
345
- actions: "Node|Function|Array|({ close })=>Node",
346
- footer: "Alias of actions",
347
- size: "xs|sm|md|lg|xl|full",
348
- state: "primary|secondary|warning|danger|success|info|light|dark",
349
- color: "Alias of state",
350
- width: "string|number",
351
- minWidth: "string|number",
352
- maxWidth: "string|number",
353
- maxHeight: "string|number",
354
- bodyMaxHeight: "string|number",
355
- persistent: "boolean",
356
- closable: "boolean",
357
- closeButton: "boolean",
358
- closeIcon: "string",
359
- align: "top|center|bottom",
360
- actionsAlign: "start|center|end|between|stretch",
361
- stickyHeader: "boolean",
362
- stickyActions: "boolean",
363
- scrollable: "boolean",
364
- stackActions: "boolean",
365
- fullscreen: "boolean",
366
- backdrop: "boolean",
367
- backdropBlur: "boolean",
368
- lockScroll: "boolean",
369
- trapFocus: "boolean",
370
- autoFocus: "boolean",
371
- closeOnOutside: "boolean",
372
- closeOnBackdrop: "boolean",
373
- closeOnEsc: "boolean",
374
- slots: "{ icon?, eyebrow?, title?, subtitle?, header?, content?, body?, footer?, actions?, close?, default? }",
375
- class: "string",
376
- panelClass: "string",
377
- overlayClass: "string",
378
- style: "object"
379
- },
380
- events: {
381
- onOpen: "(entry)",
382
- onClose: "void"
383
- },
384
- slots: {
385
- icon: "Dialog icon ({ close })",
386
- eyebrow: "Eyebrow above the title ({ close })",
387
- title: "Dialog title ({ close })",
388
- subtitle: "Dialog subtitle ({ close })",
389
- header: "Custom header ({ close })",
390
- content: "Dialog body ({ close })",
391
- body: "Alias of content ({ close })",
392
- footer: "Custom footer ({ close })",
393
- actions: "Dialog actions ({ close })",
394
- close: "Close action ({ close })",
395
- default: "Fallback body content ({ close })"
396
- },
397
- description: "Standardized dialog overlay with variants, animations, structured slots, and imperative API.",
398
- returns: "Object { open(overrides?), close(), toggle(overrides?), update(props), isOpen(), entry(), props() }"
399
- };
400
- }
401
-
306
+ if (lastActive && typeof lastActive.focus === "function") {
307
+ setTimeout(() => lastActive.focus(), 0);
308
+ }
309
+ }
310
+ });
311
+ const onPanelClick = (e) => {
312
+ const target = e.target;
313
+ if (target && target.closest && target.closest("[data-dialog-close]")) close();
314
+ };
315
+ entry.panel.addEventListener("click", onPanelClick);
316
+ applyEntryOptions(entry);
317
+ overlayEnter(entry);
318
+ getOptions().onOpen?.(entry);
319
+ return entry;
320
+ };
321
+
322
+ const close = () => {
323
+ if (!entry) return;
324
+ const toClose = entry;
325
+ overlayLeave(toClose, () => JSswift.overlay.close(toClose.id));
326
+ };
327
+
328
+ const isOpen = () => !!entry;
329
+ const toggle = (nextProps = null) => isOpen() ? (close(), null) : open(nextProps);
330
+ const api = { open, close, toggle, update, isOpen, entry: () => entry, props: () => ({ ...currentProps }) };
331
+
332
+ return api;
333
+ };