jsswift 2.0.1 → 2.1.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 (44) hide show
  1. package/AI.md +30 -3
  2. package/README.md +2 -0
  3. package/dist/build/build-configured-runtime.d.mts +29 -0
  4. package/dist/build/build-configured-runtime.mjs +92 -0
  5. package/dist/build/runtime-layout.mjs +13 -0
  6. package/dist/build/select-ui.mjs +167 -0
  7. package/dist/build/vite-jss.d.mts +2 -0
  8. package/dist/build/vite-jss.mjs +73 -0
  9. package/dist/cms.js +3522 -0
  10. package/dist/css/min-ui.css +1 -1
  11. package/dist/css/ui-components.css +13 -0
  12. package/dist/css/ui-light.css +13 -0
  13. package/dist/css/ui-light.min.css +1 -1
  14. package/dist/css/ui.css +13 -0
  15. package/dist/css/ui.min.css +1 -1
  16. package/dist/documentions/AI.md +30 -3
  17. package/dist/documentions/README.md +1 -0
  18. package/dist/documentions/components/table.md +3 -0
  19. package/dist/documentions/configured-runtime.md +82 -0
  20. package/dist/documentions/examples/crud.md +9 -5
  21. package/dist/documentions/knowledge-map.json +20 -1
  22. package/dist/jsswift.js +2 -2
  23. package/dist/jsswift.min.js +1 -1
  24. package/dist/min-jsswift.js +1 -1
  25. package/dist/ui-src/00-bootstrap.js +330 -0
  26. package/dist/ui-src/01-foundation-helpers.js +809 -0
  27. package/dist/ui-src/10-primitives-layout.js +5091 -0
  28. package/dist/ui-src/20-display-content.js +780 -0
  29. package/dist/ui-src/21-list-content.js +246 -0
  30. package/dist/ui-src/22-banner-content.js +376 -0
  31. package/dist/ui-src/23-tooltip.js +276 -0
  32. package/dist/ui-src/31-form-advanced.js +2784 -0
  33. package/dist/ui-src/40-navigation.js +1262 -0
  34. package/dist/ui-src/41-tab-panel.js +896 -0
  35. package/dist/ui-src/50-feedback.js +999 -0
  36. package/dist/ui-src/51-feedback-service.js +166 -0
  37. package/dist/ui-src/60-shell-app.js +1223 -0
  38. package/dist/ui-src/61-form-service.js +368 -0
  39. package/dist/ui-src/70-dialog.js +333 -0
  40. package/dist/ui-src/71-menu-overlays.js +1217 -0
  41. package/dist/ui-src/80-data.js +437 -0
  42. package/dist/ui-src/modules.json +212 -0
  43. package/dist/ui.js +12565 -0
  44. package/package.json +26 -2
@@ -0,0 +1,333 @@
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
+ let currentProps = { ...props };
7
+ let entry = null;
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
+ };
19
+ const setStyleValue = (target, name, value, formatter = (v) => v) => {
20
+ if (!target) return;
21
+ if (value == null || value === "") {
22
+ target.style.removeProperty(name);
23
+ return;
24
+ }
25
+ target.style.setProperty(name, formatter(value));
26
+ };
27
+ const fullscreenViewportSize = () => {
28
+ const vv = window.visualViewport;
29
+ const left = Math.max(0, Math.round(vv?.offsetLeft ?? 0));
30
+ const top = Math.max(0, Math.round(vv?.offsetTop ?? 0));
31
+ const width = Math.max(0, Math.round(vv?.width ?? window.innerWidth ?? 0));
32
+ const height = Math.max(0, Math.round(vv?.height ?? window.innerHeight ?? 0));
33
+ return { left, top, width, height };
34
+ };
35
+ const syncFullscreenViewport = (currentEntry) => {
36
+ if (!currentEntry?.panel) return;
37
+ const { left, top, width, height } = fullscreenViewportSize();
38
+ setStyleValue(currentEntry.panel, "position", "fixed");
39
+ setStyleValue(currentEntry.panel, "inset", 0, (v) => `${v}px`);
40
+ setStyleValue(currentEntry.panel, "top", top, (v) => `${v}px`);
41
+ setStyleValue(currentEntry.panel, "left", left, (v) => `${v}px`);
42
+ setStyleValue(currentEntry.panel, "right", "auto");
43
+ setStyleValue(currentEntry.panel, "bottom", "auto");
44
+ setStyleValue(currentEntry.panel, "width", width, (v) => `${v}px`);
45
+ setStyleValue(currentEntry.panel, "height", height, (v) => `${v}px`);
46
+ setStyleValue(currentEntry.panel, "min-height", height, (v) => `${v}px`);
47
+ setStyleValue(currentEntry.panel, "max-width", width, (v) => `${v}px`);
48
+ setStyleValue(currentEntry.panel, "max-height", height, (v) => `${v}px`);
49
+ setStyleValue(currentEntry.panel, "min-width", 0, (v) => `${v}px`);
50
+ setStyleValue(currentEntry.panel, "margin", 0, (v) => `${v}px`);
51
+ setStyleValue(currentEntry.panel, "box-sizing", "border-box");
52
+ setStyleValue(currentEntry.panel, "border-width", 0, (v) => `${v}px`);
53
+ setStyleValue(currentEntry.panel, "border-radius", 0, (v) => `${v}px`);
54
+ setStyleValue(currentEntry.panel, "transform", "none");
55
+ setStyleValue(currentEntry.panel, "overflow", "hidden");
56
+ setStyleValue(currentEntry.panel, "--jss-dialog-fullscreen-left", left, (v) => `${v}px`);
57
+ setStyleValue(currentEntry.panel, "--jss-dialog-fullscreen-top", top, (v) => `${v}px`);
58
+ setStyleValue(currentEntry.panel, "--jss-dialog-fullscreen-width", width, (v) => `${v}px`);
59
+ setStyleValue(currentEntry.panel, "--jss-dialog-fullscreen-height", height, (v) => `${v}px`);
60
+ };
61
+ const clearFullscreenViewport = (currentEntry) => {
62
+ if (!currentEntry?.panel) return;
63
+ setStyleValue(currentEntry.panel, "position", null);
64
+ setStyleValue(currentEntry.panel, "inset", null);
65
+ setStyleValue(currentEntry.panel, "top", null);
66
+ setStyleValue(currentEntry.panel, "left", null);
67
+ setStyleValue(currentEntry.panel, "right", null);
68
+ setStyleValue(currentEntry.panel, "bottom", null);
69
+ setStyleValue(currentEntry.panel, "width", null);
70
+ setStyleValue(currentEntry.panel, "height", null);
71
+ setStyleValue(currentEntry.panel, "min-height", null);
72
+ setStyleValue(currentEntry.panel, "max-width", null);
73
+ setStyleValue(currentEntry.panel, "max-height", null);
74
+ setStyleValue(currentEntry.panel, "min-width", null);
75
+ setStyleValue(currentEntry.panel, "margin", null);
76
+ setStyleValue(currentEntry.panel, "box-sizing", null);
77
+ setStyleValue(currentEntry.panel, "border-width", null);
78
+ setStyleValue(currentEntry.panel, "border-radius", null);
79
+ setStyleValue(currentEntry.panel, "transform", null);
80
+ setStyleValue(currentEntry.panel, "overflow", null);
81
+ setStyleValue(currentEntry.panel, "--jss-dialog-fullscreen-left", null);
82
+ setStyleValue(currentEntry.panel, "--jss-dialog-fullscreen-top", null);
83
+ setStyleValue(currentEntry.panel, "--jss-dialog-fullscreen-width", null);
84
+ setStyleValue(currentEntry.panel, "--jss-dialog-fullscreen-height", null);
85
+ };
86
+ const ensureFullscreenViewportTracking = (currentEntry, enabled) => {
87
+ if (!currentEntry) return;
88
+ currentEntry._dialogViewportCleanup?.();
89
+ currentEntry._dialogViewportCleanup = null;
90
+ if (!enabled) {
91
+ clearFullscreenViewport(currentEntry);
92
+ return;
93
+ }
94
+ let frameId = null;
95
+ const raf = globalThis.requestAnimationFrame || ((fn) => setTimeout(() => fn(Date.now()), 0));
96
+ const caf = globalThis.cancelAnimationFrame || clearTimeout;
97
+ const sync = () => syncFullscreenViewport(currentEntry);
98
+ const scheduleSync = () => {
99
+ if (frameId != null) caf(frameId);
100
+ frameId = raf(() => {
101
+ frameId = null;
102
+ sync();
103
+ });
104
+ };
105
+ sync();
106
+ scheduleSync();
107
+ window.addEventListener("resize", sync);
108
+ window.addEventListener("orientationchange", scheduleSync);
109
+ window.visualViewport?.addEventListener?.("resize", sync);
110
+ window.visualViewport?.addEventListener?.("scroll", scheduleSync);
111
+ currentEntry._dialogViewportCleanup = () => {
112
+ if (frameId != null) {
113
+ caf(frameId);
114
+ frameId = null;
115
+ }
116
+ window.removeEventListener("resize", sync);
117
+ window.removeEventListener("orientationchange", scheduleSync);
118
+ window.visualViewport?.removeEventListener?.("resize", sync);
119
+ window.visualViewport?.removeEventListener?.("scroll", scheduleSync);
120
+ };
121
+ };
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 "jss-dialog-align-top";
143
+ if (["bottom", "end", "flex-end"].includes(raw)) return "jss-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: "jss-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: "jss-dialog-head jss-dialog-head-custom" }, ...customHeaderNodes);
192
+ } else if (eyebrowNodes.length || titleNodes.length || subtitleNodes.length || iconNodes.length || closeSlotNodes.length) {
193
+ headerEl = _.div(
194
+ { class: "jss-dialog-head" },
195
+ iconNodes.length ? _.div({ class: "jss-dialog-icon" }, ...iconNodes) : null,
196
+ _.div(
197
+ { class: "jss-dialog-head-main" },
198
+ eyebrowNodes.length ? _.div({ class: "jss-dialog-eyebrow" }, ...eyebrowNodes) : null,
199
+ titleNodes.length ? _.div({ class: "jss-dialog-title" }, ...titleNodes) : null,
200
+ subtitleNodes.length ? _.div({ class: "jss-dialog-subtitle" }, ...subtitleNodes) : null
201
+ ),
202
+ closeSlotNodes.length ? _.div({ class: "jss-dialog-close-wrap" }, ...closeSlotNodes) : null
203
+ );
204
+ }
205
+
206
+ const bodyEl = _.div(
207
+ { class: uiClass(["jss-dialog-body", opts.bodyClass]) },
208
+ ...contentNodes
209
+ );
210
+ const footerEl = footerNodes.length
211
+ ? _.div({
212
+ class: uiClass([
213
+ "jss-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
+ "jss-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
+ "jss-dialog",
238
+ "jss-singularity",
239
+ "jss-clear-set",
240
+ sizeClass ? `jss-dialog-${sizeClass}` : "",
241
+ stateClass,
242
+ stateClass ? `jss-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) ? "jss-dialog-overlay-blur" : "",
254
+ ...splitClasses(opts.overlayClass)
255
+ ]);
256
+ setStyleValue(currentEntry.panel, "--jss-dialog-width", uiUnwrap(opts.width), toCssSize);
257
+ setStyleValue(currentEntry.panel, "--jss-dialog-min-width", uiUnwrap(opts.minWidth), toCssSize);
258
+ setStyleValue(currentEntry.panel, "--jss-dialog-max-width", uiUnwrap(opts.maxWidth), toCssSize);
259
+ setStyleValue(currentEntry.panel, "--jss-dialog-max-height", uiUnwrap(opts.maxHeight), toCssSize);
260
+ setStyleValue(currentEntry.panel, "--jss-dialog-body-max-height", uiUnwrap(opts.bodyMaxHeight ?? opts.contentMaxHeight), toCssSize);
261
+ if (opts.style) Object.assign(currentEntry.panel.style, opts.style);
262
+ setPropertyProps(currentEntry.panel, opts);
263
+ ensureFullscreenViewportTracking(currentEntry, uiUnwrap(opts.fullscreen));
264
+ currentEntry.panel.setAttribute("role", opts.role || "dialog");
265
+ currentEntry.panel.setAttribute("aria-modal", opts.modal === false ? "false" : "true");
266
+ if (opts.ariaLabel) currentEntry.panel.setAttribute("aria-label", opts.ariaLabel);
267
+ else currentEntry.panel.removeAttribute("aria-label");
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,
300
+ className: uiClassStatic(["jss-dialog"]),
301
+ onClose: () => {
302
+ entry?._dialogViewportCleanup?.();
303
+ entry?.panel?.removeEventListener("click", onPanelClick);
304
+ entry = null;
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
+ };