jsswift 2.0.1 → 2.1.0

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.
@@ -0,0 +1,809 @@
1
+ const UI_SHADOW_TOKENS = new Set(["none", "sm", "md", "lg"]);
2
+ const sizeMap = {
3
+ xxs: { font: "9px", pad: "2px 5px" },
4
+ xs: { font: "10px", pad: "2px 6px" },
5
+ sm: { font: "11px", pad: "2px 7px" },
6
+ md: { font: "12px", pad: "2px 8px" },
7
+ lg: { font: "13px", pad: "3px 10px" },
8
+ xl: { font: "14px", pad: "4px 12px" },
9
+ xxl: { font: "15px", pad: "5px 14px" },
10
+ xxxl: { font: "16px", pad: "6px 16px" }
11
+ };
12
+
13
+ // normalize color is RGB | RGBA | HEX | color name
14
+ const isTokenCSS = (value) => {
15
+ // se la parola contiene un trattino - è un token
16
+ if (value.indexOf("-") > -1) return true;
17
+ }
18
+ const normalizeState = (value) => {
19
+ if (!value) return "";
20
+ const key = String(value).toLowerCase();
21
+ return UI_STATE_ALIASES[key] || "";
22
+ };
23
+
24
+ const normalizeRadius = (value) => {
25
+ if (value == null) return null;
26
+ const key = String(value).toLowerCase();
27
+ if (key === "full") return "xxxl";
28
+ if (UI_RADIUS_TOKENS.has(key)) return key;
29
+ return null;
30
+ };
31
+
32
+ const normalizeShadow = (value) => {
33
+ if (value === true) return "sm";
34
+ if (value == null || value === false) return null;
35
+ const key = String(value).toLowerCase();
36
+ if (UI_SHADOW_TOKENS.has(key)) return key;
37
+ return null;
38
+ };
39
+
40
+ const isUIPlainObject = (value) => value && typeof value === "object" && !value.nodeType && !Array.isArray(value) && !(value instanceof Function);
41
+ const isListItemNode = (value) => value && value.nodeType === 1 && String(value.tagName || "").toLowerCase() === "li";
42
+ const asNodeArray = (value) => {
43
+ if (value == null || value === false || value === "") return [];
44
+ return Array.isArray(value) ? value : [value];
45
+ };
46
+
47
+ JSswift.isUIPlainObject = isUIPlainObject;
48
+ JSswift.isListItemNode = isListItemNode;
49
+ JSswift.asNodeArray = asNodeArray;
50
+
51
+ const UI_RESPONSIVE_DEVICES = [
52
+ { key: "mobile", aliases: ["mobile", "mobil"], prefix: "jss-" },
53
+ { key: "tablet", aliases: ["tablet"], prefix: "jss-tablet-" },
54
+ { key: "pc", aliases: ["pc", "desktop"], prefix: "jss-pc-" }
55
+ ];
56
+ const UI_RESPONSIVE_PROP_KEYS = UI_RESPONSIVE_DEVICES.flatMap((device) => device.aliases);
57
+ const uiResponsiveHasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key);
58
+ const uiResponsivePropsFor = (props = {}, device) => {
59
+ if (!device) return null;
60
+ for (const alias of device.aliases) {
61
+ if (!uiResponsiveHasOwn(props, alias)) continue;
62
+ const value = uiUnwrap(props[alias]);
63
+ if (isUIPlainObject(value)) return value;
64
+ }
65
+ return null;
66
+ };
67
+ const uiResponsiveHasProp = (props = {}, names) => {
68
+ const list = Array.isArray(names) ? names : [names];
69
+ return UI_RESPONSIVE_DEVICES.some((device) => {
70
+ const deviceProps = uiResponsivePropsFor(props, device);
71
+ return !!deviceProps && list.some((name) => uiResponsiveHasOwn(deviceProps, name));
72
+ });
73
+ };
74
+ const uiResponsiveNormalizeToken = (value) => String(value).trim().toLowerCase().replace(/\s+/g, "-");
75
+ const uiResponsiveClassList = (props = {}, rules = []) => {
76
+ const tokens = [];
77
+ for (const device of UI_RESPONSIVE_DEVICES) {
78
+ const deviceProps = uiResponsivePropsFor(props, device);
79
+ if (!deviceProps) continue;
80
+ for (const rule of rules) {
81
+ const propNames = Array.isArray(rule.prop) ? rule.prop : [rule.prop];
82
+ let hasValue = false;
83
+ let value;
84
+ let propName = "";
85
+ for (const name of propNames) {
86
+ if (!uiResponsiveHasOwn(deviceProps, name)) continue;
87
+ value = uiUnwrap(deviceProps[name]);
88
+ propName = name;
89
+ hasValue = true;
90
+ break;
91
+ }
92
+ if (!hasValue || value == null || value === "" || (value === false && !rule.allowFalse)) continue;
93
+ const resolved = typeof rule.map === "function"
94
+ ? rule.map(value, { propName, props: deviceProps, device, rule })
95
+ : `${rule.class}-${uiResponsiveNormalizeToken(value)}`;
96
+ const list = Array.isArray(resolved) ? resolved : [resolved];
97
+ for (const suffix of list) {
98
+ if (!suffix) continue;
99
+ tokens.push(String(suffix).startsWith("jss-") ? suffix : `${device.prefix}${suffix}`);
100
+ }
101
+ }
102
+ }
103
+ return tokens;
104
+ };
105
+ const uiResponsiveHasConfig = (props = {}) => {
106
+ return UI_RESPONSIVE_DEVICES.some((device) => !!uiResponsivePropsFor(props, device));
107
+ };
108
+ const uiResponsiveFirstValue = (props = {}, propNames = []) => {
109
+ for (const propName of propNames) {
110
+ if (!uiResponsiveHasOwn(props, propName)) continue;
111
+ const value = uiUnwrap(props[propName]);
112
+ if (value == null || value === "") continue;
113
+ return value;
114
+ }
115
+ return undefined;
116
+ };
117
+ const uiResponsiveDefaultValue = (value, rule = {}) => {
118
+ if (value == null || value === "" || (value === false && !rule.allowFalse)) return "";
119
+ if (typeof rule.mapValue === "function") return rule.mapValue(value, rule);
120
+ if (typeof value === "number") return rule.number === "raw" ? String(value) : `${value}px`;
121
+ if (rule.token && typeof value === "string" && JSswift.uiSizes?.includes(value)) {
122
+ return `var(--jss-${rule.token}-${value})`;
123
+ }
124
+ return String(value);
125
+ };
126
+ const uiResponsiveSetStyle = (style, name, value) => {
127
+ if (!style || value == null || value === "") return;
128
+ if (typeof style.setProperty === "function") style.setProperty(name, value);
129
+ else style[name] = value;
130
+ };
131
+ const uiResponsiveVarClass = (deviceKey, varName) => {
132
+ return deviceKey === "mobile" ? `jss-rsp-${varName}` : `jss-rsp-${deviceKey}-${varName}`;
133
+ };
134
+ const uiResponsiveAddClass = (target, className) => {
135
+ if (!target || !className) return;
136
+ if (target.classList?.add) {
137
+ target.classList.add(className);
138
+ return;
139
+ }
140
+ target.class = uiClass([target.class, className]);
141
+ };
142
+ const uiApplyResponsiveProps = (target, props = {}, rules = []) => {
143
+ if (!target || !props) return target;
144
+ const configurations = [];
145
+ for (const device of UI_RESPONSIVE_DEVICES) {
146
+ const deviceProps = uiResponsivePropsFor(props, device);
147
+ if (deviceProps) configurations.push([device.key, deviceProps]);
148
+ }
149
+ if (!configurations.length) return target;
150
+ const style = target.style || {};
151
+ let hasResponsiveVars = false;
152
+
153
+ for (const rule of rules) {
154
+ const propNames = Array.isArray(rule.prop) ? rule.prop : [rule.prop];
155
+ const setVar = (deviceKey, sourceProps) => {
156
+ const rawValue = uiResponsiveFirstValue(sourceProps, propNames);
157
+ const value = uiResponsiveDefaultValue(rawValue, rule);
158
+ if (!value) return;
159
+ const varName = rule.var || rule.css;
160
+ const prefix = deviceKey === "mobile" ? "--jss-rsp-" : `--jss-rsp-${deviceKey}-`;
161
+ uiResponsiveSetStyle(style, `${prefix}${varName}`, value);
162
+ uiResponsiveAddClass(target, uiResponsiveVarClass(deviceKey, varName));
163
+ hasResponsiveVars = true;
164
+ };
165
+
166
+ setVar("mobile", props);
167
+ for (const [deviceKey, deviceProps] of configurations) setVar(deviceKey, deviceProps);
168
+ }
169
+
170
+ if (hasResponsiveVars) {
171
+ if (!target.nodeType) target.style = style;
172
+ uiResponsiveAddClass(target, "jss-rsp");
173
+ }
174
+ return target;
175
+ };
176
+
177
+ JSswift.uiResponsiveDevices = UI_RESPONSIVE_DEVICES;
178
+ JSswift.uiResponsiveOmitProps = UI_RESPONSIVE_PROP_KEYS;
179
+ JSswift.uiResponsivePropsFor = uiResponsivePropsFor;
180
+ JSswift.uiResponsiveHasConfig = uiResponsiveHasConfig;
181
+ JSswift.uiResponsiveHasProp = uiResponsiveHasProp;
182
+ JSswift.uiResponsiveClasses = uiResponsiveClassList;
183
+ JSswift.uiApplyResponsiveProps = uiApplyResponsiveProps;
184
+
185
+ const uiOmitBase = JSswift.omit;
186
+ JSswift.omit = (obj, keys = []) => {
187
+ const allKeys = Array.isArray(keys)
188
+ ? keys.concat(UI_RESPONSIVE_PROP_KEYS)
189
+ : UI_RESPONSIVE_PROP_KEYS;
190
+ return uiOmitBase(obj, allKeys);
191
+ };
192
+
193
+ const applyCommonProps = (props = {}) => {
194
+ const classTokens = [];
195
+ const style = {};
196
+
197
+ classTokens.push(
198
+ uiWhen(props.clickable, "jss-clickable"),
199
+ uiWhen(props.dense, "jss-dense"),
200
+ uiWhen(props.flat, "jss-flat"),
201
+ uiWhen(props.border, "jss-border"),
202
+ uiWhen(props.glossy, "jss-glossy"),
203
+ uiWhen(props.glow, "jss-glow"),
204
+ uiWhen(props.glass, "jss-glass"),
205
+ uiWhen(props.shadow, "jss-shadow"),
206
+ uiWhen(props.outline, "jss-outline"),
207
+ uiWhen(props.rounded, "jss-rounded"),
208
+ uiWhen(props.gradient, "jss-gradient"),
209
+ uiWhen(props.textGradient, "jss-text-gradient"),
210
+ uiWhen(props.lightShadow, "jss-light-shadow")
211
+ );
212
+
213
+ if (uiIsReactive(props.color)) {
214
+ classTokens.push(() => {
215
+ const v = uiUnwrap(props.color);
216
+ const state = normalizeState(v);
217
+ return state ? `jss-state-${state}` : "";
218
+ });
219
+ style.backgroundColor = () => {
220
+ const v = uiUnwrap(props.color);
221
+ const state = normalizeState(v);
222
+ if (state) return "";
223
+ if (v && isTokenCSS(String(v))) return `var(--jss-${v})`;
224
+ return v || "";
225
+ };
226
+ } else {
227
+ const state = normalizeState(props.color);
228
+ if (state) {
229
+ classTokens.push(`jss-state-${state}`);
230
+ } else if (props.color && isTokenCSS(props.color)) {
231
+ style.backgroundColor = `var(--jss-${props.color})`;
232
+ } else if (props.color) {
233
+ style.backgroundColor = props.color;
234
+ }
235
+ }
236
+
237
+ // facciamo text color per i token
238
+ if (uiIsReactive(props.textColor)) {
239
+ style.color = () => {
240
+ const v = uiUnwrap(props.textColor);
241
+ if (v && isTokenCSS(String(v))) return `var(--jss-${v})`;
242
+ return v || "";
243
+ };
244
+ } else if (props.textColor && isTokenCSS(props.textColor)) {
245
+ style.color = `var(--jss-${props.textColor})`;
246
+ } else if (props.textColor) {
247
+ style.color = props.textColor;
248
+ }
249
+
250
+ if (uiIsReactive(props.size)) {
251
+ classTokens.push(() => {
252
+ const v = uiUnwrap(props.size);
253
+ if (typeof v === "string" && JSswift.uiSizes?.includes(v)) return `jss-size-${v}`;
254
+ return "";
255
+ });
256
+ } else {
257
+ const size = props.size;
258
+ if (typeof size === "string" && JSswift.uiSizes?.includes(size)) {
259
+ classTokens.push(`jss-size-${size}`);
260
+ }
261
+ }
262
+
263
+ if (uiIsReactive(props.shadow)) {
264
+ classTokens.push(() => {
265
+ const v = uiUnwrap(props.shadow);
266
+ const shadow = normalizeShadow(v);
267
+ return shadow ? `jss-shadow-${shadow}` : "";
268
+ });
269
+ style.boxShadow = () => {
270
+ const v = uiUnwrap(props.shadow);
271
+ const shadow = normalizeShadow(v);
272
+ if (shadow || v == null || v === false) return "";
273
+ return typeof v === "string" ? v : "";
274
+ };
275
+ } else {
276
+ const shadow = normalizeShadow(props.shadow);
277
+ if (shadow) classTokens.push(`jss-shadow-${shadow}`);
278
+ else if (props.shadow && typeof props.shadow === "string") {
279
+ style.boxShadow = props.shadow;
280
+ }
281
+ }
282
+
283
+ if (classTokens.length) {
284
+ props.class = uiClass([props.class, ...classTokens]);
285
+ }
286
+ if (Object.keys(style).length) {
287
+ props.style = { ...style, ...(props.style || {}) };
288
+ }
289
+ };
290
+
291
+ const normalizeArgsBase = JSswift.uiNormalizeArgs;
292
+ JSswift.uiNormalizeArgs = function (args) {
293
+ const out = normalizeArgsBase(args);
294
+ const props = out.props || {};
295
+ if (props && Object.prototype.hasOwnProperty.call(props, "children")) {
296
+ const propChildren = props.children;
297
+ if (!out.children || out.children.length === 0) {
298
+ out.children = Array.isArray(propChildren) ? propChildren : [propChildren];
299
+ }
300
+ delete props.children;
301
+ }
302
+ applyCommonProps(props);
303
+ return out;
304
+ };
305
+
306
+ // --------------------------------
307
+ // 2) UI PRIMITIVES (layout + atoms)
308
+ // --------------------------------
309
+ const UI = window._;
310
+
311
+ /*
312
+ UI.Row
313
+ UI.Col
314
+ UI.Spacer
315
+ UI.Container
316
+ UI.Card
317
+ UI.Btn
318
+ UI.Input
319
+ UI.Select
320
+ UI.Layout
321
+ UI.Footer
322
+ UI.Toolbar
323
+ UI.Grid
324
+ UI.GridCol
325
+ UI.Icon
326
+ UI.Badge
327
+ UI.Avatar
328
+ UI.Chip
329
+ UI.Tooltip
330
+ UI.List
331
+ UI.Item
332
+ UI.Separator
333
+ UI.Checkbox
334
+ UI.Radio
335
+ UI.Toggle
336
+ UI.Slider
337
+ UI.Rating
338
+ UI.Datepicker
339
+ UI.Calendar
340
+ UI.Date
341
+ UI.Time
342
+ UI.Tabs
343
+ UI.RouteTab
344
+ UI.Breadcrumbs
345
+ UI.Pagination
346
+ UI.Spinner
347
+ UI.Progress
348
+ UI.LoadingBar
349
+ UI.Banner
350
+ UI.Header
351
+ UI.Drawer
352
+ UI.Page
353
+ UI.AppShell
354
+ UI.Parallax
355
+ */
356
+
357
+ function flattenSlotValue(value) {
358
+ if (!value) return null;
359
+ if (!Array.isArray(value)) return value;
360
+ const out = [];
361
+ const stack = value.slice();
362
+ while (stack.length) {
363
+ const item = stack.shift();
364
+ if (!item) continue;
365
+ if (Array.isArray(item)) stack.unshift(...item);
366
+ else out.push(item);
367
+ }
368
+ return out.length ? out : null;
369
+ }
370
+ const STYLE_PROP_TOKEN_MAP = {
371
+ padding: "p",
372
+ paddingTop: "p",
373
+ paddingRight: "p",
374
+ paddingBottom: "p",
375
+ paddingLeft: "p",
376
+ paddingInline: "p",
377
+ paddingInlineStart: "p",
378
+ paddingInlineEnd: "p",
379
+ paddingBlock: "p",
380
+ paddingBlockStart: "p",
381
+ paddingBlockEnd: "p",
382
+ margin: "m",
383
+ marginTop: "m",
384
+ marginRight: "m",
385
+ marginBottom: "m",
386
+ marginLeft: "m",
387
+ marginInline: "m",
388
+ marginInlineStart: "m",
389
+ marginInlineEnd: "m",
390
+ marginBlock: "m",
391
+ marginBlockStart: "m",
392
+ marginBlockEnd: "m",
393
+ width: "w",
394
+ minWidth: "w",
395
+ maxWidth: "w",
396
+ height: "h",
397
+ minHeight: "h",
398
+ maxHeight: "h"
399
+ };
400
+ const uiResponsiveWrapValue = (value) => {
401
+ if (value === true) return "wrap";
402
+ if (value === false) return "nowrap";
403
+ return String(value);
404
+ };
405
+ const uiResponsiveGridColsValue = (value) => {
406
+ if (typeof value === "number") return `repeat(${Math.max(1, Math.round(value))}, minmax(0, 1fr))`;
407
+ return String(value);
408
+ };
409
+ const uiResponsiveGridFlowValue = (value) => {
410
+ const flow = String(value).trim().toLowerCase().replace("-", " ");
411
+ return flow;
412
+ };
413
+ const UI_RESPONSIVE_STYLE_RULES = [
414
+ { prop: "display", css: "display", var: "display" },
415
+ { prop: "direction", css: "flex-direction", var: "flex-direction" },
416
+ { prop: "wrap", css: "flex-wrap", var: "flex-wrap", allowFalse: true, mapValue: uiResponsiveWrapValue },
417
+ { prop: "align", css: "align-items", var: "align-items" },
418
+ { prop: "justify", css: "justify-content", var: "justify-content" },
419
+ { prop: "self", css: "align-self", var: "align-self" },
420
+ { prop: "justifySelf", css: "justify-self", var: "justify-self" },
421
+ { prop: "place", css: "place-items", var: "place-items" },
422
+ { prop: "placeSelf", css: "place-self", var: "place-self" },
423
+ { prop: ["cols", "columns"], css: "grid-template-columns", var: "grid-template-columns", mapValue: uiResponsiveGridColsValue },
424
+ { prop: "rows", css: "grid-template-rows", var: "grid-template-rows", mapValue: uiResponsiveGridColsValue },
425
+ { prop: "flow", css: "grid-auto-flow", var: "grid-auto-flow", mapValue: uiResponsiveGridFlowValue },
426
+ { prop: "autoRows", css: "grid-auto-rows", var: "grid-auto-rows" },
427
+ { prop: ["span", "gridColumn"], css: "grid-column", var: "grid-column", number: "raw" },
428
+ { prop: ["row", "rowSpan"], css: "grid-row", var: "grid-row", number: "raw" },
429
+ { prop: "area", css: "grid-area", var: "grid-area" },
430
+ { prop: "gap", css: "gap", var: "gap", token: "gap" },
431
+ { prop: "rowGap", css: "row-gap", var: "row-gap", token: "gap" },
432
+ { prop: ["columnGap", "colGap"], css: "column-gap", var: "column-gap", token: "gap" },
433
+ { prop: "width", css: "width", var: "width", token: "w" },
434
+ { prop: "minWidth", css: "min-width", var: "min-width", token: "min-w" },
435
+ { prop: "maxWidth", css: "max-width", var: "max-width", token: "max-w" },
436
+ { prop: "height", css: "height", var: "height", token: "h" },
437
+ { prop: "minHeight", css: "min-height", var: "min-height", token: "min-h" },
438
+ { prop: "maxHeight", css: "max-height", var: "max-height", token: "max-h" },
439
+ { prop: "padding", css: "padding", var: "padding", token: "p" },
440
+ { prop: "paddingLeft", css: "padding-left", var: "padding-left", token: "p" },
441
+ { prop: "paddingRight", css: "padding-right", var: "padding-right", token: "p" },
442
+ { prop: "paddingTop", css: "padding-top", var: "padding-top", token: "p" },
443
+ { prop: "paddingBottom", css: "padding-bottom", var: "padding-bottom", token: "p" },
444
+ { prop: "margin", css: "margin", var: "margin", token: "m" },
445
+ { prop: "marginLeft", css: "margin-left", var: "margin-left", token: "m" },
446
+ { prop: "marginRight", css: "margin-right", var: "margin-right", token: "m" },
447
+ { prop: "marginTop", css: "margin-top", var: "margin-top", token: "m" },
448
+ { prop: "marginBottom", css: "margin-bottom", var: "margin-bottom", token: "m" },
449
+ { prop: "fontSize", css: "font-size", var: "font-size", token: "f" },
450
+ { prop: "fontWeight", css: "font-weight", var: "font-weight", token: "fw", number: "raw" },
451
+ { prop: "lineHeight", css: "line-height", var: "line-height", token: "lh", number: "raw" },
452
+ { prop: "letterSpacing", css: "letter-spacing", var: "letter-spacing", token: "ls" },
453
+ { prop: ["borderRadius", "radius"], css: "border-radius", var: "border-radius", token: "r" },
454
+ { prop: "overflow", css: "overflow", var: "overflow" },
455
+ { prop: "order", css: "order", var: "order", number: "raw" },
456
+ ];
457
+ const UI_RESPONSIVE_COMMON_STYLE_RULES = UI_RESPONSIVE_STYLE_RULES.filter((rule) => {
458
+ const props = Array.isArray(rule.prop) ? rule.prop : [rule.prop];
459
+ return props.some((prop) => Object.prototype.hasOwnProperty.call(STYLE_PROP_TOKEN_MAP, prop))
460
+ || props.includes("fontSize")
461
+ || props.includes("fontWeight")
462
+ || props.includes("lineHeight")
463
+ || props.includes("letterSpacing")
464
+ || props.includes("borderRadius")
465
+ || props.includes("radius");
466
+ });
467
+ JSswift.uiResponsiveStyleRules = UI_RESPONSIVE_STYLE_RULES;
468
+ JSswift.uiResponsiveCommonStyleRules = UI_RESPONSIVE_COMMON_STYLE_RULES;
469
+ const camelToCssProperty = (name) => name.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
470
+ const applyStyleProp = (obj, value, name, tokenName) => {
471
+ if (value == null || value === false || value === "") return;
472
+ obj.style.setProperty(camelToCssProperty(name), unitCover(value, tokenName));
473
+ };
474
+ function setPropertyProps(obj, props) {
475
+ if (props.size) {
476
+ if (typeof props.size === "number") {
477
+ obj.style.setProperty("--jss-font-size", `${props.size}px`);
478
+ } else if (JSswift.uiSizes.includes(props.size)) {
479
+ obj.style.setProperty("--jss-font-size", `var(--jss-font-size-${props.size})`);
480
+ }
481
+ }
482
+
483
+ Object.entries(STYLE_PROP_TOKEN_MAP).forEach(([name, tokenName]) => {
484
+ if (uiResponsiveHasProp(props, name)) return;
485
+ applyStyleProp(obj, props[name], name, tokenName);
486
+ });
487
+ uiApplyResponsiveProps(obj, props, UI_RESPONSIVE_COMMON_STYLE_RULES);
488
+
489
+ // gradient
490
+ if (props.gradient) {
491
+ if (typeof props.gradient == "number") {
492
+ obj.style.setProperty("--set-gradient-deg", `${props.gradient}deg`);
493
+ }
494
+ }
495
+ // radius
496
+ if (props.radius) {
497
+ if (typeof props.radius == "number") {
498
+ obj.style.setProperty("--set-border-radius", `${props.radius}px`);
499
+ } else if (JSswift.uiSizes.includes(props.radius)) {
500
+ obj.style.setProperty("--set-border-radius", `var(--jss-r-${props.radius})`);
501
+ } else {
502
+ obj.style.setProperty("--set-border-radius", `${props.radius}`);
503
+ }
504
+ }
505
+
506
+ }
507
+
508
+ function uiOptionNode(props = {}, ...children) {
509
+ const el = document.createElement("option");
510
+ if (props && typeof props === "object") {
511
+ if (props.value != null) el.value = String(props.value);
512
+ if (props.label != null) el.label = String(props.label);
513
+ if (props.disabled != null) el.disabled = !!props.disabled;
514
+ if (props.selected != null) el.selected = !!props.selected;
515
+ if (props.class != null) el.className = String(props.class);
516
+ }
517
+ const content = children.flat ? children.flat(Infinity) : children;
518
+ if (content.length) {
519
+ content.forEach((child) => {
520
+ if (child == null || child === false) return;
521
+ if (child instanceof Node) el.appendChild(child);
522
+ else el.appendChild(document.createTextNode(String(child)));
523
+ });
524
+ } else if (props && props.text != null) {
525
+ el.textContent = String(props.text);
526
+ }
527
+ return el;
528
+ }
529
+
530
+ JSswift.ui.getSlot = (slots, name) => {
531
+ if (!slots) return null;
532
+ return Object.prototype.hasOwnProperty.call(slots, name) ? slots[name] : null;
533
+ };
534
+
535
+ JSswift.ui.renderSlot = (slots, name, ctx, fallback) => {
536
+ const slot = JSswift.ui.getSlot(slots, name);
537
+ const hasSlot = slot !== null && slot !== undefined;
538
+ const raw = hasSlot ? (typeof slot === "function" ? slot(ctx || {}) : slot) : fallback;
539
+ if (raw == null) return null;
540
+ return flattenSlotValue(JSswift.ui.slot(raw));
541
+ };
542
+
543
+ const renderSlotToArray = (slots, name, ctx, fallback) => {
544
+ const v = JSswift.ui.renderSlot(slots, name, ctx, fallback);
545
+ if (!v) return [];
546
+ return Array.isArray(v) ? v : [v];
547
+ };
548
+
549
+ const UI_SHORTCODE_REGISTRY = new Set();
550
+ let uiShortcodeListenerAttached = false;
551
+ const UI_SHORTCODE_MODIFIERS = new Set(["ctrl", "alt", "shift", "meta"]);
552
+ const UI_SHORTCODE_ALIASES = {
553
+ control: "ctrl",
554
+ ctl: "ctrl",
555
+ option: "alt",
556
+ opt: "alt",
557
+ command: "meta",
558
+ cmd: "meta",
559
+ "⌘": "meta",
560
+ "⌃": "ctrl",
561
+ "⌥": "alt",
562
+ "⇧": "shift",
563
+ esc: "escape",
564
+ return: "enter",
565
+ plus: "+",
566
+ spacebar: "space",
567
+ up: "arrowup",
568
+ down: "arrowdown",
569
+ left: "arrowleft",
570
+ right: "arrowright",
571
+ del: "delete"
572
+ };
573
+ const UI_SHORTCODE_DISPLAY = {
574
+ ctrl: "Ctrl",
575
+ alt: "Alt",
576
+ shift: "Shift",
577
+ meta: "Meta",
578
+ escape: "Esc",
579
+ enter: "Enter",
580
+ tab: "Tab",
581
+ space: "Space",
582
+ backspace: "Backspace",
583
+ delete: "Del",
584
+ arrowup: "Up",
585
+ arrowdown: "Down",
586
+ arrowleft: "Left",
587
+ arrowright: "Right",
588
+ pageup: "PgUp",
589
+ pagedown: "PgDn"
590
+ };
591
+
592
+ const uiIsApplePlatform = () => {
593
+ if (typeof navigator === "undefined") return false;
594
+ const platform = String(navigator.platform || navigator.userAgent || "");
595
+ return /(mac|iphone|ipad|ipod)/i.test(platform);
596
+ };
597
+
598
+ const uiNormalizeShortcodeToken = (token) => {
599
+ if (token == null) return "";
600
+ const raw = String(token).trim().toLowerCase();
601
+ if (!raw) return "";
602
+ return UI_SHORTCODE_ALIASES[raw] || raw;
603
+ };
604
+
605
+ const uiParseShortcode = (rawValue) => {
606
+ if (rawValue == null || rawValue === false) return null;
607
+ const source = String(rawValue).trim();
608
+ if (!source) return null;
609
+
610
+ const parts = source
611
+ .split("+")
612
+ .map(uiNormalizeShortcodeToken)
613
+ .filter(Boolean);
614
+
615
+ if (!parts.length) return null;
616
+
617
+ const spec = {
618
+ raw: source,
619
+ key: "",
620
+ ctrl: false,
621
+ alt: false,
622
+ shift: false,
623
+ meta: false
624
+ };
625
+
626
+ parts.forEach((part) => {
627
+ if (part === "mod") {
628
+ if (uiIsApplePlatform()) spec.meta = true;
629
+ else spec.ctrl = true;
630
+ return;
631
+ }
632
+ if (UI_SHORTCODE_MODIFIERS.has(part)) {
633
+ spec[part] = true;
634
+ return;
635
+ }
636
+ spec.key = part;
637
+ });
638
+
639
+ if (!spec.key) return null;
640
+ return spec;
641
+ };
642
+
643
+ const uiNormalizeShortcodeList = (rawValue) => {
644
+ if (rawValue == null || rawValue === false) return [];
645
+ const list = Array.isArray(rawValue) ? rawValue : [rawValue];
646
+ return list.map((item) => {
647
+ if (item == null || item === false) return null;
648
+ if (typeof item === "object" && !Array.isArray(item)) {
649
+ const parsed = uiParseShortcode(item.shortcode ?? item.shortcut ?? item.keys ?? item.key);
650
+ return parsed ? { ...item, ...parsed } : null;
651
+ }
652
+ const parsed = uiParseShortcode(item);
653
+ return parsed ? { ...parsed } : null;
654
+ }).filter(Boolean);
655
+ };
656
+
657
+ const uiResolveShortcodeList = (props = {}) =>
658
+ uiNormalizeShortcodeList(props.shortcode ?? props.shortcut ?? props.hotkey ?? props.keys);
659
+
660
+ const uiIsShortcodeVisible = (props = {}) =>
661
+ props.showShortcode === true || props.showShortcut === true;
662
+
663
+ const uiFormatShortcodeKey = (key) => {
664
+ const normalized = uiNormalizeShortcodeToken(key);
665
+ if (!normalized) return "";
666
+ const display = { ...UI_SHORTCODE_DISPLAY };
667
+ if (uiIsApplePlatform()) {
668
+ display.alt = "Option";
669
+ display.meta = "Cmd";
670
+ }
671
+ if (display[normalized]) return display[normalized];
672
+ if (normalized.length === 1) return normalized.toUpperCase();
673
+ return normalized.replace(/(^|[-_ ])(\w)/g, (_, prefix, char) => `${prefix}${char.toUpperCase()}`);
674
+ };
675
+
676
+ const uiFormatShortcode = (spec) => {
677
+ if (!spec) return "";
678
+ const parts = [];
679
+ if (spec.ctrl) parts.push(uiFormatShortcodeKey("ctrl"));
680
+ if (spec.alt) parts.push(uiFormatShortcodeKey("alt"));
681
+ if (spec.shift) parts.push(uiFormatShortcodeKey("shift"));
682
+ if (spec.meta) parts.push(uiFormatShortcodeKey("meta"));
683
+ parts.push(uiFormatShortcodeKey(spec.key));
684
+ return parts.filter(Boolean).join("+");
685
+ };
686
+
687
+ const uiFormatShortcodeList = (specs) =>
688
+ (Array.isArray(specs) ? specs : []).map(uiFormatShortcode).filter(Boolean).join(" / ");
689
+
690
+ const uiCreateShortcodeHint = (props = {}, options = {}) => {
691
+ const specs = uiResolveShortcodeList(props);
692
+ if (!uiIsShortcodeVisible(props) || !specs.length || typeof document === "undefined") return null;
693
+ const node = document.createElement("span");
694
+ node.className = options.className || "jss-shortcode";
695
+ node.setAttribute("aria-hidden", "true");
696
+ node.textContent = uiFormatShortcodeList(specs);
697
+ return node;
698
+ };
699
+
700
+ const uiIsEditableEventTarget = (target) => {
701
+ const el = target?.nodeType === 1 ? target : target?.parentElement;
702
+ if (!el || typeof el.closest !== "function") return false;
703
+ if (el.isContentEditable) return true;
704
+ const editable = el.closest("input, textarea, select, [contenteditable], [contenteditable='true'], [contenteditable='plaintext-only']");
705
+ if (!editable) return false;
706
+ if ("disabled" in editable && editable.disabled) return false;
707
+ return true;
708
+ };
709
+
710
+ const uiEventMatchesShortcode = (event, spec) => {
711
+ if (!event || !spec) return false;
712
+ if (!!event.ctrlKey !== !!spec.ctrl) return false;
713
+ if (!!event.altKey !== !!spec.alt) return false;
714
+ if (!!event.shiftKey !== !!spec.shift) return false;
715
+ if (!!event.metaKey !== !!spec.meta) return false;
716
+ return uiNormalizeShortcodeToken(event.key) === spec.key;
717
+ };
718
+
719
+ const uiDetachShortcodeListenerIfEmpty = () => {
720
+ if (UI_SHORTCODE_REGISTRY.size || !uiShortcodeListenerAttached || typeof document === "undefined") return;
721
+ document.removeEventListener("keydown", uiHandleShortcodeKeydown, true);
722
+ uiShortcodeListenerAttached = false;
723
+ };
724
+
725
+ function uiHandleShortcodeKeydown(event) {
726
+ if (!UI_SHORTCODE_REGISTRY.size || !event || event.defaultPrevented || event.isComposing) return;
727
+
728
+ const isEditable = uiIsEditableEventTarget(event.target);
729
+ const bindings = Array.from(UI_SHORTCODE_REGISTRY);
730
+
731
+ for (let index = bindings.length - 1; index >= 0; index -= 1) {
732
+ const binding = bindings[index];
733
+ const anchor = binding?.anchor;
734
+ if (!anchor || !anchor.isConnected) {
735
+ UI_SHORTCODE_REGISTRY.delete(binding);
736
+ continue;
737
+ }
738
+ if (binding.isEnabled?.() === false) continue;
739
+
740
+ const matched = binding.specs.find((spec) => {
741
+ const allowInEditable = spec.allowInEditable ?? binding.allowInEditable ?? false;
742
+ if (isEditable && !allowInEditable) return false;
743
+ return uiEventMatchesShortcode(event, spec);
744
+ });
745
+
746
+ if (!matched) continue;
747
+
748
+ const shouldPreventDefault = matched.preventDefault ?? binding.preventDefault ?? true;
749
+ const shouldStopPropagation = matched.stopPropagation ?? binding.stopPropagation ?? true;
750
+ if (shouldPreventDefault) event.preventDefault();
751
+ if (shouldStopPropagation) event.stopPropagation();
752
+
753
+ const result = binding.action?.(event, matched, anchor);
754
+ if (result !== false) break;
755
+ }
756
+
757
+ uiDetachShortcodeListenerIfEmpty();
758
+ }
759
+
760
+ const uiRegisterShortcode = (anchor, props = {}, options = {}) => {
761
+ const specs = uiResolveShortcodeList(props);
762
+ if (!anchor || !specs.length || typeof document === "undefined") return null;
763
+
764
+ const binding = {
765
+ anchor,
766
+ specs,
767
+ action: typeof options.action === "function" ? options.action : () => anchor.click?.(),
768
+ isEnabled: typeof options.isEnabled === "function" ? options.isEnabled : null,
769
+ allowInEditable: !!options.allowInEditable,
770
+ preventDefault: options.preventDefault ?? true,
771
+ stopPropagation: options.stopPropagation ?? true
772
+ };
773
+
774
+ UI_SHORTCODE_REGISTRY.add(binding);
775
+
776
+ if (!uiShortcodeListenerAttached) {
777
+ document.addEventListener("keydown", uiHandleShortcodeKeydown, true);
778
+ uiShortcodeListenerAttached = true;
779
+ }
780
+
781
+ const dispose = () => {
782
+ UI_SHORTCODE_REGISTRY.delete(binding);
783
+ uiDetachShortcodeListenerIfEmpty();
784
+ };
785
+
786
+ JSswift._registerCleanup(anchor, dispose);
787
+ return dispose;
788
+ };
789
+
790
+ const uiFocusShortcutTarget = (target, options = {}) => {
791
+ if (!target || typeof target.focus !== "function") return false;
792
+ try {
793
+ target.focus({ preventScroll: options.preventScroll ?? true });
794
+ } catch {
795
+ target.focus();
796
+ }
797
+ if (options.selectText && typeof target.select === "function") {
798
+ try { target.select(); } catch { }
799
+ }
800
+ return true;
801
+ };
802
+
803
+ const unitCover = (v, name = 'size') => {
804
+ if (typeof v === "number") return v + "px";
805
+ if (JSswift.uiSizes.includes(v)) return `var(--jss-${name}-${v})`;
806
+ if (typeof v === "string") return v;
807
+ return v;
808
+ };
809
+