@jsswift/ui 1.0.41
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/dist/css/animation.css +121 -0
- package/dist/css/base.css +621 -0
- package/dist/css/docs.css +216 -0
- package/dist/css/min-ui.css +1 -0
- package/dist/css/responsive.css +3259 -0
- package/dist/css/ui-components.css +8757 -0
- package/dist/css/ui.css +12765 -0
- package/dist/css/ui.min.css +1 -0
- package/dist/fonts/Nunito-Black.ttf +0 -0
- package/dist/fonts/Nunito-BlackItalic.ttf +0 -0
- package/dist/fonts/Nunito-Bold.ttf +0 -0
- package/dist/fonts/Nunito-BoldItalic.ttf +0 -0
- package/dist/fonts/Nunito-ExtraBold.ttf +0 -0
- package/dist/fonts/Nunito-ExtraBoldItalic.ttf +0 -0
- package/dist/fonts/Nunito-ExtraLight.ttf +0 -0
- package/dist/fonts/Nunito-ExtraLightItalic.ttf +0 -0
- package/dist/fonts/Nunito-Italic.ttf +0 -0
- package/dist/fonts/Nunito-Light.ttf +0 -0
- package/dist/fonts/Nunito-LightItalic.ttf +0 -0
- package/dist/fonts/Nunito-Medium.ttf +0 -0
- package/dist/fonts/Nunito-MediumItalic.ttf +0 -0
- package/dist/fonts/Nunito-Regular.ttf +0 -0
- package/dist/fonts/Nunito-SemiBold.ttf +0 -0
- package/dist/fonts/Nunito-SemiBoldItalic.ttf +0 -0
- package/dist/img/logo.svg +131 -0
- package/dist/img/svg/tabler-icons-sprite.svg +39534 -0
- package/dist/min-ui.js +34 -0
- package/dist/ui.js +21027 -0
- package/dist/ui.min.js +34 -0
- package/package.json +40 -0
- package/src/00-bootstrap.js +330 -0
- package/src/01-foundation-helpers.js +1208 -0
- package/src/10-primitives-layout.js +5972 -0
- package/src/20-display-content.js +1014 -0
- package/src/21-list-content.js +341 -0
- package/src/22-banner-content.js +489 -0
- package/src/23-tooltip.js +326 -0
- package/src/31-form-advanced.js +3149 -0
- package/src/40-navigation.js +1454 -0
- package/src/41-tab-panel.js +1090 -0
- package/src/50-feedback.js +1209 -0
- package/src/51-feedback-service.js +166 -0
- package/src/60-shell-app.js +1483 -0
- package/src/61-form-service.js +450 -0
- package/src/70-dialog.js +401 -0
- package/src/71-menu-overlays.js +1442 -0
- package/src/80-data.js +503 -0
- package/src/README.md +159 -0
- package/src/css/README.md +17 -0
- package/src/css/animation.css +121 -0
- package/src/css/base.css +621 -0
- package/src/css/docs.css +216 -0
- package/src/css/responsive.css +3259 -0
- package/src/css/ui-components.css +8757 -0
- package/src/modules.json +176 -0
|
@@ -0,0 +1,1483 @@
|
|
|
1
|
+
UI.Header = (...args) => {
|
|
2
|
+
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
3
|
+
const slots = rawProps.slots || {};
|
|
4
|
+
const props = { ...rawProps };
|
|
5
|
+
applyCommonProps(props);
|
|
6
|
+
|
|
7
|
+
const hasOwn = (key) => Object.prototype.hasOwnProperty.call(rawProps, key);
|
|
8
|
+
const currentStateKey = rawProps.drawerStateKey ?? drawerStateKey;
|
|
9
|
+
if (currentStateKey) {
|
|
10
|
+
drawerStateKey = currentStateKey;
|
|
11
|
+
drawerOpen = readDrawerOpen(currentStateKey);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const isDrawerOpen = () => readDrawerOpen(currentStateKey);
|
|
15
|
+
const openDrawer = () => setDrawerOpen(true, currentStateKey);
|
|
16
|
+
const closeDrawer = () => setDrawerOpen(false, currentStateKey);
|
|
17
|
+
const toggleDrawer = () => setDrawerOpen(!isDrawerOpen(), currentStateKey);
|
|
18
|
+
const ctx = {
|
|
19
|
+
props: rawProps,
|
|
20
|
+
stateKey: currentStateKey,
|
|
21
|
+
isDrawerOpen,
|
|
22
|
+
openDrawer,
|
|
23
|
+
closeDrawer,
|
|
24
|
+
toggleDrawer,
|
|
25
|
+
toggleAside: toggleDrawer
|
|
26
|
+
};
|
|
27
|
+
const appendResolvedValue = (host, value) => {
|
|
28
|
+
if (value == null || value === false) return;
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
value.forEach((item) => appendResolvedValue(host, item));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (value?.nodeType) {
|
|
34
|
+
host.appendChild(value);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
host.appendChild(document.createTextNode(String(value)));
|
|
38
|
+
};
|
|
39
|
+
const renderPropNodes = (name, fallback, map = (value) => value) => {
|
|
40
|
+
const slot = JSswift.ui.getSlot(slots, name);
|
|
41
|
+
if (slot !== null && slot !== undefined) {
|
|
42
|
+
return renderSlotToArray(slots, name, ctx, null);
|
|
43
|
+
}
|
|
44
|
+
if (typeof fallback === "function") {
|
|
45
|
+
const inlineNames = new Set(["eyebrow", "title", "subtitle"]);
|
|
46
|
+
const host = _[inlineNames.has(name) ? "span" : "div"]({ class: `cms-header-slot-${name}` });
|
|
47
|
+
JSswift.reactive.effect(() => {
|
|
48
|
+
const nextValue = map(fallback(ctx));
|
|
49
|
+
const normalized = flattenSlotValue(JSswift.ui.slot(nextValue));
|
|
50
|
+
host.replaceChildren();
|
|
51
|
+
if (Array.isArray(normalized)) normalized.forEach((item) => appendResolvedValue(host, item));
|
|
52
|
+
else appendResolvedValue(host, normalized);
|
|
53
|
+
}, `UI.Header:${name}`);
|
|
54
|
+
return [host];
|
|
55
|
+
}
|
|
56
|
+
return renderSlotToArray(slots, name, ctx, map(fallback));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const renderIconValue = (value, as = "icon", sizeFallback = rawProps.iconSize || rawProps.size || "md") => {
|
|
60
|
+
if (value == null || value === false) return null;
|
|
61
|
+
if (typeof value === "string") return UI.Icon({ name: value, size: sizeFallback });
|
|
62
|
+
return JSswift.ui.slot(value, { as });
|
|
63
|
+
};
|
|
64
|
+
const renderDrawerToggleValue = (open) => {
|
|
65
|
+
const value = open ? (rawProps.drawerOpenIcon ?? "✕") : (rawProps.drawerCloseIcon ?? "☰");
|
|
66
|
+
if (value == null || value === false) return null;
|
|
67
|
+
if (typeof value === "string") return value;
|
|
68
|
+
return JSswift.ui.slot(value, { as: open ? "drawerOpenIcon" : "drawerCloseIcon" });
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const toggleIconHost = _.span({ class: "cms-header-toggle-icon" });
|
|
72
|
+
const paintToggleIcon = (open) => {
|
|
73
|
+
const nodes = renderSlotToArray(null, "default", ctx, renderDrawerToggleValue(open));
|
|
74
|
+
toggleIconHost.replaceChildren(...(nodes.length ? nodes : [""]));
|
|
75
|
+
};
|
|
76
|
+
paintToggleIcon(isDrawerOpen());
|
|
77
|
+
|
|
78
|
+
const autoLeft = UI.Btn({
|
|
79
|
+
class: "cms-header-toggle",
|
|
80
|
+
outline: true,
|
|
81
|
+
size: rawProps.toggleSize ?? rawProps.size ?? "sm",
|
|
82
|
+
onClick: toggleDrawer,
|
|
83
|
+
"aria-label": rawProps.toggleLabel || "Toggle navigation"
|
|
84
|
+
}, toggleIconHost);
|
|
85
|
+
if (rawProps.left == null && rawProps.left !== false && !JSswift.ui.getSlot(slots, "left")) {
|
|
86
|
+
drawerToggleIcons.add({ update: paintToggleIcon });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const leftFallback = rawProps.left === false
|
|
90
|
+
? null
|
|
91
|
+
: (rawProps.left != null ? rawProps.left : autoLeft);
|
|
92
|
+
const titleFallback = hasOwn("title") ? rawProps.title : (rawProps.label ?? "App");
|
|
93
|
+
const subtitleFallback = rawProps.subtitle ?? rawProps.description ?? "";
|
|
94
|
+
const rightFallback = hasOwn("right") ? rawProps.right : rawProps.end;
|
|
95
|
+
const contentFallback = hasOwn("content")
|
|
96
|
+
? rawProps.content
|
|
97
|
+
: (hasOwn("body") ? rawProps.body : (children?.length ? children : null));
|
|
98
|
+
|
|
99
|
+
const startNodes = [
|
|
100
|
+
...renderPropNodes("left", leftFallback),
|
|
101
|
+
...renderPropNodes("start", null)
|
|
102
|
+
];
|
|
103
|
+
const iconNodes = renderPropNodes("icon", rawProps.icon, renderIconValue);
|
|
104
|
+
const eyebrowNodes = renderPropNodes("eyebrow", rawProps.eyebrow ?? rawProps.kicker);
|
|
105
|
+
const titleNodes = renderPropNodes("title", titleFallback);
|
|
106
|
+
const subtitleNodes = renderPropNodes("subtitle", subtitleFallback);
|
|
107
|
+
const metaNodes = renderPropNodes("meta", rawProps.meta);
|
|
108
|
+
const contentNodes = (() => {
|
|
109
|
+
const explicit = renderPropNodes("content", null);
|
|
110
|
+
return explicit.length ? explicit : renderPropNodes("default", contentFallback);
|
|
111
|
+
})();
|
|
112
|
+
const customCenterNodes = [
|
|
113
|
+
...renderPropNodes("center", null),
|
|
114
|
+
...renderPropNodes("body", null)
|
|
115
|
+
];
|
|
116
|
+
const rightNodes = [
|
|
117
|
+
...renderPropNodes("right", rightFallback),
|
|
118
|
+
...renderPropNodes("end", null)
|
|
119
|
+
];
|
|
120
|
+
const actionNodes = renderPropNodes("actions", rawProps.actions);
|
|
121
|
+
|
|
122
|
+
const structuredCenter = _.div(
|
|
123
|
+
{ class: uiClass(["cms-header-body", rawProps.bodyClass, rawProps.centerClass]) },
|
|
124
|
+
_.div(
|
|
125
|
+
{ class: "cms-header-heading" },
|
|
126
|
+
iconNodes.length ? _.div({ class: "cms-header-icon" }, ...iconNodes) : null,
|
|
127
|
+
_.div(
|
|
128
|
+
{ class: "cms-header-copy" },
|
|
129
|
+
eyebrowNodes.length ? _.div({ class: uiClass(["cms-header-eyebrow", rawProps.eyebrowClass]) }, ...eyebrowNodes) : null,
|
|
130
|
+
titleNodes.length ? _.div({ class: uiClass(["cms-header-title", rawProps.titleClass]) }, ...titleNodes) : null,
|
|
131
|
+
subtitleNodes.length ? _.div({ class: uiClass(["cms-header-subtitle", rawProps.subtitleClass]) }, ...subtitleNodes) : null,
|
|
132
|
+
contentNodes.length ? _.div({ class: uiClass(["cms-header-content", rawProps.contentClass]) }, ...contentNodes) : null
|
|
133
|
+
),
|
|
134
|
+
metaNodes.length ? _.div({ class: uiClass(["cms-header-meta", rawProps.metaClass]) }, ...metaNodes) : null
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const endContent = [
|
|
139
|
+
...rightNodes,
|
|
140
|
+
...(actionNodes.length ? [_.div({ class: uiClass(["cms-header-actions", rawProps.actionsClass]) }, ...actionNodes)] : [])
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const p = JSswift.omit(props, [
|
|
144
|
+
"actions", "actionsClass", "body", "bodyClass", "centerClass", "content", "contentClass",
|
|
145
|
+
"description", "divider", "drawerCloseIcon", "drawerOpenIcon", "drawerStateKey", "elevated",
|
|
146
|
+
"end", "eyebrow", "eyebrowClass", "icon", "iconSize", "kicker", "label", "left", "meta",
|
|
147
|
+
"metaClass", "right", "slots", "stack", "sticky", "subtitle", "subtitleClass", "title",
|
|
148
|
+
"titleClass", "toggleLabel", "toggleSize", "gap", "minHeight", "startClass", "endClass"
|
|
149
|
+
]);
|
|
150
|
+
p.class = uiClass([
|
|
151
|
+
"cms-panel",
|
|
152
|
+
"cms-header",
|
|
153
|
+
"cms-singularity",
|
|
154
|
+
uiWhen(rawProps.sticky !== false, "sticky"),
|
|
155
|
+
uiWhen(rawProps.stack, "stack"),
|
|
156
|
+
uiWhen(rawProps.elevated, "elevated"),
|
|
157
|
+
uiWhen(rawProps.divider !== false, "divider"),
|
|
158
|
+
props.class
|
|
159
|
+
]);
|
|
160
|
+
p.style = { ...(props.style || {}) };
|
|
161
|
+
if (rawProps.gap != null) p.style["--cms-header-gap"] = toCssSize(uiUnwrap(rawProps.gap));
|
|
162
|
+
if (rawProps.minHeight != null) p.style.minHeight = toCssSize(uiUnwrap(rawProps.minHeight));
|
|
163
|
+
|
|
164
|
+
const el = _.div(
|
|
165
|
+
p,
|
|
166
|
+
...(startNodes.length ? [_.div({ class: uiClass(["cms-header-start", rawProps.startClass]) }, ...startNodes)] : []),
|
|
167
|
+
_.div(
|
|
168
|
+
{ class: "cms-header-main" },
|
|
169
|
+
...(customCenterNodes.length
|
|
170
|
+
? [_.div({ class: uiClass(["cms-header-body", rawProps.bodyClass, rawProps.centerClass]) }, ...customCenterNodes)]
|
|
171
|
+
: [structuredCenter]),
|
|
172
|
+
...(endContent.length ? [_.div({ class: uiClass(["cms-header-end", rawProps.endClass]) }, ...endContent)] : [])
|
|
173
|
+
)
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
setPropertyProps(el, rawProps);
|
|
177
|
+
return el;
|
|
178
|
+
};
|
|
179
|
+
if (JSswift.isDev?.()) {
|
|
180
|
+
UI.meta = UI.meta || {};
|
|
181
|
+
UI.meta.Header = {
|
|
182
|
+
signature: "UI.Header(...children) | UI.Header(props, ...children)",
|
|
183
|
+
props: {
|
|
184
|
+
title: "String|Node|Function|Array",
|
|
185
|
+
subtitle: "String|Node|Function|Array",
|
|
186
|
+
eyebrow: "String|Node|Function|Array",
|
|
187
|
+
content: "Node|Function|Array",
|
|
188
|
+
meta: "Node|Function|Array",
|
|
189
|
+
icon: "String|Node|Function|Array",
|
|
190
|
+
left: "Node|Function|Array|false",
|
|
191
|
+
right: "Node|Function|Array",
|
|
192
|
+
actions: "Node|Function|Array",
|
|
193
|
+
drawerOpenIcon: "string|Node",
|
|
194
|
+
drawerCloseIcon: "string|Node",
|
|
195
|
+
drawerStateKey: "string",
|
|
196
|
+
sticky: "boolean",
|
|
197
|
+
stack: "boolean",
|
|
198
|
+
dense: "boolean",
|
|
199
|
+
elevated: "boolean",
|
|
200
|
+
divider: "boolean",
|
|
201
|
+
gap: "string|number",
|
|
202
|
+
minHeight: "string|number",
|
|
203
|
+
slots: "{ left?, start?, right?, end?, center?, body?, icon?, eyebrow?, title?, subtitle?, meta?, content?, actions? }",
|
|
204
|
+
class: "string",
|
|
205
|
+
style: "object"
|
|
206
|
+
},
|
|
207
|
+
slots: {
|
|
208
|
+
left: "Left area, falls back to the drawer toggle",
|
|
209
|
+
start: "Alias/addon for the left area",
|
|
210
|
+
right: "Main right area",
|
|
211
|
+
end: "Alias/addon for the right area",
|
|
212
|
+
center: "Full override for the central body",
|
|
213
|
+
body: "Alias of center",
|
|
214
|
+
icon: "Leading icon",
|
|
215
|
+
eyebrow: "Eyebrow / kicker",
|
|
216
|
+
title: "Title",
|
|
217
|
+
subtitle: "Subtitle",
|
|
218
|
+
meta: "Meta info next to the central content",
|
|
219
|
+
content: "Extra content below the subtitle",
|
|
220
|
+
actions: "Actions grouped in the right area"
|
|
221
|
+
},
|
|
222
|
+
returns: "HTMLDivElement",
|
|
223
|
+
description: "Structured header with start/body/end regions, integrated drawer toggle, metadata, and composable slots."
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
UI.Drawer = (...args) => {
|
|
228
|
+
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
229
|
+
const slots = rawProps.slots || {};
|
|
230
|
+
const props = { ...rawProps };
|
|
231
|
+
const hasOwn = (key) => Object.prototype.hasOwnProperty.call(rawProps, key);
|
|
232
|
+
const currentStateKey = rawProps.stateKey ?? drawerStateKey;
|
|
233
|
+
if (currentStateKey) {
|
|
234
|
+
drawerStateKey = currentStateKey;
|
|
235
|
+
drawerOpen = readDrawerOpen(currentStateKey);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const isDrawerOpen = () => readDrawerOpen(currentStateKey);
|
|
239
|
+
const openDrawer = () => setDrawerOpen(true, currentStateKey);
|
|
240
|
+
const closeDrawer = () => setDrawerOpen(false, currentStateKey);
|
|
241
|
+
const toggleDrawer = () => setDrawerOpen(!isDrawerOpen(), currentStateKey);
|
|
242
|
+
const ctx = {
|
|
243
|
+
props: rawProps,
|
|
244
|
+
stateKey: currentStateKey,
|
|
245
|
+
isDrawerOpen,
|
|
246
|
+
openDrawer,
|
|
247
|
+
closeDrawer,
|
|
248
|
+
toggleDrawer,
|
|
249
|
+
toggleAside: toggleDrawer
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const store = JSswift?.store;
|
|
253
|
+
const canStore = !!(store?.get && store?.set);
|
|
254
|
+
const groupStateKey = `${currentStateKey}:groups`;
|
|
255
|
+
const activeStateKey = `${currentStateKey}:active`;
|
|
256
|
+
let groupState = canStore ? (store.get(groupStateKey, {}) || {}) : {};
|
|
257
|
+
let activeItemKey = canStore ? (store.get(activeStateKey, null) ?? null) : null;
|
|
258
|
+
const activeLeafRefs = new Map();
|
|
259
|
+
const activeGroupRefs = new Map();
|
|
260
|
+
|
|
261
|
+
const normalizeList = (value) => {
|
|
262
|
+
const raw = uiUnwrap(value);
|
|
263
|
+
if (raw == null || raw === false) return [];
|
|
264
|
+
if (Array.isArray(raw)) return raw.flat(Infinity);
|
|
265
|
+
return [raw];
|
|
266
|
+
};
|
|
267
|
+
const renderArea = (names, fallback, localCtx = ctx) => {
|
|
268
|
+
const list = Array.isArray(names) ? names : [names];
|
|
269
|
+
for (const name of list) {
|
|
270
|
+
if (JSswift.ui.getSlot(slots, name) != null) {
|
|
271
|
+
return renderSlotToArray(slots, name, localCtx, fallback);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return fallback == null ? [] : renderSlotToArray(null, "default", localCtx, uiUnwrap(fallback));
|
|
275
|
+
};
|
|
276
|
+
const hasArea = (names) => {
|
|
277
|
+
const list = Array.isArray(names) ? names : [names];
|
|
278
|
+
return list.some((name) => JSswift.ui.getSlot(slots, name) != null);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const isExternalLink = (it) => {
|
|
282
|
+
if (typeof uiUnwrap(it.external) === "boolean") return !!uiUnwrap(it.external);
|
|
283
|
+
const href = uiUnwrap(it.href || it.to || it.link || "");
|
|
284
|
+
return /^(https?:)?\/\//.test(href);
|
|
285
|
+
};
|
|
286
|
+
const closeOnSelect = rawProps.closeOnSelect ?? true;
|
|
287
|
+
const shouldClose = (it) => !!closeOnSelect && uiUnwrap(it.keepOpen) !== true;
|
|
288
|
+
const itemIconSize = rawProps.itemIconSize ?? rawProps.size ?? "md";
|
|
289
|
+
const groupOpenIcon = rawProps.groupOpenIcon ?? "arrow_drop_up";
|
|
290
|
+
const groupCloseIcon = rawProps.groupCloseIcon ?? "arrow_drop_down";
|
|
291
|
+
|
|
292
|
+
const isStoredActive = (key) => !!key && activeItemKey === key;
|
|
293
|
+
const isBranchActive = (key) => !!key && !!activeItemKey && (activeItemKey === key || activeItemKey.startsWith(`${key}::`));
|
|
294
|
+
const syncActiveClasses = () => {
|
|
295
|
+
activeLeafRefs.forEach(({ el, baseActive }, key) => {
|
|
296
|
+
const active = !!baseActive || isStoredActive(key);
|
|
297
|
+
el.classList.toggle("active", active);
|
|
298
|
+
if (active) el.setAttribute("aria-current", "page");
|
|
299
|
+
else el.removeAttribute("aria-current");
|
|
300
|
+
});
|
|
301
|
+
activeGroupRefs.forEach(({ wrap, toggle, baseActive }, key) => {
|
|
302
|
+
const active = !!baseActive || isBranchActive(key);
|
|
303
|
+
wrap.classList.toggle("active", active);
|
|
304
|
+
toggle.classList.toggle("active", active);
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
const setActiveItem = (key) => {
|
|
308
|
+
activeItemKey = key || null;
|
|
309
|
+
if (canStore) store.set(activeStateKey, activeItemKey);
|
|
310
|
+
syncActiveClasses();
|
|
311
|
+
};
|
|
312
|
+
const inferBaseActive = (it, href) => {
|
|
313
|
+
if (uiUnwrap(it.active) != null) return !!uiUnwrap(it.active);
|
|
314
|
+
if (uiUnwrap(it.current) != null) return !!uiUnwrap(it.current);
|
|
315
|
+
if (!href || isExternalLink(it)) return false;
|
|
316
|
+
try {
|
|
317
|
+
if (href.startsWith("#")) return window.location.hash === href;
|
|
318
|
+
const fullPath = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
319
|
+
return href === fullPath || href === window.location.pathname;
|
|
320
|
+
} catch {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
const inferActive = (it, href, itemStateKey) => inferBaseActive(it, href) || isStoredActive(itemStateKey);
|
|
325
|
+
const hasActiveChildren = (list = [], level = 0, path = []) => {
|
|
326
|
+
return normalizeList(list).some((entry, idx) => {
|
|
327
|
+
const it = uiUnwrap(entry);
|
|
328
|
+
if (!it || typeof it !== "object" || it.nodeType) return false;
|
|
329
|
+
const href = uiUnwrap(it.href || it.to || it.link || "");
|
|
330
|
+
const label = uiUnwrap(it.label ?? it.title ?? it.text ?? href ?? null) ?? `item-${level}-${idx}`;
|
|
331
|
+
const keyPart = itemKeyPart(it, label, level, idx);
|
|
332
|
+
const itemStateKey = path.concat(keyPart).join("::");
|
|
333
|
+
if (inferActive(it, href, itemStateKey)) return true;
|
|
334
|
+
const nested = it.items || it.children || it.sub;
|
|
335
|
+
return hasActiveChildren(nested, level + 1, path.concat(keyPart));
|
|
336
|
+
});
|
|
337
|
+
};
|
|
338
|
+
const iconNodes = (icon, localCtx = {}) => {
|
|
339
|
+
const raw = uiUnwrap(icon);
|
|
340
|
+
if (raw == null || raw === false) return [];
|
|
341
|
+
const slotValue = typeof raw === "string"
|
|
342
|
+
? UI.Icon({ name: raw, size: localCtx.iconSize ?? itemIconSize })
|
|
343
|
+
: JSswift.ui.slot(raw, { ...localCtx, as: "icon" });
|
|
344
|
+
return renderSlotToArray(null, "default", localCtx, slotValue);
|
|
345
|
+
};
|
|
346
|
+
const wrapIconNodes = (icon, side, localCtx = {}) => {
|
|
347
|
+
const nodes = iconNodes(icon, localCtx);
|
|
348
|
+
return nodes.length ? [_.span({ class: uiClass(["cms-drawer-item-icon", side]) }, ...nodes)] : [];
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const getItemIcons = (it) => {
|
|
352
|
+
const side = uiUnwrap(it.iconPosition) || "left";
|
|
353
|
+
const leftIcon = Object.prototype.hasOwnProperty.call(it, "iconLeft") ? it.iconLeft : (side !== "right" ? it.icon : null);
|
|
354
|
+
const rightIcon = Object.prototype.hasOwnProperty.call(it, "iconRight") ? it.iconRight : (side === "right" ? it.icon : null);
|
|
355
|
+
return {
|
|
356
|
+
left: leftIcon,
|
|
357
|
+
right: rightIcon
|
|
358
|
+
};
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const itemKeyPart = (it, label, level, idx) => uiUnwrap(it.key || it.id || it.to || it.href || label || `item-${level}-${idx}`);
|
|
362
|
+
|
|
363
|
+
const readGroupOpen = (key, fallback) => {
|
|
364
|
+
if (!canStore) return fallback;
|
|
365
|
+
if (Object.prototype.hasOwnProperty.call(groupState, key)) return !!groupState[key];
|
|
366
|
+
return fallback;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const writeGroupOpen = (key, open) => {
|
|
370
|
+
if (!canStore) return;
|
|
371
|
+
groupState = { ...groupState, [key]: !!open };
|
|
372
|
+
store.set(groupStateKey, groupState);
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const buildStructuredHeader = () => {
|
|
376
|
+
const headerCtx = { ...ctx, area: "header" };
|
|
377
|
+
const icon = renderArea(["headerIcon", "icon"], rawProps.icon, headerCtx);
|
|
378
|
+
const eyebrow = renderArea(["eyebrow"], rawProps.eyebrow, headerCtx);
|
|
379
|
+
const title = renderArea(["title"], rawProps.title, headerCtx);
|
|
380
|
+
const subtitle = renderArea(["subtitle"], rawProps.subtitle, headerCtx);
|
|
381
|
+
const meta = renderArea(["meta"], rawProps.meta, headerCtx);
|
|
382
|
+
const content = renderArea(["content"], rawProps.content, headerCtx);
|
|
383
|
+
const actions = renderArea(["actions"], rawProps.actions, headerCtx);
|
|
384
|
+
const extra = hasOwn("header") ? renderSlotToArray(null, "default", headerCtx, uiUnwrap(rawProps.header)) : [];
|
|
385
|
+
const hasStructured = icon.length || eyebrow.length || title.length || subtitle.length || meta.length || content.length || actions.length;
|
|
386
|
+
if (!hasStructured && !extra.length) return [];
|
|
387
|
+
if (!hasStructured) return extra;
|
|
388
|
+
return [
|
|
389
|
+
_.div(
|
|
390
|
+
{ class: uiClass(["cms-drawer-header", rawProps.headerClass]) },
|
|
391
|
+
...(icon.length ? [_.div({ class: "cms-drawer-header-icon" }, ...icon)] : []),
|
|
392
|
+
_.div(
|
|
393
|
+
{ class: "cms-drawer-header-body" },
|
|
394
|
+
...(eyebrow.length ? [_.div({ class: "cms-drawer-header-eyebrow" }, ...eyebrow)] : []),
|
|
395
|
+
...(title.length ? [_.div({ class: "cms-drawer-header-title" }, ...title)] : []),
|
|
396
|
+
...(subtitle.length ? [_.div({ class: "cms-drawer-header-subtitle" }, ...subtitle)] : []),
|
|
397
|
+
...(meta.length ? [_.div({ class: "cms-drawer-header-meta" }, ...meta)] : []),
|
|
398
|
+
...(content.length ? [_.div({ class: "cms-drawer-header-content" }, ...content)] : []),
|
|
399
|
+
...(extra.length ? [_.div({ class: "cms-drawer-header-extra" }, ...extra)] : [])
|
|
400
|
+
),
|
|
401
|
+
...(actions.length ? [_.div({ class: "cms-drawer-header-actions" }, ...actions)] : [])
|
|
402
|
+
)
|
|
403
|
+
];
|
|
404
|
+
};
|
|
405
|
+
const invokeItemHandler = (it, itemCtx, e) => {
|
|
406
|
+
const itemResult = it.onClick?.(e, itemCtx);
|
|
407
|
+
if (itemResult === false || e?.defaultPrevented) return false;
|
|
408
|
+
const rootResult = rawProps.onSelect?.(it, itemCtx, e);
|
|
409
|
+
if (rootResult === false || e?.defaultPrevented) return false;
|
|
410
|
+
return true;
|
|
411
|
+
};
|
|
412
|
+
const renderEntryNodes = (it, itemCtx, extras = {}) => {
|
|
413
|
+
const slotPrefix = itemCtx.hasChildren ? "group" : "item";
|
|
414
|
+
const labelNodes = renderSlotToArray(slots, `${slotPrefix}Label`, itemCtx, itemCtx.label);
|
|
415
|
+
const noteNodes = renderSlotToArray(slots, `${slotPrefix}Note`, itemCtx, itemCtx.note);
|
|
416
|
+
const badgeNodes = renderSlotToArray(slots, `${slotPrefix}Badge`, itemCtx, itemCtx.badge);
|
|
417
|
+
const asideNodes = renderSlotToArray(slots, `${slotPrefix}Aside`, itemCtx, itemCtx.aside);
|
|
418
|
+
const contentNodes = renderSlotToArray(slots, `${slotPrefix}Content`, itemCtx, itemCtx.content);
|
|
419
|
+
const mainChildren = [];
|
|
420
|
+
|
|
421
|
+
if (labelNodes.length || noteNodes.length || badgeNodes.length || asideNodes.length) {
|
|
422
|
+
mainChildren.push(
|
|
423
|
+
_.div(
|
|
424
|
+
{ class: "cms-drawer-item-top" },
|
|
425
|
+
_.div(
|
|
426
|
+
{ class: "cms-drawer-item-copy" },
|
|
427
|
+
...(labelNodes.length ? [_.div({ class: "cms-drawer-item-label" }, ...labelNodes)] : []),
|
|
428
|
+
...(noteNodes.length ? [_.div({ class: "cms-drawer-item-note" }, ...noteNodes)] : [])
|
|
429
|
+
),
|
|
430
|
+
...(badgeNodes.length || asideNodes.length
|
|
431
|
+
? [_.div(
|
|
432
|
+
{ class: "cms-drawer-item-meta" },
|
|
433
|
+
...(badgeNodes.length ? [_.div({ class: "cms-drawer-item-badge" }, ...badgeNodes)] : []),
|
|
434
|
+
...(asideNodes.length ? [_.div({ class: "cms-drawer-item-aside" }, ...asideNodes)] : [])
|
|
435
|
+
)]
|
|
436
|
+
: [])
|
|
437
|
+
)
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
if (contentNodes.length) mainChildren.push(_.div({ class: "cms-drawer-item-content" }, ...contentNodes));
|
|
441
|
+
|
|
442
|
+
return [
|
|
443
|
+
...(extras.start || []),
|
|
444
|
+
...wrapIconNodes(itemCtx.iconLeft, "left", itemCtx),
|
|
445
|
+
...(mainChildren.length ? [_.div({ class: "cms-drawer-item-main" }, ...mainChildren)] : []),
|
|
446
|
+
...wrapIconNodes(itemCtx.iconRight, "right", itemCtx),
|
|
447
|
+
...(extras.end || [])
|
|
448
|
+
];
|
|
449
|
+
};
|
|
450
|
+
const renderItems = (list = [], level = 0, path = []) => {
|
|
451
|
+
return normalizeList(list).map((entry, idx) => {
|
|
452
|
+
const it = uiUnwrap(entry);
|
|
453
|
+
if (it == null || it === false) return null;
|
|
454
|
+
|
|
455
|
+
if (it.nodeType || typeof it !== "object" || Array.isArray(it)) {
|
|
456
|
+
return _.div({
|
|
457
|
+
class: "cms-drawer-custom",
|
|
458
|
+
"data-level": String(level)
|
|
459
|
+
}, ...renderSlotToArray(null, "default", { ...ctx, item: it, level, index: idx, path }, it));
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (uiUnwrap(it.hidden) === true || uiUnwrap(it.visible) === false) return null;
|
|
463
|
+
|
|
464
|
+
const childItems = normalizeList(it.items || it.children || it.sub);
|
|
465
|
+
const href = uiUnwrap(it.href || it.to || it.link || null);
|
|
466
|
+
const rawLabel = uiUnwrap(it.label ?? it.title ?? it.text ?? href ?? null);
|
|
467
|
+
const label = rawLabel ?? `item-${level}-${idx}`;
|
|
468
|
+
const note = uiUnwrap(it.note ?? it.subtitle ?? it.description ?? null);
|
|
469
|
+
const badge = uiUnwrap(it.badge ?? null);
|
|
470
|
+
const aside = uiUnwrap(it.aside ?? it.meta ?? null);
|
|
471
|
+
const content = uiUnwrap(it.content ?? null);
|
|
472
|
+
const keyPart = itemKeyPart(it, label, level, idx);
|
|
473
|
+
const itemStateKey = path.concat(keyPart).join("::");
|
|
474
|
+
const { left: itemIconLeft, right: itemIconRight } = getItemIcons(it);
|
|
475
|
+
const tone = normalizeState(uiUnwrap(it.state) || uiUnwrap(it.color) || "");
|
|
476
|
+
const baseIsActive = inferBaseActive(it, href);
|
|
477
|
+
const isActive = baseIsActive || isStoredActive(itemStateKey);
|
|
478
|
+
const disabled = !!uiUnwrap(it.disabled);
|
|
479
|
+
const itemCtx = {
|
|
480
|
+
...ctx,
|
|
481
|
+
item: it,
|
|
482
|
+
index: idx,
|
|
483
|
+
level,
|
|
484
|
+
path,
|
|
485
|
+
label,
|
|
486
|
+
note,
|
|
487
|
+
badge,
|
|
488
|
+
aside,
|
|
489
|
+
content,
|
|
490
|
+
itemStateKey,
|
|
491
|
+
iconLeft: itemIconLeft,
|
|
492
|
+
iconRight: itemIconRight,
|
|
493
|
+
iconSize: uiUnwrap(it.iconSize) || itemIconSize,
|
|
494
|
+
active: isActive,
|
|
495
|
+
disabled,
|
|
496
|
+
hasChildren: false
|
|
497
|
+
};
|
|
498
|
+
const dividerLike = uiUnwrap(it.divider) === true || it.type === "divider" || it.type === "separator";
|
|
499
|
+
const sectionLike = uiUnwrap(it.section) === true || it.type === "section" || it.type === "heading" || it.type === "label";
|
|
500
|
+
const contentOnly = it.type === "content" || it.type === "custom" || (!href && !childItems.length && !uiUnwrap(it.button) && it.type !== "button" && rawLabel == null && content != null);
|
|
501
|
+
|
|
502
|
+
if (dividerLike) {
|
|
503
|
+
return _.div({
|
|
504
|
+
class: uiClass(["cms-drawer-separator", it.class]),
|
|
505
|
+
"data-level": String(level)
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (sectionLike) {
|
|
510
|
+
const sectionNodes = renderSlotToArray(slots, "sectionLabel", itemCtx, label || content);
|
|
511
|
+
return _.div({
|
|
512
|
+
class: uiClass(["cms-drawer-section-label", it.class]),
|
|
513
|
+
"data-level": String(level)
|
|
514
|
+
}, ...sectionNodes);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (contentOnly) {
|
|
518
|
+
const customNodes = renderSlotToArray(slots, "item", itemCtx, content ?? label);
|
|
519
|
+
return _.div({
|
|
520
|
+
class: uiClass(["cms-drawer-custom", tone ? `cms-drawer-tone-${tone}` : "", it.class]),
|
|
521
|
+
style: it.style,
|
|
522
|
+
"data-level": String(level)
|
|
523
|
+
}, ...customNodes);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (childItems.length) {
|
|
527
|
+
const baseActiveInGroup = baseIsActive || hasActiveChildren(childItems, level + 1, path.concat(keyPart));
|
|
528
|
+
const activeInGroup = baseActiveInGroup || isBranchActive(itemStateKey);
|
|
529
|
+
let open = readGroupOpen(itemStateKey, !!uiUnwrap(it.expanded) || !!uiUnwrap(it.open) || activeInGroup);
|
|
530
|
+
const toggleIconEl = _.span({ class: "cms-drawer-group-icon" });
|
|
531
|
+
const groupCtx = {
|
|
532
|
+
...itemCtx,
|
|
533
|
+
active: activeInGroup,
|
|
534
|
+
hasChildren: true,
|
|
535
|
+
isOpen: () => open,
|
|
536
|
+
setOpen: (value) => setOpen(value),
|
|
537
|
+
toggle: () => setOpen(!open)
|
|
538
|
+
};
|
|
539
|
+
const paintToggleIcon = () => {
|
|
540
|
+
const icon = open ? (it.openIcon ?? groupOpenIcon) : (it.closeIcon ?? groupCloseIcon);
|
|
541
|
+
const side = uiUnwrap(
|
|
542
|
+
open
|
|
543
|
+
? (it.iconSidePosition || it.openIconSide || it.openIconPosition || "right")
|
|
544
|
+
: (it.iconSidePosition || it.closeIconSide || it.closeIconPosition || "right")
|
|
545
|
+
) === "left" ? "left" : "right";
|
|
546
|
+
toggleIconEl.innerHTML = "";
|
|
547
|
+
toggleIconEl.className = `cms-drawer-group-icon ${side}`;
|
|
548
|
+
iconNodes(icon, { ...groupCtx, as: "toggleIcon" }).forEach((node) => toggleIconEl.appendChild(node));
|
|
549
|
+
};
|
|
550
|
+
const toggleBtn = _.button({
|
|
551
|
+
type: "button",
|
|
552
|
+
class: uiClass([
|
|
553
|
+
"cms-drawer-group-toggle",
|
|
554
|
+
"cms-drawer-entry",
|
|
555
|
+
open ? "open" : "",
|
|
556
|
+
activeInGroup ? "active" : "",
|
|
557
|
+
disabled ? "is-disabled" : "",
|
|
558
|
+
tone ? `cms-drawer-tone-${tone}` : "",
|
|
559
|
+
it.class
|
|
560
|
+
]),
|
|
561
|
+
style: it.style,
|
|
562
|
+
"data-level": String(level),
|
|
563
|
+
"aria-expanded": String(open),
|
|
564
|
+
"aria-disabled": disabled ? "true" : null,
|
|
565
|
+
onClick: (e) => {
|
|
566
|
+
if (disabled) {
|
|
567
|
+
e.preventDefault();
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
setActiveItem(itemStateKey);
|
|
571
|
+
setOpen(!open);
|
|
572
|
+
}
|
|
573
|
+
}, ...renderEntryNodes(it, groupCtx, {
|
|
574
|
+
start: uiUnwrap(it.iconSidePosition || it.openIconSide || it.closeIconSide || it.openIconPosition || it.closeIconPosition || "right") === "left" ? [toggleIconEl] : [],
|
|
575
|
+
end: uiUnwrap(it.iconSidePosition || it.openIconSide || it.closeIconSide || it.openIconPosition || it.closeIconPosition || "right") === "right" ? [toggleIconEl] : []
|
|
576
|
+
}));
|
|
577
|
+
const customGroupHeader = renderSlotToArray(slots, "group", groupCtx, null);
|
|
578
|
+
const groupItems = _.div({ class: "cms-drawer-group-items" }, ...renderItems(childItems, level + 1, path.concat(keyPart)));
|
|
579
|
+
const groupWrap = _.div({
|
|
580
|
+
class: uiClass([
|
|
581
|
+
"cms-drawer-group",
|
|
582
|
+
open ? "open" : "",
|
|
583
|
+
activeInGroup ? "active" : "",
|
|
584
|
+
tone ? `cms-drawer-tone-${tone}` : ""
|
|
585
|
+
]),
|
|
586
|
+
"data-level": String(level)
|
|
587
|
+
}, ...(customGroupHeader.length ? customGroupHeader : [toggleBtn]), groupItems);
|
|
588
|
+
const setOpen = (value) => {
|
|
589
|
+
open = !!value;
|
|
590
|
+
groupWrap.classList.toggle("open", open);
|
|
591
|
+
toggleBtn.classList.toggle("open", open);
|
|
592
|
+
toggleBtn.setAttribute("aria-expanded", String(open));
|
|
593
|
+
paintToggleIcon();
|
|
594
|
+
writeGroupOpen(itemStateKey, open);
|
|
595
|
+
};
|
|
596
|
+
activeGroupRefs.set(itemStateKey, {
|
|
597
|
+
wrap: groupWrap,
|
|
598
|
+
toggle: toggleBtn,
|
|
599
|
+
baseActive: baseActiveInGroup
|
|
600
|
+
});
|
|
601
|
+
paintToggleIcon();
|
|
602
|
+
return groupWrap;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const customItem = renderSlotToArray(slots, "item", itemCtx, null);
|
|
606
|
+
const isButtonItem = uiUnwrap(it.button) === true || it.type === "button" || (!href && typeof it.onClick === "function");
|
|
607
|
+
const sharedProps = {
|
|
608
|
+
class: uiClass([
|
|
609
|
+
"cms-drawer-entry",
|
|
610
|
+
isButtonItem ? "cms-drawer-btn" : "cms-drawer-link",
|
|
611
|
+
isActive ? "active" : "",
|
|
612
|
+
disabled ? "is-disabled" : "",
|
|
613
|
+
tone ? `cms-drawer-tone-${tone}` : "",
|
|
614
|
+
it.class
|
|
615
|
+
]),
|
|
616
|
+
style: it.style,
|
|
617
|
+
"data-level": String(level),
|
|
618
|
+
"aria-current": isActive ? "page" : null,
|
|
619
|
+
"aria-disabled": disabled ? "true" : null
|
|
620
|
+
};
|
|
621
|
+
const entryNodes = customItem.length ? customItem : renderEntryNodes(it, itemCtx);
|
|
622
|
+
|
|
623
|
+
if (isButtonItem) {
|
|
624
|
+
const entryEl = _.button({
|
|
625
|
+
...sharedProps,
|
|
626
|
+
type: it.buttonType || "button",
|
|
627
|
+
disabled,
|
|
628
|
+
onClick: (e) => {
|
|
629
|
+
if (disabled) {
|
|
630
|
+
e.preventDefault();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (!invokeItemHandler(it, itemCtx, e)) return;
|
|
634
|
+
setActiveItem(itemStateKey);
|
|
635
|
+
if (shouldClose(it)) closeDrawer();
|
|
636
|
+
}
|
|
637
|
+
}, ...entryNodes);
|
|
638
|
+
activeLeafRefs.set(itemStateKey, { el: entryEl, baseActive: baseIsActive });
|
|
639
|
+
return entryEl;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const external = isExternalLink(it);
|
|
643
|
+
const target = external ? (uiUnwrap(it.target) || "_blank") : uiUnwrap(it.target);
|
|
644
|
+
const rel = external ? (uiUnwrap(it.rel) || "noopener noreferrer") : uiUnwrap(it.rel);
|
|
645
|
+
|
|
646
|
+
const entryEl = _.a({
|
|
647
|
+
...sharedProps,
|
|
648
|
+
href: href || "#",
|
|
649
|
+
target: target || null,
|
|
650
|
+
rel: rel || null,
|
|
651
|
+
tabIndex: disabled ? -1 : null,
|
|
652
|
+
onClick: (e) => {
|
|
653
|
+
if (disabled) {
|
|
654
|
+
e.preventDefault();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (!invokeItemHandler(it, itemCtx, e)) return;
|
|
658
|
+
if (!external && it.to && app.router?.navigate) {
|
|
659
|
+
e.preventDefault();
|
|
660
|
+
app.router.navigate(it.to);
|
|
661
|
+
}
|
|
662
|
+
if (shouldClose(it)) closeDrawer();
|
|
663
|
+
}
|
|
664
|
+
}, ...entryNodes);
|
|
665
|
+
activeLeafRefs.set(itemStateKey, { el: entryEl, baseActive: baseIsActive });
|
|
666
|
+
return entryEl;
|
|
667
|
+
}).filter(Boolean);
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
const beforeNodes = renderArea(["before", "beforeItems"], rawProps.before ?? rawProps.beforeItems);
|
|
671
|
+
const bodyCustom = hasArea(["body"]) ? renderArea(["body"], null) : [];
|
|
672
|
+
const itemNodes = renderItems(rawProps.items || []);
|
|
673
|
+
const defaultNodes = renderArea(["default"], children);
|
|
674
|
+
const afterNodes = renderArea(["after", "afterItems"], rawProps.after ?? rawProps.afterItems);
|
|
675
|
+
const bodyNodes = bodyCustom.length ? bodyCustom : [...beforeNodes, ...itemNodes, ...defaultNodes, ...afterNodes];
|
|
676
|
+
const emptyFallback = hasOwn("empty") || hasOwn("emptyText")
|
|
677
|
+
? (rawProps.empty ?? _.div({ class: uiClass(["cms-drawer-empty", rawProps.emptyClass]) }, rawProps.emptyText || "Nessun contenuto"))
|
|
678
|
+
: _.div({ class: uiClass(["cms-drawer-empty", rawProps.emptyClass]) }, "Nessun contenuto");
|
|
679
|
+
const headerNodes = renderArea(["header"], buildStructuredHeader());
|
|
680
|
+
const footerNodes = renderArea(["footer"], rawProps.footer);
|
|
681
|
+
const finalBodyNodes = bodyNodes.length ? bodyNodes : renderArea(["empty"], emptyFallback);
|
|
682
|
+
|
|
683
|
+
const p = JSswift.omit(props, [
|
|
684
|
+
"items", "header", "footer", "before", "beforeItems", "after", "afterItems",
|
|
685
|
+
"title", "subtitle", "eyebrow", "icon", "content", "meta", "actions",
|
|
686
|
+
"empty", "emptyText", "closeOnSelect", "groupOpenIcon", "groupCloseIcon",
|
|
687
|
+
"stateKey", "sticky", "slots", "headerClass", "bodyClass", "footerClass",
|
|
688
|
+
"emptyClass", "itemIconSize", "gap", "indent", "padding", "minHeight",
|
|
689
|
+
"maxHeight", "width", "onSelect"
|
|
690
|
+
]);
|
|
691
|
+
p.class = uiClass([
|
|
692
|
+
"cms-panel",
|
|
693
|
+
"cms-drawer",
|
|
694
|
+
drawerOpen ? "open" : "",
|
|
695
|
+
uiWhen(rawProps.sticky, "sticky"),
|
|
696
|
+
props.class
|
|
697
|
+
]);
|
|
698
|
+
p.style = { ...(props.style || {}) };
|
|
699
|
+
if (rawProps.gap != null) p.style["--cms-drawer-gap"] = toCssSize(uiUnwrap(rawProps.gap));
|
|
700
|
+
if (rawProps.indent != null) p.style["--cms-drawer-indent"] = toCssSize(uiUnwrap(rawProps.indent));
|
|
701
|
+
if (rawProps.padding != null) p.style["--cms-drawer-padding"] = toCssSize(uiUnwrap(rawProps.padding));
|
|
702
|
+
if (rawProps.width != null) p.style.width = toCssSize(uiUnwrap(rawProps.width));
|
|
703
|
+
if (rawProps.minHeight != null) p.style.minHeight = toCssSize(uiUnwrap(rawProps.minHeight));
|
|
704
|
+
if (rawProps.maxHeight != null) p.style.maxHeight = toCssSize(uiUnwrap(rawProps.maxHeight));
|
|
705
|
+
|
|
706
|
+
const drawerEl = _.div(
|
|
707
|
+
p,
|
|
708
|
+
...(headerNodes.length ? headerNodes : []),
|
|
709
|
+
_.div({ class: uiClass(["cms-drawer-body", rawProps.bodyClass]) }, ...finalBodyNodes),
|
|
710
|
+
...(footerNodes.length ? [_.div({ class: uiClass(["cms-drawer-footer", rawProps.footerClass]) }, ...footerNodes)] : [])
|
|
711
|
+
);
|
|
712
|
+
const drawerSet = drawerElsByKey.get(currentStateKey) || new Set();
|
|
713
|
+
drawerSet.add(drawerEl);
|
|
714
|
+
drawerElsByKey.set(currentStateKey, drawerSet);
|
|
715
|
+
setDrawerOpen(drawerOpen, currentStateKey);
|
|
716
|
+
drawerEl.isDrawerOpen = isDrawerOpen;
|
|
717
|
+
drawerEl.openDrawer = openDrawer;
|
|
718
|
+
drawerEl.closeDrawer = closeDrawer;
|
|
719
|
+
drawerEl.toggleDrawer = toggleDrawer;
|
|
720
|
+
drawerEl.setActiveItem = setActiveItem;
|
|
721
|
+
setPropertyProps(drawerEl, rawProps);
|
|
722
|
+
syncActiveClasses();
|
|
723
|
+
return drawerEl;
|
|
724
|
+
};
|
|
725
|
+
if (JSswift.isDev?.()) {
|
|
726
|
+
UI.meta = UI.meta || {};
|
|
727
|
+
UI.meta.Drawer = {
|
|
728
|
+
signature: "UI.Drawer(props)",
|
|
729
|
+
props: {
|
|
730
|
+
items: "Array",
|
|
731
|
+
header: "Node|Function|Array",
|
|
732
|
+
footer: "Node|Function|Array",
|
|
733
|
+
before: "Node|Function|Array",
|
|
734
|
+
after: "Node|Function|Array",
|
|
735
|
+
title: "String|Node|Function|Array",
|
|
736
|
+
subtitle: "String|Node|Function|Array",
|
|
737
|
+
eyebrow: "String|Node|Function|Array",
|
|
738
|
+
icon: "String|Node|Function|Array",
|
|
739
|
+
content: "Node|Function|Array",
|
|
740
|
+
meta: "Node|Function|Array",
|
|
741
|
+
actions: "Node|Function|Array",
|
|
742
|
+
empty: "Node|Function|Array",
|
|
743
|
+
emptyText: "string",
|
|
744
|
+
stateKey: "string",
|
|
745
|
+
closeOnSelect: "boolean",
|
|
746
|
+
groupOpenIcon: "String|Node|Function|Array",
|
|
747
|
+
groupCloseIcon: "String|Node|Function|Array",
|
|
748
|
+
itemIconSize: "string|number",
|
|
749
|
+
gap: "string|number",
|
|
750
|
+
indent: "string|number",
|
|
751
|
+
padding: "string|number",
|
|
752
|
+
width: "string|number",
|
|
753
|
+
minHeight: "string|number",
|
|
754
|
+
maxHeight: "string|number",
|
|
755
|
+
onSelect: "function(item, ctx, event)",
|
|
756
|
+
sticky: "boolean",
|
|
757
|
+
slots: "{ header?, body?, footer?, before?, after?, empty?, item?, itemLabel?, itemNote?, itemBadge?, itemAside?, itemContent?, group?, groupLabel?, groupNote?, groupBadge?, groupAside?, groupContent?, sectionLabel? }",
|
|
758
|
+
class: "string",
|
|
759
|
+
style: "object"
|
|
760
|
+
},
|
|
761
|
+
slots: {
|
|
762
|
+
header: "Drawer header, full override",
|
|
763
|
+
body: "Full body override",
|
|
764
|
+
footer: "Drawer footer",
|
|
765
|
+
before: "Content before the item list",
|
|
766
|
+
after: "Content after the item list",
|
|
767
|
+
empty: "Empty state when there is no content",
|
|
768
|
+
item: "Full override for a simple item",
|
|
769
|
+
itemLabel: "Item label (ctx: { item, label, note, badge, aside, content })",
|
|
770
|
+
itemNote: "Item note/subtitle",
|
|
771
|
+
itemBadge: "Item badge",
|
|
772
|
+
itemAside: "Item aside/meta",
|
|
773
|
+
itemContent: "Extra item content",
|
|
774
|
+
group: "Full override for a group header (ctx includes toggle/isOpen)",
|
|
775
|
+
groupLabel: "Group label",
|
|
776
|
+
groupNote: "Group note",
|
|
777
|
+
groupBadge: "Group badge",
|
|
778
|
+
groupAside: "Group aside/meta",
|
|
779
|
+
groupContent: "Extra group content",
|
|
780
|
+
sectionLabel: "Label for section/heading items"
|
|
781
|
+
},
|
|
782
|
+
returns: "HTMLDivElement with openDrawer/closeDrawer/toggleDrawer/isDrawerOpen methods",
|
|
783
|
+
description: "Structured, backward-compatible drawer with header/footer, groups, extended slots, empty state, and persistent state."
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
UI.Page = (...args) => {
|
|
788
|
+
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
789
|
+
const slots = rawProps.slots || {};
|
|
790
|
+
const props = { ...rawProps };
|
|
791
|
+
|
|
792
|
+
const isSectionNode = (node, name) => {
|
|
793
|
+
return !!(node && node.nodeType === 1 && node.classList?.contains(`cms-page-${name}`));
|
|
794
|
+
};
|
|
795
|
+
const renderIconFallback = (value) => {
|
|
796
|
+
if (value == null) return null;
|
|
797
|
+
if (typeof value === "string") return UI.Icon({ name: value, size: rawProps.iconSize || rawProps.size || "xl" });
|
|
798
|
+
return JSswift.ui.slot(value, { as: "icon" });
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
const ctx = {
|
|
802
|
+
dense: !!uiUnwrap(rawProps.dense),
|
|
803
|
+
flat: !!uiUnwrap(rawProps.flat),
|
|
804
|
+
centered: !!uiUnwrap(rawProps.centered),
|
|
805
|
+
narrow: !!uiUnwrap(rawProps.narrow)
|
|
806
|
+
};
|
|
807
|
+
const appendResolvedValue = (host, value) => {
|
|
808
|
+
if (value == null || value === false) return;
|
|
809
|
+
if (Array.isArray(value)) {
|
|
810
|
+
value.forEach((item) => appendResolvedValue(host, item));
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (value?.nodeType) {
|
|
814
|
+
host.appendChild(value);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
host.appendChild(document.createTextNode(String(value)));
|
|
818
|
+
};
|
|
819
|
+
const renderPropNodes = (name, fallback, map = (value) => value) => {
|
|
820
|
+
const slot = JSswift.ui.getSlot(slots, name);
|
|
821
|
+
if (slot !== null && slot !== undefined) {
|
|
822
|
+
return renderSlotToArray(slots, name, ctx, null);
|
|
823
|
+
}
|
|
824
|
+
if (typeof fallback === "function") {
|
|
825
|
+
const inlineNames = new Set(["eyebrow", "title", "subtitle"]);
|
|
826
|
+
const host = _[inlineNames.has(name) ? "span" : "div"]({ class: `cms-page-slot-${name}` });
|
|
827
|
+
JSswift.reactive.effect(() => {
|
|
828
|
+
const nextValue = map(fallback(ctx));
|
|
829
|
+
const normalized = flattenSlotValue(JSswift.ui.slot(nextValue));
|
|
830
|
+
host.replaceChildren();
|
|
831
|
+
if (Array.isArray(normalized)) normalized.forEach((item) => appendResolvedValue(host, item));
|
|
832
|
+
else appendResolvedValue(host, normalized);
|
|
833
|
+
}, `UI.Page:${name}`);
|
|
834
|
+
return [host];
|
|
835
|
+
}
|
|
836
|
+
return renderSlotToArray(slots, name, ctx, map(fallback));
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
const defaultNodes = renderSlotToArray(slots, "default", ctx, children?.length ? children : []);
|
|
840
|
+
const sectionNodes = {
|
|
841
|
+
hero: [],
|
|
842
|
+
header: [],
|
|
843
|
+
body: [],
|
|
844
|
+
footer: [],
|
|
845
|
+
actions: []
|
|
846
|
+
};
|
|
847
|
+
const looseNodes = [];
|
|
848
|
+
|
|
849
|
+
defaultNodes.forEach((node) => {
|
|
850
|
+
if (isSectionNode(node, "hero")) sectionNodes.hero.push(node);
|
|
851
|
+
else if (isSectionNode(node, "header")) sectionNodes.header.push(node);
|
|
852
|
+
else if (isSectionNode(node, "body")) sectionNodes.body.push(node);
|
|
853
|
+
else if (isSectionNode(node, "footer")) sectionNodes.footer.push(node);
|
|
854
|
+
else if (isSectionNode(node, "actions")) sectionNodes.actions.push(node);
|
|
855
|
+
else looseNodes.push(node);
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
const iconNodes = renderPropNodes("icon", rawProps.icon, renderIconFallback);
|
|
859
|
+
const heroNodes = renderPropNodes("hero", rawProps.hero ?? rawProps.banner);
|
|
860
|
+
const eyebrowNodes = renderPropNodes("eyebrow", rawProps.eyebrow ?? rawProps.kicker);
|
|
861
|
+
const titleNodes = renderPropNodes("title", rawProps.title);
|
|
862
|
+
const subtitleNodes = renderPropNodes("subtitle", rawProps.subtitle ?? rawProps.description);
|
|
863
|
+
const headerNodes = renderPropNodes("header", rawProps.header);
|
|
864
|
+
const asideNodes = renderPropNodes("aside", rawProps.aside);
|
|
865
|
+
const bodyNodes = renderPropNodes("body", rawProps.body ?? rawProps.content);
|
|
866
|
+
const footerNodes = renderPropNodes("footer", rawProps.footer);
|
|
867
|
+
const actionsNodes = renderPropNodes("actions", rawProps.actions);
|
|
868
|
+
|
|
869
|
+
const generatedHero = heroNodes.length
|
|
870
|
+
? _.div({ class: uiClass(["cms-page-hero", rawProps.heroClass]) }, ...heroNodes)
|
|
871
|
+
: null;
|
|
872
|
+
|
|
873
|
+
const hasStructuredHeader = iconNodes.length || eyebrowNodes.length || titleNodes.length || subtitleNodes.length || headerNodes.length || asideNodes.length;
|
|
874
|
+
const generatedHeader = hasStructuredHeader
|
|
875
|
+
? _.div(
|
|
876
|
+
{ class: uiClass(["cms-page-header", rawProps.headerClass]) },
|
|
877
|
+
_.div(
|
|
878
|
+
{ class: "cms-page-head" },
|
|
879
|
+
iconNodes.length ? _.div({ class: uiClass(["cms-page-icon", rawProps.iconClass]) }, ...iconNodes) : null,
|
|
880
|
+
_.div(
|
|
881
|
+
{ class: "cms-page-head-main" },
|
|
882
|
+
eyebrowNodes.length ? _.div({ class: uiClass(["cms-page-eyebrow", rawProps.eyebrowClass]) }, ...eyebrowNodes) : null,
|
|
883
|
+
titleNodes.length ? _.div({ class: uiClass(["cms-page-title", rawProps.titleClass]) }, ...titleNodes) : null,
|
|
884
|
+
subtitleNodes.length ? _.div({ class: uiClass(["cms-page-subtitle", rawProps.subtitleClass]) }, ...subtitleNodes) : null,
|
|
885
|
+
headerNodes.length ? _.div({ class: uiClass(["cms-page-header-content", rawProps.headerContentClass]) }, ...headerNodes) : null
|
|
886
|
+
),
|
|
887
|
+
asideNodes.length ? _.div({ class: uiClass(["cms-page-aside", rawProps.asideClass]) }, ...asideNodes) : null
|
|
888
|
+
)
|
|
889
|
+
)
|
|
890
|
+
: null;
|
|
891
|
+
|
|
892
|
+
const mergedBodyNodes = [...bodyNodes, ...looseNodes];
|
|
893
|
+
const generatedBody = mergedBodyNodes.length
|
|
894
|
+
? _.div({ class: uiClass(["cms-page-body", rawProps.bodyClass]) }, ...mergedBodyNodes)
|
|
895
|
+
: null;
|
|
896
|
+
|
|
897
|
+
const mergedActionNodes = [...actionsNodes, ...sectionNodes.actions];
|
|
898
|
+
const generatedFooter = (footerNodes.length || mergedActionNodes.length)
|
|
899
|
+
? _.div(
|
|
900
|
+
{ class: uiClass(["cms-page-footer", rawProps.footerClass]) },
|
|
901
|
+
...footerNodes,
|
|
902
|
+
mergedActionNodes.length ? _.div({ class: "cms-page-actions" }, ...mergedActionNodes) : null
|
|
903
|
+
)
|
|
904
|
+
: null;
|
|
905
|
+
|
|
906
|
+
const hasHero = !!(heroNodes.length || sectionNodes.hero.length);
|
|
907
|
+
const hasHeader = !!(hasStructuredHeader || sectionNodes.header.length);
|
|
908
|
+
const p = JSswift.omit(props, [
|
|
909
|
+
"actions", "aside", "asideClass", "banner", "body", "bodyClass", "centered", "content",
|
|
910
|
+
"dense", "description", "eyebrow", "eyebrowClass", "flat", "footer", "footerClass",
|
|
911
|
+
"gap", "header", "headerClass", "headerContentClass", "headerGap", "hero", "heroClass",
|
|
912
|
+
"heroPadding", "icon", "iconClass", "iconSize", "kicker", "maxWidth", "minHeight",
|
|
913
|
+
"narrow", "padding", "size", "slots", "subtitle", "subtitleClass", "title", "titleClass"
|
|
914
|
+
]);
|
|
915
|
+
p.class = uiClass([
|
|
916
|
+
"cms-panel",
|
|
917
|
+
"cms-page",
|
|
918
|
+
uiWhen(rawProps.dense, "dense"),
|
|
919
|
+
uiWhen(rawProps.flat, "cms-page-flat"),
|
|
920
|
+
uiWhen(rawProps.centered, "cms-page-centered"),
|
|
921
|
+
uiWhen(rawProps.narrow, "cms-page-narrow"),
|
|
922
|
+
uiWhen(hasHero, "cms-page-has-hero"),
|
|
923
|
+
uiWhen(hasHeader, "cms-page-has-header"),
|
|
924
|
+
props.class
|
|
925
|
+
]);
|
|
926
|
+
p.style = { ...(props.style || {}) };
|
|
927
|
+
|
|
928
|
+
if (rawProps.gap != null || uiIsReactive(rawProps.gap)) {
|
|
929
|
+
p.style["--cms-page-gap"] = uiStyleValue(rawProps.gap, toCssSize);
|
|
930
|
+
}
|
|
931
|
+
if (rawProps.padding != null || uiIsReactive(rawProps.padding)) {
|
|
932
|
+
p.style["--cms-page-padding"] = uiStyleValue(rawProps.padding, toCssSize);
|
|
933
|
+
}
|
|
934
|
+
if (rawProps.maxWidth != null || uiIsReactive(rawProps.maxWidth)) {
|
|
935
|
+
p.style["--cms-page-max-width"] = uiStyleValue(rawProps.maxWidth, toCssSize);
|
|
936
|
+
}
|
|
937
|
+
if (rawProps.minHeight != null || uiIsReactive(rawProps.minHeight)) {
|
|
938
|
+
p.style["--cms-page-min-height"] = uiStyleValue(rawProps.minHeight, toCssSize);
|
|
939
|
+
}
|
|
940
|
+
if (rawProps.headerGap != null || uiIsReactive(rawProps.headerGap)) {
|
|
941
|
+
p.style["--cms-page-header-gap"] = uiStyleValue(rawProps.headerGap, toCssSize);
|
|
942
|
+
}
|
|
943
|
+
if (rawProps.heroPadding != null || uiIsReactive(rawProps.heroPadding)) {
|
|
944
|
+
p.style["--cms-page-hero-padding"] = uiStyleValue(rawProps.heroPadding, toCssSize);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const el = _.div(
|
|
948
|
+
p,
|
|
949
|
+
generatedHero,
|
|
950
|
+
...sectionNodes.hero,
|
|
951
|
+
generatedHeader,
|
|
952
|
+
...sectionNodes.header,
|
|
953
|
+
generatedBody,
|
|
954
|
+
...sectionNodes.body,
|
|
955
|
+
generatedFooter,
|
|
956
|
+
...sectionNodes.footer
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
setPropertyProps(el, rawProps);
|
|
960
|
+
return el;
|
|
961
|
+
};
|
|
962
|
+
if (JSswift.isDev?.()) {
|
|
963
|
+
UI.meta = UI.meta || {};
|
|
964
|
+
UI.meta.Page = {
|
|
965
|
+
signature: "UI.Page(...children) | UI.Page(props, ...children)",
|
|
966
|
+
props: {
|
|
967
|
+
hero: "Node|Function|Array",
|
|
968
|
+
icon: "String|Node|Function|Array",
|
|
969
|
+
eyebrow: "String|Node|Function|Array",
|
|
970
|
+
title: "String|Node|Function|Array",
|
|
971
|
+
subtitle: "String|Node|Function|Array",
|
|
972
|
+
header: "String|Node|Function|Array",
|
|
973
|
+
aside: "Node|Function|Array",
|
|
974
|
+
body: "Node|Function|Array",
|
|
975
|
+
footer: "Node|Function|Array",
|
|
976
|
+
actions: "Node|Function|Array",
|
|
977
|
+
dense: "boolean",
|
|
978
|
+
flat: "boolean",
|
|
979
|
+
centered: "boolean",
|
|
980
|
+
narrow: "boolean",
|
|
981
|
+
gap: "string|number",
|
|
982
|
+
padding: "string|number",
|
|
983
|
+
maxWidth: "string|number",
|
|
984
|
+
minHeight: "string|number",
|
|
985
|
+
heroPadding: "string|number",
|
|
986
|
+
headerGap: "string|number",
|
|
987
|
+
slots: "{ hero?, icon?, eyebrow?, title?, subtitle?, header?, aside?, body?, footer?, actions?, default? }",
|
|
988
|
+
class: "string",
|
|
989
|
+
style: "object"
|
|
990
|
+
},
|
|
991
|
+
slots: {
|
|
992
|
+
hero: "Top hero/banner area",
|
|
993
|
+
icon: "Page icon/visual",
|
|
994
|
+
eyebrow: "Eyebrow/kicker content",
|
|
995
|
+
title: "Page title content",
|
|
996
|
+
subtitle: "Page subtitle/description content",
|
|
997
|
+
header: "Header support content under title",
|
|
998
|
+
aside: "Top-right header content",
|
|
999
|
+
body: "Structured body content",
|
|
1000
|
+
footer: "Footer meta/content",
|
|
1001
|
+
actions: "Footer actions content",
|
|
1002
|
+
default: "Fallback body content"
|
|
1003
|
+
},
|
|
1004
|
+
returns: "HTMLDivElement",
|
|
1005
|
+
description: "Structured page container with hero, header, body, footer, and configurable layout."
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
UI.AppShell = (...args) => {
|
|
1010
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
1011
|
+
const slots = props.slots || {};
|
|
1012
|
+
const hasOwn = (obj, key) => !!obj && Object.prototype.hasOwnProperty.call(obj, key);
|
|
1013
|
+
|
|
1014
|
+
const noDrawer = uiUnwrap(props.noDrawer) === true || props.drawer === false;
|
|
1015
|
+
const reverse = uiUnwrap(props.reverse) === true;
|
|
1016
|
+
const stack = uiUnwrap(props.stack) === true;
|
|
1017
|
+
const currentStateKey = props.drawerStateKey ?? props.stateKey ?? drawerStateKey;
|
|
1018
|
+
|
|
1019
|
+
if (currentStateKey) {
|
|
1020
|
+
drawerStateKey = currentStateKey;
|
|
1021
|
+
drawerOpen = readDrawerOpen(currentStateKey);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const shellCtx = {
|
|
1025
|
+
props,
|
|
1026
|
+
noDrawer,
|
|
1027
|
+
stateKey: currentStateKey,
|
|
1028
|
+
isDrawerOpen: () => readDrawerOpen(currentStateKey),
|
|
1029
|
+
openDrawer: () => setDrawerOpen(true, currentStateKey),
|
|
1030
|
+
closeDrawer: () => setDrawerOpen(false, currentStateKey),
|
|
1031
|
+
toggleDrawer: () => setDrawerOpen(!readDrawerOpen(currentStateKey), currentStateKey)
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
const headerProps = (props.headerProps && typeof props.headerProps === "object") ? { ...props.headerProps } : {};
|
|
1035
|
+
let headerFallback = props.header;
|
|
1036
|
+
if (!hasOwn(props, "header") && (
|
|
1037
|
+
Object.keys(headerProps).length ||
|
|
1038
|
+
hasOwn(props, "title") ||
|
|
1039
|
+
hasOwn(props, "subtitle") ||
|
|
1040
|
+
hasOwn(props, "left") ||
|
|
1041
|
+
hasOwn(props, "right")
|
|
1042
|
+
)) {
|
|
1043
|
+
if (!hasOwn(headerProps, "title") && hasOwn(props, "title")) headerProps.title = props.title;
|
|
1044
|
+
if (!hasOwn(headerProps, "subtitle") && hasOwn(props, "subtitle")) headerProps.subtitle = props.subtitle;
|
|
1045
|
+
if (!hasOwn(headerProps, "left") && hasOwn(props, "left")) headerProps.left = props.left;
|
|
1046
|
+
if (!hasOwn(headerProps, "right") && hasOwn(props, "right")) headerProps.right = props.right;
|
|
1047
|
+
if (!hasOwn(headerProps, "drawerStateKey")) headerProps.drawerStateKey = currentStateKey;
|
|
1048
|
+
if (noDrawer && !hasOwn(headerProps, "left")) headerProps.left = false;
|
|
1049
|
+
headerFallback = UI.Header(headerProps);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const drawerProps = (props.drawerProps && typeof props.drawerProps === "object") ? { ...props.drawerProps } : {};
|
|
1053
|
+
const drawerItems = hasOwn(props, "drawerItems") ? props.drawerItems : props.items;
|
|
1054
|
+
const drawerHeader = hasOwn(props, "drawerHeader") ? props.drawerHeader : undefined;
|
|
1055
|
+
let drawerFallback = props.drawer;
|
|
1056
|
+
if (!hasOwn(props, "drawer") && !noDrawer && (
|
|
1057
|
+
Object.keys(drawerProps).length ||
|
|
1058
|
+
drawerItems != null ||
|
|
1059
|
+
drawerHeader != null
|
|
1060
|
+
)) {
|
|
1061
|
+
if (!hasOwn(drawerProps, "items") && drawerItems != null) drawerProps.items = drawerItems;
|
|
1062
|
+
if (!hasOwn(drawerProps, "header") && drawerHeader != null) drawerProps.header = drawerHeader;
|
|
1063
|
+
if (!hasOwn(drawerProps, "stateKey")) drawerProps.stateKey = currentStateKey;
|
|
1064
|
+
drawerFallback = UI.Drawer(drawerProps);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const pageProps = (props.pageProps && typeof props.pageProps === "object") ? { ...props.pageProps } : {};
|
|
1068
|
+
const pageContentFallback = hasOwn(props, "content") ? props.content : children;
|
|
1069
|
+
const defaultPageContent = renderSlotToArray(slots, "default", shellCtx, pageContentFallback);
|
|
1070
|
+
let pageFallback = props.page;
|
|
1071
|
+
if (!hasOwn(props, "page") && (Object.keys(pageProps).length || defaultPageContent.length)) {
|
|
1072
|
+
pageFallback = UI.Page(pageProps, ...defaultPageContent);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
const footerProps = (props.footerProps && typeof props.footerProps === "object") ? { ...props.footerProps } : {};
|
|
1076
|
+
const footerContent = hasOwn(props, "footerContent") ? props.footerContent : undefined;
|
|
1077
|
+
let footerFallback = props.footer;
|
|
1078
|
+
if (!hasOwn(props, "footer") && (Object.keys(footerProps).length || footerContent != null)) {
|
|
1079
|
+
footerFallback = UI.Footer(footerProps, ...renderSlotToArray(null, "default", shellCtx, footerContent));
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
const headerNodes = renderSlotToArray(slots, "header", shellCtx, headerFallback);
|
|
1083
|
+
const drawerNodes = noDrawer ? [] : renderSlotToArray(slots, "drawer", shellCtx, drawerFallback);
|
|
1084
|
+
const pageNodes = renderSlotToArray(slots, "page", shellCtx, pageFallback);
|
|
1085
|
+
const footerNodes = renderSlotToArray(slots, "footer", shellCtx, footerFallback);
|
|
1086
|
+
|
|
1087
|
+
const cls = uiClass([
|
|
1088
|
+
"cms-app",
|
|
1089
|
+
"cms-app-shell",
|
|
1090
|
+
uiWhen(noDrawer, "no-drawer"),
|
|
1091
|
+
uiWhen(reverse, "is-reverse"),
|
|
1092
|
+
uiWhen(stack, "is-stack"),
|
|
1093
|
+
uiWhen(props.flush, "is-flush"),
|
|
1094
|
+
uiWhen(props.divider, "is-divider"),
|
|
1095
|
+
props.class
|
|
1096
|
+
]);
|
|
1097
|
+
|
|
1098
|
+
const p = JSswift.omit(props, [
|
|
1099
|
+
"header", "drawer", "page", "footer", "content",
|
|
1100
|
+
"title", "subtitle", "left", "right",
|
|
1101
|
+
"items", "drawerItems", "drawerHeader",
|
|
1102
|
+
"headerProps", "drawerProps", "pageProps", "footerProps",
|
|
1103
|
+
"footerContent",
|
|
1104
|
+
"noDrawer", "reverse", "stack", "flush", "divider",
|
|
1105
|
+
"drawerStateKey", "stateKey",
|
|
1106
|
+
"drawerWidth", "gap", "padding",
|
|
1107
|
+
"slots"
|
|
1108
|
+
]);
|
|
1109
|
+
p.class = cls;
|
|
1110
|
+
p.style = { ...(props.style || {}) };
|
|
1111
|
+
|
|
1112
|
+
const drawerWidth = uiStyleValue(props.drawerWidth, toCssSize);
|
|
1113
|
+
if (drawerWidth != null) p.style["--cms-app-shell-drawer-width"] = drawerWidth;
|
|
1114
|
+
const gap = uiStyleValue(props.gap, toCssSize);
|
|
1115
|
+
if (gap != null) p.style["--cms-app-shell-gap"] = gap;
|
|
1116
|
+
const padding = uiStyleValue(props.padding, toCssSize);
|
|
1117
|
+
if (padding != null) p.style["--cms-app-shell-padding"] = padding;
|
|
1118
|
+
|
|
1119
|
+
const headerWrap = headerNodes.length
|
|
1120
|
+
? _.div({ class: "cms-app-shell-header-slot" }, ...headerNodes)
|
|
1121
|
+
: null;
|
|
1122
|
+
const drawerWrap = drawerNodes.length
|
|
1123
|
+
? _.div({ class: "cms-app-shell-drawer-slot" }, ...drawerNodes)
|
|
1124
|
+
: null;
|
|
1125
|
+
const pageWrap = pageNodes.length
|
|
1126
|
+
? _.div({ class: "cms-app-shell-page-slot" }, ...pageNodes)
|
|
1127
|
+
: null;
|
|
1128
|
+
const footerWrap = footerNodes.length
|
|
1129
|
+
? _.div({ class: "cms-app-shell-footer-slot" }, ...footerNodes)
|
|
1130
|
+
: null;
|
|
1131
|
+
|
|
1132
|
+
const bodyNodes = reverse
|
|
1133
|
+
? [pageWrap, drawerWrap]
|
|
1134
|
+
: [drawerWrap, pageWrap];
|
|
1135
|
+
|
|
1136
|
+
const root = _.div(
|
|
1137
|
+
p,
|
|
1138
|
+
...(headerWrap ? [headerWrap] : []),
|
|
1139
|
+
_.div({ class: "cms-app-shell-body" }, ...bodyNodes.filter(Boolean)),
|
|
1140
|
+
...(footerWrap ? [footerWrap] : [])
|
|
1141
|
+
);
|
|
1142
|
+
|
|
1143
|
+
root.openDrawer = shellCtx.openDrawer;
|
|
1144
|
+
root.closeDrawer = shellCtx.closeDrawer;
|
|
1145
|
+
root.toggleDrawer = shellCtx.toggleDrawer;
|
|
1146
|
+
root.isDrawerOpen = shellCtx.isDrawerOpen;
|
|
1147
|
+
root.header = headerWrap;
|
|
1148
|
+
root.drawer = drawerWrap;
|
|
1149
|
+
root.page = pageWrap;
|
|
1150
|
+
root.footer = footerWrap;
|
|
1151
|
+
|
|
1152
|
+
setPropertyProps(root, props);
|
|
1153
|
+
return root;
|
|
1154
|
+
};
|
|
1155
|
+
if (JSswift.isDev?.()) {
|
|
1156
|
+
UI.meta = UI.meta || {};
|
|
1157
|
+
UI.meta.AppShell = {
|
|
1158
|
+
signature: "UI.AppShell(...children) | UI.AppShell(props, ...children)",
|
|
1159
|
+
props: {
|
|
1160
|
+
header: "Node|Function|Array|false",
|
|
1161
|
+
drawer: "Node|Function|Array|false",
|
|
1162
|
+
page: "Node|Function|Array|false",
|
|
1163
|
+
footer: "Node|Function|Array|false",
|
|
1164
|
+
title: "string|Node|Function|Array",
|
|
1165
|
+
subtitle: "string|Node|Function|Array",
|
|
1166
|
+
left: "Node|Function|Array|false",
|
|
1167
|
+
right: "Node|Function|Array",
|
|
1168
|
+
items: "Array",
|
|
1169
|
+
drawerItems: "Array",
|
|
1170
|
+
drawerHeader: "Node|Function|Array",
|
|
1171
|
+
content: "Node|Function|Array",
|
|
1172
|
+
headerProps: "object",
|
|
1173
|
+
drawerProps: "object",
|
|
1174
|
+
pageProps: "object",
|
|
1175
|
+
footerProps: "object",
|
|
1176
|
+
footerContent: "Node|Function|Array",
|
|
1177
|
+
noDrawer: "boolean",
|
|
1178
|
+
reverse: "boolean",
|
|
1179
|
+
stack: "boolean",
|
|
1180
|
+
flush: "boolean",
|
|
1181
|
+
divider: "boolean",
|
|
1182
|
+
drawerStateKey: "string",
|
|
1183
|
+
stateKey: "string",
|
|
1184
|
+
drawerWidth: "number|string",
|
|
1185
|
+
gap: "number|string",
|
|
1186
|
+
padding: "number|string",
|
|
1187
|
+
slots: "{ header?, drawer?, page?, footer?, default? }",
|
|
1188
|
+
class: "string",
|
|
1189
|
+
style: "object"
|
|
1190
|
+
},
|
|
1191
|
+
slots: {
|
|
1192
|
+
header: "Header content",
|
|
1193
|
+
drawer: "Drawer content",
|
|
1194
|
+
page: "Page content",
|
|
1195
|
+
footer: "Footer content",
|
|
1196
|
+
default: "Fallback page content"
|
|
1197
|
+
},
|
|
1198
|
+
returns: "HTMLDivElement con methods openDrawer/closeDrawer/toggleDrawer/isDrawerOpen",
|
|
1199
|
+
description: "Shell applicazione composabile con shortcut per Header/Drawer/Page/Footer e gestione drawer."
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
UI.Parallax = function Parallax(...args) {
|
|
1204
|
+
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
1205
|
+
const slots = rawProps.slots || {};
|
|
1206
|
+
const props = { ...rawProps };
|
|
1207
|
+
|
|
1208
|
+
if (props.color != null && props.textColor == null) props.textColor = props.color;
|
|
1209
|
+
delete props.color;
|
|
1210
|
+
applyCommonProps(props);
|
|
1211
|
+
|
|
1212
|
+
const stateClass = uiComputed(rawProps.state, () => {
|
|
1213
|
+
const state = normalizeState(uiUnwrap(rawProps.state));
|
|
1214
|
+
return state ? `cms-state-${state}` : "";
|
|
1215
|
+
});
|
|
1216
|
+
const mapColorValue = (value) => {
|
|
1217
|
+
if (value == null || value === false || value === "") return "";
|
|
1218
|
+
const text = String(value);
|
|
1219
|
+
return isTokenCSS(text) ? `var(--cms-${text})` : text;
|
|
1220
|
+
};
|
|
1221
|
+
const getBackgroundImage = () => {
|
|
1222
|
+
const background = uiUnwrap(rawProps.background);
|
|
1223
|
+
if (background) return String(background);
|
|
1224
|
+
const source = uiUnwrap(rawProps.image) || uiUnwrap(rawProps.src) || "";
|
|
1225
|
+
if (!source) return "linear-gradient(120deg, #1b2c5a, #2f6d8f, #2a8f5c)";
|
|
1226
|
+
const text = String(source);
|
|
1227
|
+
if (/^url\(/i.test(text) || /gradient\(/i.test(text) || /^var\(/i.test(text)) return text;
|
|
1228
|
+
return `url(${text})`;
|
|
1229
|
+
};
|
|
1230
|
+
const getNumericValue = (value, fallback) => {
|
|
1231
|
+
const num = Number(uiUnwrap(value));
|
|
1232
|
+
return Number.isFinite(num) ? num : fallback;
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1235
|
+
const ctx = {
|
|
1236
|
+
speed: () => uiUnwrap(rawProps.speed) ?? 0.18,
|
|
1237
|
+
state: () => uiUnwrap(rawProps.state) || "",
|
|
1238
|
+
disabled: () => !!uiUnwrap(rawProps.disabled)
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
const badgeNodes = renderSlotToArray(slots, "badge", ctx, rawProps.badge);
|
|
1242
|
+
const backgroundNodes = renderSlotToArray(slots, "background", ctx, rawProps.backgroundContent);
|
|
1243
|
+
const eyebrowNodes = renderSlotToArray(slots, "eyebrow", ctx, rawProps.eyebrow ?? rawProps.kicker);
|
|
1244
|
+
const titleNodes = renderSlotToArray(slots, "title", ctx, rawProps.title);
|
|
1245
|
+
const subtitleNodes = renderSlotToArray(slots, "subtitle", ctx, rawProps.subtitle);
|
|
1246
|
+
const headerNodes = renderSlotToArray(slots, "header", ctx, rawProps.header);
|
|
1247
|
+
const asideNodes = renderSlotToArray(slots, "aside", ctx, rawProps.aside);
|
|
1248
|
+
const mediaNodes = renderSlotToArray(slots, "media", ctx, rawProps.media);
|
|
1249
|
+
const contentNodes = renderSlotToArray(slots, "content", ctx, rawProps.content ?? rawProps.body);
|
|
1250
|
+
const defaultNodes = renderSlotToArray(slots, "default", ctx, children?.length ? children : []);
|
|
1251
|
+
const footerNodes = renderSlotToArray(slots, "footer", ctx, rawProps.footer);
|
|
1252
|
+
const actionsNodes = renderSlotToArray(slots, "actions", ctx, rawProps.actions);
|
|
1253
|
+
const mergedBodyNodes = [...contentNodes, ...defaultNodes];
|
|
1254
|
+
|
|
1255
|
+
const generatedBadge = badgeNodes.length
|
|
1256
|
+
? _.div({ class: uiClass(["cms-parallax-badge", rawProps.badgeClass]) }, ...badgeNodes)
|
|
1257
|
+
: null;
|
|
1258
|
+
const hasStructuredHeader = eyebrowNodes.length || titleNodes.length || subtitleNodes.length || headerNodes.length || asideNodes.length;
|
|
1259
|
+
const generatedHeader = hasStructuredHeader
|
|
1260
|
+
? _.div(
|
|
1261
|
+
{ class: uiClass(["cms-parallax-header", rawProps.headerClass]) },
|
|
1262
|
+
_.div(
|
|
1263
|
+
{ class: "cms-parallax-head" },
|
|
1264
|
+
_.div(
|
|
1265
|
+
{ class: "cms-parallax-head-main" },
|
|
1266
|
+
eyebrowNodes.length ? _.div({ class: uiClass(["cms-parallax-eyebrow", rawProps.eyebrowClass]) }, ...eyebrowNodes) : null,
|
|
1267
|
+
titleNodes.length ? _.div({ class: uiClass(["cms-parallax-title", rawProps.titleClass]) }, ...titleNodes) : null,
|
|
1268
|
+
subtitleNodes.length ? _.div({ class: uiClass(["cms-parallax-subtitle", rawProps.subtitleClass]) }, ...subtitleNodes) : null,
|
|
1269
|
+
headerNodes.length ? _.div({ class: uiClass(["cms-parallax-header-content", rawProps.headerContentClass]) }, ...headerNodes) : null
|
|
1270
|
+
),
|
|
1271
|
+
asideNodes.length ? _.div({ class: uiClass(["cms-parallax-aside", rawProps.asideClass]) }, ...asideNodes) : null
|
|
1272
|
+
)
|
|
1273
|
+
)
|
|
1274
|
+
: null;
|
|
1275
|
+
const generatedMedia = mediaNodes.length
|
|
1276
|
+
? _.div({ class: uiClass(["cms-parallax-media", rawProps.mediaClass]) }, ...mediaNodes)
|
|
1277
|
+
: null;
|
|
1278
|
+
const generatedBody = mergedBodyNodes.length
|
|
1279
|
+
? _.div({ class: uiClass(["cms-parallax-body", rawProps.bodyClass]) }, ...mergedBodyNodes)
|
|
1280
|
+
: null;
|
|
1281
|
+
const generatedFooter = (footerNodes.length || actionsNodes.length)
|
|
1282
|
+
? _.div(
|
|
1283
|
+
{ class: uiClass(["cms-parallax-footer", rawProps.footerClass]) },
|
|
1284
|
+
footerNodes.length ? _.div({ class: "cms-parallax-footer-content" }, ...footerNodes) : null,
|
|
1285
|
+
actionsNodes.length ? _.div({ class: "cms-parallax-actions" }, ...actionsNodes) : null
|
|
1286
|
+
)
|
|
1287
|
+
: null;
|
|
1288
|
+
|
|
1289
|
+
const wrapProps = JSswift.omit(props, [
|
|
1290
|
+
"actions", "align", "aside", "asideClass", "background", "backgroundContent", "backgroundContentClass",
|
|
1291
|
+
"badge", "badgeClass", "bgClass", "bgPosition", "bgRepeat", "bgSize", "body", "bodyClass", "color",
|
|
1292
|
+
"content", "contentClass", "contentMaxWidth", "disabled", "eyebrow", "eyebrowClass", "footer",
|
|
1293
|
+
"footerClass", "gap", "header", "headerClass", "headerContentClass", "height", "image", "imageAlt",
|
|
1294
|
+
"imageClass", "innerClass", "justify", "kicker", "maxOffset", "media", "mediaClass", "minHeight",
|
|
1295
|
+
"overlay", "padding", "slots", "speed", "src", "startTop", "state", "subtitle", "subtitleClass",
|
|
1296
|
+
"textColor", "title", "titleClass"
|
|
1297
|
+
]);
|
|
1298
|
+
wrapProps.class = uiClass([
|
|
1299
|
+
"cms-parallax",
|
|
1300
|
+
"cms-singularity",
|
|
1301
|
+
stateClass,
|
|
1302
|
+
uiWhen(rawProps.disabled, "cms-parallax-static"),
|
|
1303
|
+
uiWhen(generatedBadge, "cms-parallax-has-badge"),
|
|
1304
|
+
uiWhen(hasStructuredHeader, "cms-parallax-has-header"),
|
|
1305
|
+
props.class
|
|
1306
|
+
]);
|
|
1307
|
+
wrapProps.style = { ...(props.style || {}) };
|
|
1308
|
+
if (rawProps.height != null) wrapProps.style["--cms-parallax-height"] = uiStyleValue(rawProps.height, toCssSize);
|
|
1309
|
+
if (rawProps.minHeight != null) wrapProps.style["--cms-parallax-min-height"] = uiStyleValue(rawProps.minHeight, toCssSize);
|
|
1310
|
+
if (rawProps.padding != null) wrapProps.style["--cms-parallax-padding"] = uiStyleValue(rawProps.padding, toCssSize);
|
|
1311
|
+
if (rawProps.gap != null) wrapProps.style["--cms-parallax-gap"] = uiStyleValue(rawProps.gap, toCssSize);
|
|
1312
|
+
if (rawProps.contentMaxWidth != null) wrapProps.style["--cms-parallax-content-max-width"] = uiStyleValue(rawProps.contentMaxWidth, toCssSize);
|
|
1313
|
+
if (rawProps.justify != null) wrapProps.style["--cms-parallax-justify"] = uiStyleValue(rawProps.justify);
|
|
1314
|
+
if (rawProps.align != null) wrapProps.style["--cms-parallax-align"] = uiStyleValue(rawProps.align);
|
|
1315
|
+
if (rawProps.color != null) wrapProps.style["--cms-parallax-color"] = uiStyleValue(rawProps.color, mapColorValue);
|
|
1316
|
+
if (rawProps.overlay != null) wrapProps.style["--cms-parallax-overlay"] = uiStyleValue(rawProps.overlay);
|
|
1317
|
+
if (rawProps.bgPosition != null) wrapProps.style["--cms-parallax-position"] = uiStyleValue(rawProps.bgPosition);
|
|
1318
|
+
if (rawProps.bgSize != null) wrapProps.style["--cms-parallax-size"] = uiStyleValue(rawProps.bgSize);
|
|
1319
|
+
if (rawProps.bgRepeat != null) wrapProps.style["--cms-parallax-repeat"] = uiStyleValue(rawProps.bgRepeat);
|
|
1320
|
+
wrapProps.style["--cms-parallax-image"] = (uiIsReactive(rawProps.background) || uiIsReactive(rawProps.image) || uiIsReactive(rawProps.src))
|
|
1321
|
+
? () => getBackgroundImage()
|
|
1322
|
+
: getBackgroundImage();
|
|
1323
|
+
|
|
1324
|
+
const bg = _.div(
|
|
1325
|
+
{ class: uiClass(["cms-parallax-bg", rawProps.bgClass]) },
|
|
1326
|
+
backgroundNodes.length
|
|
1327
|
+
? _.div({ class: uiClass(["cms-parallax-bg-content", rawProps.backgroundContentClass]) }, ...backgroundNodes)
|
|
1328
|
+
: null
|
|
1329
|
+
);
|
|
1330
|
+
const content = _.div(
|
|
1331
|
+
{ class: uiClass(["cms-parallax-content", rawProps.contentClass, rawProps.innerClass]) },
|
|
1332
|
+
generatedBadge,
|
|
1333
|
+
generatedHeader,
|
|
1334
|
+
generatedMedia,
|
|
1335
|
+
generatedBody,
|
|
1336
|
+
generatedFooter
|
|
1337
|
+
);
|
|
1338
|
+
|
|
1339
|
+
const wrap = _.div(wrapProps, bg, content);
|
|
1340
|
+
|
|
1341
|
+
let disposed = false;
|
|
1342
|
+
let ticking = false;
|
|
1343
|
+
let resizeObserver = null;
|
|
1344
|
+
|
|
1345
|
+
const cleanup = () => {
|
|
1346
|
+
if (disposed) return;
|
|
1347
|
+
disposed = true;
|
|
1348
|
+
if (typeof window !== "undefined") {
|
|
1349
|
+
window.removeEventListener("scroll", scheduleUpdate);
|
|
1350
|
+
window.removeEventListener("resize", scheduleUpdate);
|
|
1351
|
+
}
|
|
1352
|
+
resizeObserver?.disconnect?.();
|
|
1353
|
+
resizeObserver = null;
|
|
1354
|
+
};
|
|
1355
|
+
|
|
1356
|
+
const update = () => {
|
|
1357
|
+
if (disposed) return;
|
|
1358
|
+
if (!wrap.isConnected) {
|
|
1359
|
+
cleanup();
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
if (uiUnwrap(rawProps.disabled)) {
|
|
1363
|
+
bg.style.transform = "translate3d(0, 0, 0)";
|
|
1364
|
+
wrap.style.setProperty("--cms-parallax-offset", "0px");
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
const rect = wrap.getBoundingClientRect();
|
|
1368
|
+
const speed = getNumericValue(rawProps.speed, 0.18);
|
|
1369
|
+
const maxOffset = Math.abs(getNumericValue(rawProps.maxOffset, 96));
|
|
1370
|
+
const explicitStartTop = uiUnwrap(rawProps.startTop);
|
|
1371
|
+
const rawOffset = explicitStartTop != null && explicitStartTop !== ""
|
|
1372
|
+
? (rect.top - getNumericValue(rawProps.startTop, 0)) * speed
|
|
1373
|
+
: (((typeof window !== "undefined" ? window.innerHeight || document.documentElement.clientHeight || 0 : 0) / 2) - (rect.top + (rect.height / 2))) * speed;
|
|
1374
|
+
const offset = maxOffset > 0 ? Math.max(-maxOffset, Math.min(maxOffset, rawOffset)) : rawOffset;
|
|
1375
|
+
const cssOffset = `${offset.toFixed(2)}px`;
|
|
1376
|
+
bg.style.transform = `translate3d(0, ${cssOffset}, 0)`;
|
|
1377
|
+
wrap.style.setProperty("--cms-parallax-offset", cssOffset);
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
function scheduleUpdate() {
|
|
1381
|
+
if (disposed || ticking) return;
|
|
1382
|
+
ticking = true;
|
|
1383
|
+
requestAnimationFrame(() => {
|
|
1384
|
+
ticking = false;
|
|
1385
|
+
update();
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if (typeof window !== "undefined") {
|
|
1390
|
+
window.addEventListener("scroll", scheduleUpdate, { passive: true });
|
|
1391
|
+
window.addEventListener("resize", scheduleUpdate);
|
|
1392
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1393
|
+
resizeObserver = new ResizeObserver(() => { scheduleUpdate(); });
|
|
1394
|
+
resizeObserver.observe(wrap);
|
|
1395
|
+
}
|
|
1396
|
+
requestAnimationFrame(() => { scheduleUpdate(); });
|
|
1397
|
+
setTimeout(() => { scheduleUpdate(); }, 90);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
wrap.refresh = () => {
|
|
1401
|
+
scheduleUpdate();
|
|
1402
|
+
return wrap;
|
|
1403
|
+
};
|
|
1404
|
+
wrap.update = wrap.refresh;
|
|
1405
|
+
wrap.destroy = () => {
|
|
1406
|
+
cleanup();
|
|
1407
|
+
wrap.remove();
|
|
1408
|
+
return null;
|
|
1409
|
+
};
|
|
1410
|
+
wrap._dispose = cleanup;
|
|
1411
|
+
|
|
1412
|
+
setPropertyProps(wrap, rawProps);
|
|
1413
|
+
return wrap;
|
|
1414
|
+
};
|
|
1415
|
+
if (JSswift.isDev?.()) {
|
|
1416
|
+
UI.meta = UI.meta || {};
|
|
1417
|
+
UI.meta.Parallax = {
|
|
1418
|
+
signature: "UI.Parallax(...children) | UI.Parallax(props, ...children)",
|
|
1419
|
+
props: {
|
|
1420
|
+
src: "string",
|
|
1421
|
+
image: "string",
|
|
1422
|
+
background: "string",
|
|
1423
|
+
backgroundContent: "Node|Function|Array",
|
|
1424
|
+
height: "string|number",
|
|
1425
|
+
minHeight: "string|number",
|
|
1426
|
+
speed: "number",
|
|
1427
|
+
maxOffset: "number",
|
|
1428
|
+
startTop: "number",
|
|
1429
|
+
state: "success|warning|danger|info|primary|secondary|dark|light|string",
|
|
1430
|
+
overlay: "string",
|
|
1431
|
+
color: "string",
|
|
1432
|
+
bgPosition: "string",
|
|
1433
|
+
bgSize: "string",
|
|
1434
|
+
bgRepeat: "string",
|
|
1435
|
+
padding: "string|number",
|
|
1436
|
+
gap: "string|number",
|
|
1437
|
+
justify: "string",
|
|
1438
|
+
align: "string",
|
|
1439
|
+
contentMaxWidth: "string|number",
|
|
1440
|
+
disabled: "boolean",
|
|
1441
|
+
badge: "Node|Function|Array",
|
|
1442
|
+
eyebrow: "String|Node|Function|Array",
|
|
1443
|
+
title: "String|Node|Function|Array",
|
|
1444
|
+
subtitle: "String|Node|Function|Array",
|
|
1445
|
+
header: "Node|Function|Array",
|
|
1446
|
+
aside: "Node|Function|Array",
|
|
1447
|
+
media: "Node|Function|Array",
|
|
1448
|
+
content: "Node|Function|Array",
|
|
1449
|
+
footer: "Node|Function|Array",
|
|
1450
|
+
actions: "Node|Function|Array",
|
|
1451
|
+
bgClass: "string",
|
|
1452
|
+
badgeClass: "string",
|
|
1453
|
+
headerClass: "string",
|
|
1454
|
+
bodyClass: "string",
|
|
1455
|
+
footerClass: "string",
|
|
1456
|
+
contentClass: "string",
|
|
1457
|
+
slots: "{ background?, badge?, eyebrow?, title?, subtitle?, header?, aside?, media?, content?, footer?, actions?, default? }",
|
|
1458
|
+
class: "string",
|
|
1459
|
+
style: "object"
|
|
1460
|
+
},
|
|
1461
|
+
slots: {
|
|
1462
|
+
background: "Decorative content inside the background layer",
|
|
1463
|
+
badge: "Meta badge/chip above the main content",
|
|
1464
|
+
eyebrow: "Eyebrow/kicker",
|
|
1465
|
+
title: "Main title",
|
|
1466
|
+
subtitle: "Subtitle or supporting text",
|
|
1467
|
+
header: "Additional header content",
|
|
1468
|
+
aside: "Header side area",
|
|
1469
|
+
media: "Media content or foreground card",
|
|
1470
|
+
content: "Main body",
|
|
1471
|
+
footer: "Informational footer",
|
|
1472
|
+
actions: "Actions area",
|
|
1473
|
+
default: "Fallback body content"
|
|
1474
|
+
},
|
|
1475
|
+
returns: "HTMLDivElement with refresh/update/destroy methods",
|
|
1476
|
+
description: "Standardized parallax hero/section with structured header, body, actions, slots, and minimal refresh API."
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
// Example:
|
|
1481
|
+
// _.Parallax({ src: "/assets/hero.jpg", height: "280px", speed: 0.2 }, _.h2("Hello"));
|
|
1482
|
+
|
|
1483
|
+
// -------------------------------
|