@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.
Files changed (55) hide show
  1. package/dist/css/animation.css +121 -0
  2. package/dist/css/base.css +621 -0
  3. package/dist/css/docs.css +216 -0
  4. package/dist/css/min-ui.css +1 -0
  5. package/dist/css/responsive.css +3259 -0
  6. package/dist/css/ui-components.css +8757 -0
  7. package/dist/css/ui.css +12765 -0
  8. package/dist/css/ui.min.css +1 -0
  9. package/dist/fonts/Nunito-Black.ttf +0 -0
  10. package/dist/fonts/Nunito-BlackItalic.ttf +0 -0
  11. package/dist/fonts/Nunito-Bold.ttf +0 -0
  12. package/dist/fonts/Nunito-BoldItalic.ttf +0 -0
  13. package/dist/fonts/Nunito-ExtraBold.ttf +0 -0
  14. package/dist/fonts/Nunito-ExtraBoldItalic.ttf +0 -0
  15. package/dist/fonts/Nunito-ExtraLight.ttf +0 -0
  16. package/dist/fonts/Nunito-ExtraLightItalic.ttf +0 -0
  17. package/dist/fonts/Nunito-Italic.ttf +0 -0
  18. package/dist/fonts/Nunito-Light.ttf +0 -0
  19. package/dist/fonts/Nunito-LightItalic.ttf +0 -0
  20. package/dist/fonts/Nunito-Medium.ttf +0 -0
  21. package/dist/fonts/Nunito-MediumItalic.ttf +0 -0
  22. package/dist/fonts/Nunito-Regular.ttf +0 -0
  23. package/dist/fonts/Nunito-SemiBold.ttf +0 -0
  24. package/dist/fonts/Nunito-SemiBoldItalic.ttf +0 -0
  25. package/dist/img/logo.svg +131 -0
  26. package/dist/img/svg/tabler-icons-sprite.svg +39534 -0
  27. package/dist/min-ui.js +34 -0
  28. package/dist/ui.js +21027 -0
  29. package/dist/ui.min.js +34 -0
  30. package/package.json +40 -0
  31. package/src/00-bootstrap.js +330 -0
  32. package/src/01-foundation-helpers.js +1208 -0
  33. package/src/10-primitives-layout.js +5972 -0
  34. package/src/20-display-content.js +1014 -0
  35. package/src/21-list-content.js +341 -0
  36. package/src/22-banner-content.js +489 -0
  37. package/src/23-tooltip.js +326 -0
  38. package/src/31-form-advanced.js +3149 -0
  39. package/src/40-navigation.js +1454 -0
  40. package/src/41-tab-panel.js +1090 -0
  41. package/src/50-feedback.js +1209 -0
  42. package/src/51-feedback-service.js +166 -0
  43. package/src/60-shell-app.js +1483 -0
  44. package/src/61-form-service.js +450 -0
  45. package/src/70-dialog.js +401 -0
  46. package/src/71-menu-overlays.js +1442 -0
  47. package/src/80-data.js +503 -0
  48. package/src/README.md +159 -0
  49. package/src/css/README.md +17 -0
  50. package/src/css/animation.css +121 -0
  51. package/src/css/base.css +621 -0
  52. package/src/css/docs.css +216 -0
  53. package/src/css/responsive.css +3259 -0
  54. package/src/css/ui-components.css +8757 -0
  55. package/src/modules.json +176 -0
@@ -0,0 +1,1208 @@
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: "cms-" },
53
+ { key: "tablet", aliases: ["tablet"], prefix: "cms-tablet-" },
54
+ { key: "pc", aliases: ["pc", "desktop"], prefix: "cms-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("cms-") ? 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(--cms-${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" ? `cms-rsp-${varName}` : `cms-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 || !uiResponsiveHasConfig(props)) return target;
144
+ const style = target.style || {};
145
+ let hasResponsiveVars = false;
146
+
147
+ for (const rule of rules) {
148
+ const propNames = Array.isArray(rule.prop) ? rule.prop : [rule.prop];
149
+ const setVar = (deviceKey, sourceProps) => {
150
+ const rawValue = uiResponsiveFirstValue(sourceProps, propNames);
151
+ const value = uiResponsiveDefaultValue(rawValue, rule);
152
+ if (!value) return;
153
+ const varName = rule.var || rule.css;
154
+ const prefix = deviceKey === "mobile" ? "--cms-rsp-" : `--cms-rsp-${deviceKey}-`;
155
+ uiResponsiveSetStyle(style, `${prefix}${varName}`, value);
156
+ uiResponsiveAddClass(target, uiResponsiveVarClass(deviceKey, varName));
157
+ hasResponsiveVars = true;
158
+ };
159
+
160
+ setVar("mobile", props);
161
+ for (const device of UI_RESPONSIVE_DEVICES) {
162
+ const deviceProps = uiResponsivePropsFor(props, device);
163
+ if (deviceProps) setVar(device.key, deviceProps);
164
+ }
165
+ }
166
+
167
+ if (hasResponsiveVars) {
168
+ if (!target.nodeType) target.style = style;
169
+ uiResponsiveAddClass(target, "cms-rsp");
170
+ }
171
+ return target;
172
+ };
173
+
174
+ JSswift.uiResponsiveDevices = UI_RESPONSIVE_DEVICES;
175
+ JSswift.uiResponsiveOmitProps = UI_RESPONSIVE_PROP_KEYS;
176
+ JSswift.uiResponsivePropsFor = uiResponsivePropsFor;
177
+ JSswift.uiResponsiveHasConfig = uiResponsiveHasConfig;
178
+ JSswift.uiResponsiveHasProp = uiResponsiveHasProp;
179
+ JSswift.uiResponsiveClasses = uiResponsiveClassList;
180
+ JSswift.uiApplyResponsiveProps = uiApplyResponsiveProps;
181
+
182
+ const uiOmitBase = JSswift.omit;
183
+ JSswift.omit = (obj, keys = []) => {
184
+ const allKeys = Array.isArray(keys)
185
+ ? keys.concat(UI_RESPONSIVE_PROP_KEYS)
186
+ : UI_RESPONSIVE_PROP_KEYS;
187
+ return uiOmitBase(obj, allKeys);
188
+ };
189
+
190
+ const applyCommonProps = (props = {}) => {
191
+ const classTokens = [];
192
+ const style = {};
193
+
194
+ classTokens.push(
195
+ uiWhen(props.clickable, "cms-clickable"),
196
+ uiWhen(props.dense, "cms-dense"),
197
+ uiWhen(props.flat, "cms-flat"),
198
+ uiWhen(props.border, "cms-border"),
199
+ uiWhen(props.glossy, "cms-glossy"),
200
+ uiWhen(props.glow, "cms-glow"),
201
+ uiWhen(props.glass, "cms-glass"),
202
+ uiWhen(props.shadow, "cms-shadow"),
203
+ uiWhen(props.outline, "cms-outline"),
204
+ uiWhen(props.rounded, "cms-rounded"),
205
+ uiWhen(props.gradient, "cms-gradient"),
206
+ uiWhen(props.textGradient, "cms-text-gradient"),
207
+ uiWhen(props.lightShadow, "cms-light-shadow")
208
+ );
209
+
210
+ if (uiIsReactive(props.color)) {
211
+ classTokens.push(() => {
212
+ const v = uiUnwrap(props.color);
213
+ const state = normalizeState(v);
214
+ return state ? `cms-state-${state}` : "";
215
+ });
216
+ style.backgroundColor = () => {
217
+ const v = uiUnwrap(props.color);
218
+ const state = normalizeState(v);
219
+ if (state) return "";
220
+ if (v && isTokenCSS(String(v))) return `var(--cms-${v})`;
221
+ return v || "";
222
+ };
223
+ } else {
224
+ const state = normalizeState(props.color);
225
+ if (state) {
226
+ classTokens.push(`cms-state-${state}`);
227
+ } else if (props.color && isTokenCSS(props.color)) {
228
+ style.backgroundColor = `var(--cms-${props.color})`;
229
+ } else if (props.color) {
230
+ style.backgroundColor = props.color;
231
+ }
232
+ }
233
+
234
+ // facciamo text color per i token
235
+ if (uiIsReactive(props.textColor)) {
236
+ style.color = () => {
237
+ const v = uiUnwrap(props.textColor);
238
+ if (v && isTokenCSS(String(v))) return `var(--cms-${v})`;
239
+ return v || "";
240
+ };
241
+ } else if (props.textColor && isTokenCSS(props.textColor)) {
242
+ style.color = `var(--cms-${props.textColor})`;
243
+ } else if (props.textColor) {
244
+ style.color = props.textColor;
245
+ }
246
+
247
+ if (uiIsReactive(props.size)) {
248
+ classTokens.push(() => {
249
+ const v = uiUnwrap(props.size);
250
+ if (typeof v === "string" && JSswift.uiSizes?.includes(v)) return `cms-size-${v}`;
251
+ return "";
252
+ });
253
+ } else {
254
+ const size = props.size;
255
+ if (typeof size === "string" && JSswift.uiSizes?.includes(size)) {
256
+ classTokens.push(`cms-size-${size}`);
257
+ }
258
+ }
259
+
260
+ if (uiIsReactive(props.shadow)) {
261
+ classTokens.push(() => {
262
+ const v = uiUnwrap(props.shadow);
263
+ const shadow = normalizeShadow(v);
264
+ return shadow ? `cms-shadow-${shadow}` : "";
265
+ });
266
+ style.boxShadow = () => {
267
+ const v = uiUnwrap(props.shadow);
268
+ const shadow = normalizeShadow(v);
269
+ if (shadow || v == null || v === false) return "";
270
+ return typeof v === "string" ? v : "";
271
+ };
272
+ } else {
273
+ const shadow = normalizeShadow(props.shadow);
274
+ if (shadow) classTokens.push(`cms-shadow-${shadow}`);
275
+ else if (props.shadow && typeof props.shadow === "string") {
276
+ style.boxShadow = props.shadow;
277
+ }
278
+ }
279
+
280
+ if (classTokens.length) {
281
+ props.class = uiClass([props.class, ...classTokens]);
282
+ }
283
+ if (Object.keys(style).length) {
284
+ props.style = { ...style, ...(props.style || {}) };
285
+ }
286
+ };
287
+
288
+ const normalizeArgsBase = JSswift.uiNormalizeArgs;
289
+ JSswift.uiNormalizeArgs = function (args) {
290
+ const out = normalizeArgsBase(args);
291
+ const props = out.props || {};
292
+ if (props && Object.prototype.hasOwnProperty.call(props, "children")) {
293
+ const propChildren = props.children;
294
+ if (!out.children || out.children.length === 0) {
295
+ out.children = Array.isArray(propChildren) ? propChildren : [propChildren];
296
+ }
297
+ delete props.children;
298
+ }
299
+ applyCommonProps(props);
300
+ return out;
301
+ };
302
+
303
+ // --------------------------------
304
+ // 2) UI PRIMITIVES (layout + atoms)
305
+ // --------------------------------
306
+ const UI = window._;
307
+
308
+ /*
309
+ UI.Row
310
+ UI.Col
311
+ UI.Spacer
312
+ UI.Container
313
+ UI.Card
314
+ UI.Btn
315
+ UI.Input
316
+ UI.Select
317
+ UI.Layout
318
+ UI.Footer
319
+ UI.Toolbar
320
+ UI.Grid
321
+ UI.GridCol
322
+ UI.Icon
323
+ UI.Badge
324
+ UI.Avatar
325
+ UI.Chip
326
+ UI.Tooltip
327
+ UI.List
328
+ UI.Item
329
+ UI.Separator
330
+ UI.Checkbox
331
+ UI.Radio
332
+ UI.Toggle
333
+ UI.Slider
334
+ UI.Rating
335
+ UI.Datepicker
336
+ UI.Calendar
337
+ UI.Date
338
+ UI.Time
339
+ UI.Tabs
340
+ UI.RouteTab
341
+ UI.Breadcrumbs
342
+ UI.Pagination
343
+ UI.Spinner
344
+ UI.Progress
345
+ UI.LoadingBar
346
+ UI.Banner
347
+ UI.Header
348
+ UI.Drawer
349
+ UI.Page
350
+ UI.AppShell
351
+ UI.Parallax
352
+ */
353
+
354
+ const META_PROP_DESCRIPTIONS = {
355
+ actions: "Content rendered in the action area, typically in a footer or trailing region.",
356
+ active: "Marks the item as active/selected and applies active styling/ARIA when relevant.",
357
+ align: "Alignment of content along the cross axis (e.g. left/center/right).",
358
+ allowCustom: "Allows values not present in the options list.",
359
+ allowCustomValue: "Accepts free-form values in addition to predefined options.",
360
+ aside: "Content or configuration for the aside/secondary region.",
361
+ auto: "Enables automatic sizing or layout behavior instead of fixed sizing.",
362
+ autocomplete: "Native autocomplete attribute for input elements.",
363
+ backdrop: "Whether to render a backdrop overlay behind the component.",
364
+ bgClass: "Additional CSS classes applied to the background layer.",
365
+ bgPosition: "CSS background-position value for the background image/layer.",
366
+ bgSize: "CSS background-size value for the background image/layer.",
367
+ cardClass: "Additional CSS classes applied to the card container.",
368
+ checked: "Checked state for toggleable controls (controlled).",
369
+ class: "Additional CSS classes applied to the component root element.",
370
+ clearable: "Shows a clear action to reset the current value.",
371
+ clickable: "Enables pointer/hover styles and click handling on the component.",
372
+ closeOnEsc: "Closes the component when the Escape key is pressed.",
373
+ closeOnOutside: "Closes the component when clicking outside its bounds.",
374
+ closeOnSelect: "Closes the menu/popover after selecting an item.",
375
+ color: "Semantic color name used to style the component.",
376
+ cols: "Number of columns for grid layouts.",
377
+ columns: "Column definitions for table-like components.",
378
+ content: "Main content node(s) or render function for the component body.",
379
+ contentClass: "Additional CSS classes applied to the content container.",
380
+ control: "Custom control element or render function for the input area.",
381
+ children: "Default child content (nodes, arrays, or render function).",
382
+ delay: "Delay in milliseconds before showing or hiding.",
383
+ dense: "Uses compact spacing and sizing.",
384
+ disabled: "Disables interaction and applies disabled styling/ARIA.",
385
+ divider: "Shows dividers between items or sections.",
386
+ drawer: "Content or configuration for the drawer region.",
387
+ drawerBreakpoint: "Viewport width where the drawer switches behavior (overlay vs persistent).",
388
+ drawerCloseIcon: "Icon used for the drawer close control.",
389
+ drawerOpen: "Controls whether the drawer is open (controlled).",
390
+ drawerOpenIcon: "Icon used for the drawer open control.",
391
+ drawerStateKey: "Storage key used to persist drawer open state.",
392
+ drawerWidth: "Drawer width (number interpreted as px or CSS length).",
393
+ elevated: "Applies elevated shadow/raised styling.",
394
+ emptyText: "Text shown when there is no data to display.",
395
+ error: "Error state or message displayed with the component.",
396
+ escClose: "Alias for close-on-escape behavior.",
397
+ filterPlaceholder: "Placeholder text for the filter/search input.",
398
+ filterable: "Enables filtering UI for option lists.",
399
+ flat: "Removes elevation/border for a flat look.",
400
+ footer: "Footer content node(s) or render function.",
401
+ form: "HTML form attribute to associate controls with a form.",
402
+ gap: "Gap/spacing between child elements.",
403
+ getValue: "Function to extract a value from an item object.",
404
+ groupCloseIcon: "Icon used to collapse grouped items.",
405
+ groupOpenIcon: "Icon used to expand grouped items.",
406
+ header: "Header content node(s) or render function.",
407
+ height: "Explicit height (number interpreted as px or CSS length).",
408
+ hint: "Helper text shown near the control.",
409
+ icon: "Icon name or node rendered with the component.",
410
+ iconRight: "Icon name or node rendered on the right side of the component.",
411
+ iconAlign: "Position of the icon relative to the label.",
412
+ initialSort: "Initial sort configuration for table data.",
413
+ inputmode: "Native inputmode attribute hint for virtual keyboards.",
414
+ items: "Array of items or nodes to render.",
415
+ justify: "Justification of items along the main axis.",
416
+ label: "Label text or node for the component.",
417
+ left: "Content or configuration for the left region.",
418
+ lg: "Column span at large breakpoint.",
419
+ loading: "Shows loading state and disables interactions where appropriate.",
420
+ loadingText: "Text displayed while loading.",
421
+ lockScroll: "Prevents body scroll while an overlay is open.",
422
+ max: "Maximum value for range-based controls.",
423
+ md: "Column span at medium breakpoint.",
424
+ message: "Message text shown in banners/alerts.",
425
+ min: "Minimum value for range-based controls.",
426
+ mobile: "Responsive overrides applied at the base/mobile viewport.",
427
+ model: "Two-way bound value (alias to value in some components).",
428
+ multi: "Enables multi-selection behavior.",
429
+ multiple: "Allows selecting multiple values.",
430
+ name: "HTML name attribute for form submission.",
431
+ noDrawer: "Disables rendering of the drawer region.",
432
+ note: "Secondary note text.",
433
+ number: "Forces numeric input/formatting where supported.",
434
+ offset: "General offset for positioning.",
435
+ offsetX: "Horizontal offset for overlay placement in pixels.",
436
+ offsetY: "Vertical offset for overlay placement in pixels.",
437
+ onBlur: "Callback fired when the control loses focus.",
438
+ onChange: "Callback fired when the value or selection changes.",
439
+ onClear: "Callback fired when the clear action is used.",
440
+ onClose: "Callback fired when the component closes.",
441
+ onFocus: "Callback fired when the control gains focus.",
442
+ onInput: "Callback fired on input events or while typing.",
443
+ onOpen: "Callback fired when the component opens.",
444
+ onRemove: "Callback fired when an item is removed.",
445
+ onSubmit: "Callback fired when a form is submitted.",
446
+ open: "Controls open/visible state (controlled).",
447
+ options: "Array of selectable options or option groups.",
448
+ outline: "Uses outlined visual style.",
449
+ overlay: "Renders content in an overlay layer or portal.",
450
+ overlayClose: "Closes the overlay when the backdrop is clicked.",
451
+ page: "Current page index (1-based unless documented otherwise).",
452
+ pageSize: "Number of items per page.",
453
+ padding: "Padding applied to the component root element.",
454
+ persistent: "Prevents closing via outside click or Escape.",
455
+ pc: "Responsive overrides applied from the desktop breakpoint upward.",
456
+ placeholder: "Placeholder text for inputs.",
457
+ placement: "Overlay placement relative to the target/anchor.",
458
+ prefix: "Content rendered before the main control.",
459
+ readonly: "Prevents editing while keeping focus and selection.",
460
+ removable: "Shows a remove affordance for chips/items.",
461
+ right: "Content or configuration for the right region.",
462
+ rowKey: "Key or function used to derive unique row IDs.",
463
+ rows: "Row data array for table-like components.",
464
+ margin: "Margin applied to the component root element.",
465
+ maxHeight: "Maximum height constraint (number interpreted as px or CSS length).",
466
+ maxWidth: "Maximum width constraint (number interpreted as px or CSS length).",
467
+ minHeight: "Minimum height constraint (number interpreted as px or CSS length).",
468
+ minWidth: "Minimum width constraint (number interpreted as px or CSS length).",
469
+ radius: "Border radius token or CSS length applied to the component.",
470
+ borderRadius: "Border radius token or CSS length applied to the component.",
471
+ separator: "Separator string/node between items.",
472
+ shortcode: "Keyboard shortcut string, object, or array bound to the component action.",
473
+ showShortcode: "Displays the configured keyboard shortcut badge when the component supports it.",
474
+ showLabel: "Whether to render the label text.",
475
+ size: "Size token, number, or CSS length for sizing.",
476
+ slots: "Named slots map for render overrides.",
477
+ sm: "Column span at small breakpoint.",
478
+ span: "Column span within a grid.",
479
+ speed: "Animation speed or duration in milliseconds.",
480
+ square: "Use square corners instead of rounded.",
481
+ src: "Image source URL.",
482
+ startTop: "Starting scroll offset for parallax effects.",
483
+ stateKey: "Storage key used to persist component state.",
484
+ step: "Step increment for numeric controls.",
485
+ sticky: "Makes the component sticky within its container.",
486
+ stickyAside: "Keeps the aside region sticky during scroll.",
487
+ stickyFooter: "Keeps the footer region sticky during scroll.",
488
+ stickyHeader: "Keeps the header region sticky during scroll.",
489
+ striped: "Applies striped row styling.",
490
+ style: "Inline styles applied to the component root element.",
491
+ subtitle: "Subtitle text or node.",
492
+ success: "Marks the component with success state styling.",
493
+ suffix: "Content rendered after the main control.",
494
+ tableClass: "Additional CSS classes applied to the table element.",
495
+ tablet: "Responsive overrides applied from the tablet breakpoint upward.",
496
+ tabs: "Tab definitions array.",
497
+ tagPage: "Query parameter name used to read/write the current page.",
498
+ target: "Target element or anchor used for positioning.",
499
+ text: "Text content for simple components.",
500
+ thickness: "Stroke/line thickness for progress indicators.",
501
+ title: "Title text or node.",
502
+ to: "Navigation target/route to open on click.",
503
+ topLabel: "Label displayed above the control.",
504
+ trapFocus: "Keeps focus trapped inside the dialog/popover.",
505
+ type: "Variant or native input type.",
506
+ value: "Current value (controlled).",
507
+ vertical: "Vertical orientation instead of horizontal.",
508
+ warning: "Marks the component with warning state styling.",
509
+ width: "Explicit width (number interpreted as px or CSS length).",
510
+ wrap: "Allows children to wrap to new lines.",
511
+ wrapClass: "Additional CSS classes applied to the outer wrapper.",
512
+ zIndex: "z-index for overlay stacking.",
513
+ shadow: "Shadow token or CSS box-shadow string applied to the component.",
514
+ };
515
+
516
+ const META_SLOT_DESCRIPTIONS = {
517
+ actions: "Slot for action buttons or links.",
518
+ arrow: "Slot for the tooltip/popover arrow element.",
519
+ aside: "Slot for aside/secondary content.",
520
+ center: "Slot for centered content within a toolbar/row.",
521
+ clear: "Slot for the clear control/content.",
522
+ content: "Slot for the main body content.",
523
+ control: "Slot for custom control/input rendering.",
524
+ default: "Primary content for the component.",
525
+ drawer: "Slot for drawer content.",
526
+ empty: "Slot for empty-state content.",
527
+ errorMessage: "Slot for custom error message rendering.",
528
+ filter: "Slot for custom filter/search UI.",
529
+ footer: "Slot for footer content.",
530
+ group: "Slot for grouped content container.",
531
+ groupLabel: "Slot for group label content.",
532
+ header: "Slot for header content.",
533
+ hint: "Slot for helper/hint text.",
534
+ icon: "Slot for icon content.",
535
+ iconRight: "Slot for right-aligned icon content.",
536
+ input: "Slot for custom input element/content.",
537
+ item: "Slot for custom item rendering.",
538
+ itemLabel: "Slot for item label rendering.",
539
+ label: "Slot for label content.",
540
+ left: "Slot for left-aligned content.",
541
+ loading: "Slot for loading state content.",
542
+ message: "Slot for message/alert content.",
543
+ next: "Slot for the next-page control.",
544
+ note: "Slot for note/secondary text.",
545
+ option: "Slot for custom option rendering.",
546
+ page: "Slot for page indicator/content.",
547
+ prefix: "Slot for prefix content.",
548
+ prev: "Slot for the previous-page control.",
549
+ right: "Slot for right-aligned content.",
550
+ separator: "Slot for separator rendering between items.",
551
+ star: "Slot for custom star icon/content in rating.",
552
+ subtitle: "Slot for subtitle content.",
553
+ success: "Slot for success message/content.",
554
+ suffix: "Slot for suffix content.",
555
+ tab: "Slot for custom tab label/content.",
556
+ target: "Slot for custom target/anchor element.",
557
+ title: "Slot for title content.",
558
+ topLabel: "Slot for top label content.",
559
+ warning: "Slot for warning message/content."
560
+ };
561
+
562
+ const META_PROP_DEFAULTS = {
563
+ dense: false,
564
+ disabled: false,
565
+ readonly: false,
566
+ outline: false,
567
+ clickable: false,
568
+ flat: false,
569
+ elevated: false
570
+ };
571
+ const DEFAULT_SIZE = [...JSswift.uiSizes];
572
+ const DEFAULT_COLOR = [...JSswift.uiColors];
573
+ const META_PROP_VALUES = {
574
+ size: ["number", "CSS units", ...DEFAULT_SIZE],
575
+ color: DEFAULT_COLOR,
576
+ padding: ["number", "CSS units", ...DEFAULT_SIZE],
577
+ margin: ["number", "CSS units", ...DEFAULT_SIZE],
578
+ width: ["number", "CSS units", ...DEFAULT_SIZE],
579
+ minWidth: ["number", "CSS units", ...DEFAULT_SIZE],
580
+ maxWidth: ["number", "CSS units", ...DEFAULT_SIZE],
581
+ height: ["number", "CSS units", ...DEFAULT_SIZE],
582
+ minHeight: ["number", "CSS units", ...DEFAULT_SIZE],
583
+ maxHeight: ["number", "CSS units", ...DEFAULT_SIZE],
584
+ iconAlign: ["left", "right", "before", "after"],
585
+ align: ["left", "center", "right"],
586
+ justify: ["start", "center", "end", "space-between", "space-around", "space-evenly"],
587
+ placement: ["top", "bottom", "left", "right", "top-start", "top-end", "bottom-start", "bottom-end"],
588
+ type: DEFAULT_COLOR,
589
+ shadow: ["none", ...DEFAULT_SIZE],
590
+ outline: ["number", "CSS units", ...DEFAULT_SIZE],
591
+ borderRadius: ["number", "CSS units", ...DEFAULT_SIZE],
592
+ radius: ["number", "CSS units", ...DEFAULT_SIZE],
593
+ icon: ["#name_tabler", "name_material", "class"],
594
+ iconRight: ["#name_tabler", "name_material", "class"]
595
+ };
596
+
597
+ const META_PROP_CATEGORIES = {
598
+ class: "style",
599
+ style: "style",
600
+ size: "style",
601
+ color: "style",
602
+ outline: "style",
603
+ shadow: "style",
604
+ borderRadius: "style",
605
+ radius: "style",
606
+ padding: "layout",
607
+ margin: "layout",
608
+ width: "layout",
609
+ minWidth: "layout",
610
+ maxWidth: "layout",
611
+ height: "layout",
612
+ minHeight: "layout",
613
+ maxHeight: "layout",
614
+ mobile: "layout",
615
+ tablet: "layout",
616
+ pc: "layout",
617
+ dense: "layout",
618
+ flat: "style",
619
+ elevated: "style",
620
+ clickable: "behavior",
621
+ disabled: "state",
622
+ readonly: "state",
623
+ loading: "state",
624
+ active: "state",
625
+ error: "state",
626
+ success: "state",
627
+ warning: "state",
628
+ note: "state",
629
+ hint: "state",
630
+ value: "data",
631
+ model: "data",
632
+ items: "data",
633
+ options: "data",
634
+ rows: "data",
635
+ columns: "data",
636
+ onClick: "events",
637
+ onChange: "events",
638
+ onInput: "events",
639
+ onFocus: "events",
640
+ onBlur: "events",
641
+ onSubmit: "events"
642
+ };
643
+
644
+ const normalizeMetaType = (value) => {
645
+ if (typeof value === "string") return value;
646
+ if (!value || typeof value !== "object" || Array.isArray(value)) return "unknown";
647
+ return value.type || value.signature || value.value || "unknown";
648
+ };
649
+
650
+ const fallbackMetaDescription = (name, kind) => {
651
+ if (kind === "slot") {
652
+ return `Slot content for "${name}". Accepts nodes, arrays, or render functions.`;
653
+ }
654
+ return `Component configuration for "${name}". When not handled internally, it is forwarded to the root element.`;
655
+ };
656
+
657
+ const normalizeMetaFields = (fields, descriptions, kind) => {
658
+ if (!fields || typeof fields !== "object") return fields;
659
+ const out = {};
660
+ for (const [name, value] of Object.entries(fields)) {
661
+ const description = descriptions[name] || fallbackMetaDescription(name, kind);
662
+ if (value && typeof value === "object" && !Array.isArray(value)) {
663
+ const type = normalizeMetaType(value);
664
+ out[name] = {
665
+ ...value,
666
+ type,
667
+ description: value.description || description,
668
+ default: Object.prototype.hasOwnProperty.call(value, "default") ? value.default : (META_PROP_DEFAULTS[name] ?? null),
669
+ values: value.values || META_PROP_VALUES[name] || null,
670
+ category: value.category || META_PROP_CATEGORIES[name] || (kind === "slot" ? "slot" : "general")
671
+ };
672
+ } else {
673
+ const type = normalizeMetaType(value);
674
+ out[name] = {
675
+ type,
676
+ description,
677
+ default: META_PROP_DEFAULTS[name] ?? null,
678
+ values: META_PROP_VALUES[name] || null,
679
+ category: META_PROP_CATEGORIES[name] || (kind === "slot" ? "slot" : "general")
680
+ };
681
+ }
682
+ }
683
+ return out;
684
+ };
685
+
686
+ const normalizeMetaEntry = (componentName, meta) => {
687
+ if (!meta || typeof meta !== "object") return meta;
688
+ if (!meta.props || typeof meta.props !== "object" || Array.isArray(meta.props)) {
689
+ meta.props = meta.props && typeof meta.props === "object" && !Array.isArray(meta.props) ? meta.props : {};
690
+ }
691
+ if (!Object.prototype.hasOwnProperty.call(meta.props, "children")) {
692
+ meta.props.children = "Node|Array|Function";
693
+ }
694
+ const commonProps = {
695
+ size: meta.props.size || "string|number",
696
+ color: meta.props.color || "string",
697
+ outline: meta.props.outline || "boolean",
698
+ clickable: meta.props.clickable || "boolean",
699
+ padding: meta.props.padding || "string|number",
700
+ margin: meta.props.margin || "string|number",
701
+ width: meta.props.width || "string|number",
702
+ minWidth: meta.props.minWidth || "string|number",
703
+ maxWidth: meta.props.maxWidth || "string|number",
704
+ height: meta.props.height || "string|number",
705
+ minHeight: meta.props.minHeight || "string|number",
706
+ maxHeight: meta.props.maxHeight || "string|number",
707
+ radius: meta.props.radius || "string|number",
708
+ borderRadius: meta.props.borderRadius || "string|number",
709
+ shadow: meta.props.shadow || "string|boolean",
710
+ mobile: meta.props.mobile || "object",
711
+ tablet: meta.props.tablet || "object",
712
+ pc: meta.props.pc || "object",
713
+ class: meta.props.class || "string",
714
+ style: meta.props.style || "object"
715
+ };
716
+ meta.props = { ...commonProps, ...meta.props };
717
+ if (meta.props.icon && !meta.props.iconRight) {
718
+ meta.props.iconRight = meta.props.icon;
719
+ }
720
+ if (meta.props) meta.props = normalizeMetaFields(meta.props, META_PROP_DESCRIPTIONS, "prop");
721
+ if (meta.slots) meta.slots = normalizeMetaFields(meta.slots, META_SLOT_DESCRIPTIONS, "slot");
722
+ if (meta.slots && meta.slots.icon && !meta.slots.iconRight) {
723
+ meta.slots.iconRight = { type: meta.slots.icon?.type || "Node|Array|Function", description: "Slot for right-aligned icon content." };
724
+ }
725
+ if (!meta.description) {
726
+ meta.description = `${componentName} component meta definition.`;
727
+ }
728
+ return meta;
729
+ };
730
+
731
+ const ensureMetaProxy = () => {
732
+ if (app.ui._metaProxyInstalled) return;
733
+ app.ui._metaProxyInstalled = true;
734
+ const target = app.ui.meta || {};
735
+ for (const key of Object.keys(target)) {
736
+ normalizeMetaEntry(key, target[key]);
737
+ }
738
+ const proxy = new Proxy(target, {
739
+ set(obj, prop, value) {
740
+ if (typeof prop === "symbol") {
741
+ obj[prop] = value;
742
+ return true;
743
+ }
744
+ obj[prop] = normalizeMetaEntry(prop, value);
745
+ return true;
746
+ }
747
+ });
748
+ app.ui.meta = proxy;
749
+ UI.meta = proxy;
750
+ };
751
+
752
+ if (app.isDev?.()) {
753
+ ensureMetaProxy();
754
+ }
755
+
756
+ function flattenSlotValue(value) {
757
+ if (!value) return null;
758
+ if (!Array.isArray(value)) return value;
759
+ const out = [];
760
+ const stack = value.slice();
761
+ while (stack.length) {
762
+ const item = stack.shift();
763
+ if (!item) continue;
764
+ if (Array.isArray(item)) stack.unshift(...item);
765
+ else out.push(item);
766
+ }
767
+ return out.length ? out : null;
768
+ }
769
+ const STYLE_PROP_TOKEN_MAP = {
770
+ padding: "p",
771
+ paddingTop: "p",
772
+ paddingRight: "p",
773
+ paddingBottom: "p",
774
+ paddingLeft: "p",
775
+ paddingInline: "p",
776
+ paddingInlineStart: "p",
777
+ paddingInlineEnd: "p",
778
+ paddingBlock: "p",
779
+ paddingBlockStart: "p",
780
+ paddingBlockEnd: "p",
781
+ margin: "m",
782
+ marginTop: "m",
783
+ marginRight: "m",
784
+ marginBottom: "m",
785
+ marginLeft: "m",
786
+ marginInline: "m",
787
+ marginInlineStart: "m",
788
+ marginInlineEnd: "m",
789
+ marginBlock: "m",
790
+ marginBlockStart: "m",
791
+ marginBlockEnd: "m",
792
+ width: "w",
793
+ minWidth: "w",
794
+ maxWidth: "w",
795
+ height: "h",
796
+ minHeight: "h",
797
+ maxHeight: "h"
798
+ };
799
+ const uiResponsiveWrapValue = (value) => {
800
+ if (value === true) return "wrap";
801
+ if (value === false) return "nowrap";
802
+ return String(value);
803
+ };
804
+ const uiResponsiveGridColsValue = (value) => {
805
+ if (typeof value === "number") return `repeat(${Math.max(1, Math.round(value))}, minmax(0, 1fr))`;
806
+ return String(value);
807
+ };
808
+ const uiResponsiveGridFlowValue = (value) => {
809
+ const flow = String(value).trim().toLowerCase().replace("-", " ");
810
+ return flow;
811
+ };
812
+ const UI_RESPONSIVE_STYLE_RULES = [
813
+ { prop: "display", css: "display", var: "display" },
814
+ { prop: "direction", css: "flex-direction", var: "flex-direction" },
815
+ { prop: "wrap", css: "flex-wrap", var: "flex-wrap", allowFalse: true, mapValue: uiResponsiveWrapValue },
816
+ { prop: "align", css: "align-items", var: "align-items" },
817
+ { prop: "justify", css: "justify-content", var: "justify-content" },
818
+ { prop: "self", css: "align-self", var: "align-self" },
819
+ { prop: "justifySelf", css: "justify-self", var: "justify-self" },
820
+ { prop: "place", css: "place-items", var: "place-items" },
821
+ { prop: "placeSelf", css: "place-self", var: "place-self" },
822
+ { prop: ["cols", "columns"], css: "grid-template-columns", var: "grid-template-columns", mapValue: uiResponsiveGridColsValue },
823
+ { prop: "rows", css: "grid-template-rows", var: "grid-template-rows", mapValue: uiResponsiveGridColsValue },
824
+ { prop: "flow", css: "grid-auto-flow", var: "grid-auto-flow", mapValue: uiResponsiveGridFlowValue },
825
+ { prop: "autoRows", css: "grid-auto-rows", var: "grid-auto-rows" },
826
+ { prop: ["span", "gridColumn"], css: "grid-column", var: "grid-column", number: "raw" },
827
+ { prop: ["row", "rowSpan"], css: "grid-row", var: "grid-row", number: "raw" },
828
+ { prop: "area", css: "grid-area", var: "grid-area" },
829
+ { prop: "gap", css: "gap", var: "gap", token: "gap" },
830
+ { prop: "rowGap", css: "row-gap", var: "row-gap", token: "gap" },
831
+ { prop: ["columnGap", "colGap"], css: "column-gap", var: "column-gap", token: "gap" },
832
+ { prop: "width", css: "width", var: "width", token: "w" },
833
+ { prop: "minWidth", css: "min-width", var: "min-width", token: "min-w" },
834
+ { prop: "maxWidth", css: "max-width", var: "max-width", token: "max-w" },
835
+ { prop: "height", css: "height", var: "height", token: "h" },
836
+ { prop: "minHeight", css: "min-height", var: "min-height", token: "min-h" },
837
+ { prop: "maxHeight", css: "max-height", var: "max-height", token: "max-h" },
838
+ { prop: "padding", css: "padding", var: "padding", token: "p" },
839
+ { prop: "paddingLeft", css: "padding-left", var: "padding-left", token: "p" },
840
+ { prop: "paddingRight", css: "padding-right", var: "padding-right", token: "p" },
841
+ { prop: "paddingTop", css: "padding-top", var: "padding-top", token: "p" },
842
+ { prop: "paddingBottom", css: "padding-bottom", var: "padding-bottom", token: "p" },
843
+ { prop: "margin", css: "margin", var: "margin", token: "m" },
844
+ { prop: "marginLeft", css: "margin-left", var: "margin-left", token: "m" },
845
+ { prop: "marginRight", css: "margin-right", var: "margin-right", token: "m" },
846
+ { prop: "marginTop", css: "margin-top", var: "margin-top", token: "m" },
847
+ { prop: "marginBottom", css: "margin-bottom", var: "margin-bottom", token: "m" },
848
+ { prop: "fontSize", css: "font-size", var: "font-size", token: "f" },
849
+ { prop: "fontWeight", css: "font-weight", var: "font-weight", token: "fw", number: "raw" },
850
+ { prop: "lineHeight", css: "line-height", var: "line-height", token: "lh", number: "raw" },
851
+ { prop: "letterSpacing", css: "letter-spacing", var: "letter-spacing", token: "ls" },
852
+ { prop: ["borderRadius", "radius"], css: "border-radius", var: "border-radius", token: "r" },
853
+ { prop: "overflow", css: "overflow", var: "overflow" },
854
+ { prop: "order", css: "order", var: "order", number: "raw" },
855
+ ];
856
+ const UI_RESPONSIVE_COMMON_STYLE_RULES = UI_RESPONSIVE_STYLE_RULES.filter((rule) => {
857
+ const props = Array.isArray(rule.prop) ? rule.prop : [rule.prop];
858
+ return props.some((prop) => Object.prototype.hasOwnProperty.call(STYLE_PROP_TOKEN_MAP, prop))
859
+ || props.includes("fontSize")
860
+ || props.includes("fontWeight")
861
+ || props.includes("lineHeight")
862
+ || props.includes("letterSpacing")
863
+ || props.includes("borderRadius")
864
+ || props.includes("radius");
865
+ });
866
+ JSswift.uiResponsiveStyleRules = UI_RESPONSIVE_STYLE_RULES;
867
+ JSswift.uiResponsiveCommonStyleRules = UI_RESPONSIVE_COMMON_STYLE_RULES;
868
+ const camelToCssProperty = (name) => name.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
869
+ const applyStyleProp = (obj, value, name, tokenName) => {
870
+ if (value == null || value === false || value === "") return;
871
+ obj.style.setProperty(camelToCssProperty(name), unitCover(value, tokenName));
872
+ };
873
+ function setPropertyProps(obj, props) {
874
+ if (props.size) {
875
+ if (typeof props.size === "number") {
876
+ obj.style.setProperty("--cms-font-size", `${props.size}px`);
877
+ } else if (JSswift.uiSizes.includes(props.size)) {
878
+ obj.style.setProperty("--cms-font-size", `var(--cms-font-size-${props.size})`);
879
+ }
880
+ }
881
+
882
+ Object.entries(STYLE_PROP_TOKEN_MAP).forEach(([name, tokenName]) => {
883
+ if (uiResponsiveHasProp(props, name)) return;
884
+ applyStyleProp(obj, props[name], name, tokenName);
885
+ });
886
+ uiApplyResponsiveProps(obj, props, UI_RESPONSIVE_COMMON_STYLE_RULES);
887
+
888
+ // gradient
889
+ if (props.gradient) {
890
+ if (typeof props.gradient == "number") {
891
+ obj.style.setProperty("--set-gradient-deg", `${props.gradient}deg`);
892
+ }
893
+ }
894
+ // radius
895
+ if (props.radius) {
896
+ if (typeof props.radius == "number") {
897
+ obj.style.setProperty("--set-border-radius", `${props.radius}px`);
898
+ } else if (JSswift.uiSizes.includes(props.radius)) {
899
+ obj.style.setProperty("--set-border-radius", `var(--cms-r-${props.radius})`);
900
+ } else {
901
+ obj.style.setProperty("--set-border-radius", `${props.radius}`);
902
+ }
903
+ }
904
+
905
+ }
906
+
907
+ function uiOptionNode(props = {}, ...children) {
908
+ const el = document.createElement("option");
909
+ if (props && typeof props === "object") {
910
+ if (props.value != null) el.value = String(props.value);
911
+ if (props.label != null) el.label = String(props.label);
912
+ if (props.disabled != null) el.disabled = !!props.disabled;
913
+ if (props.selected != null) el.selected = !!props.selected;
914
+ if (props.class != null) el.className = String(props.class);
915
+ }
916
+ const content = children.flat ? children.flat(Infinity) : children;
917
+ if (content.length) {
918
+ content.forEach((child) => {
919
+ if (child == null || child === false) return;
920
+ if (child instanceof Node) el.appendChild(child);
921
+ else el.appendChild(document.createTextNode(String(child)));
922
+ });
923
+ } else if (props && props.text != null) {
924
+ el.textContent = String(props.text);
925
+ }
926
+ return el;
927
+ }
928
+
929
+ JSswift.ui.getSlot = (slots, name) => {
930
+ if (!slots) return null;
931
+ return Object.prototype.hasOwnProperty.call(slots, name) ? slots[name] : null;
932
+ };
933
+
934
+ JSswift.ui.renderSlot = (slots, name, ctx, fallback) => {
935
+ const slot = JSswift.ui.getSlot(slots, name);
936
+ const hasSlot = slot !== null && slot !== undefined;
937
+ const raw = hasSlot ? (typeof slot === "function" ? slot(ctx || {}) : slot) : fallback;
938
+ if (raw == null) return null;
939
+ return flattenSlotValue(JSswift.ui.slot(raw));
940
+ };
941
+
942
+ const renderSlotToArray = (slots, name, ctx, fallback) => {
943
+ const v = JSswift.ui.renderSlot(slots, name, ctx, fallback);
944
+ if (!v) return [];
945
+ return Array.isArray(v) ? v : [v];
946
+ };
947
+
948
+ const UI_SHORTCODE_REGISTRY = new Set();
949
+ let uiShortcodeListenerAttached = false;
950
+ const UI_SHORTCODE_MODIFIERS = new Set(["ctrl", "alt", "shift", "meta"]);
951
+ const UI_SHORTCODE_ALIASES = {
952
+ control: "ctrl",
953
+ ctl: "ctrl",
954
+ option: "alt",
955
+ opt: "alt",
956
+ command: "meta",
957
+ cmd: "meta",
958
+ "⌘": "meta",
959
+ "⌃": "ctrl",
960
+ "⌥": "alt",
961
+ "⇧": "shift",
962
+ esc: "escape",
963
+ return: "enter",
964
+ plus: "+",
965
+ spacebar: "space",
966
+ up: "arrowup",
967
+ down: "arrowdown",
968
+ left: "arrowleft",
969
+ right: "arrowright",
970
+ del: "delete"
971
+ };
972
+ const UI_SHORTCODE_DISPLAY = {
973
+ ctrl: "Ctrl",
974
+ alt: "Alt",
975
+ shift: "Shift",
976
+ meta: "Meta",
977
+ escape: "Esc",
978
+ enter: "Enter",
979
+ tab: "Tab",
980
+ space: "Space",
981
+ backspace: "Backspace",
982
+ delete: "Del",
983
+ arrowup: "Up",
984
+ arrowdown: "Down",
985
+ arrowleft: "Left",
986
+ arrowright: "Right",
987
+ pageup: "PgUp",
988
+ pagedown: "PgDn"
989
+ };
990
+
991
+ const uiIsApplePlatform = () => {
992
+ if (typeof navigator === "undefined") return false;
993
+ const platform = String(navigator.platform || navigator.userAgent || "");
994
+ return /(mac|iphone|ipad|ipod)/i.test(platform);
995
+ };
996
+
997
+ const uiNormalizeShortcodeToken = (token) => {
998
+ if (token == null) return "";
999
+ const raw = String(token).trim().toLowerCase();
1000
+ if (!raw) return "";
1001
+ return UI_SHORTCODE_ALIASES[raw] || raw;
1002
+ };
1003
+
1004
+ const uiParseShortcode = (rawValue) => {
1005
+ if (rawValue == null || rawValue === false) return null;
1006
+ const source = String(rawValue).trim();
1007
+ if (!source) return null;
1008
+
1009
+ const parts = source
1010
+ .split("+")
1011
+ .map(uiNormalizeShortcodeToken)
1012
+ .filter(Boolean);
1013
+
1014
+ if (!parts.length) return null;
1015
+
1016
+ const spec = {
1017
+ raw: source,
1018
+ key: "",
1019
+ ctrl: false,
1020
+ alt: false,
1021
+ shift: false,
1022
+ meta: false
1023
+ };
1024
+
1025
+ parts.forEach((part) => {
1026
+ if (part === "mod") {
1027
+ if (uiIsApplePlatform()) spec.meta = true;
1028
+ else spec.ctrl = true;
1029
+ return;
1030
+ }
1031
+ if (UI_SHORTCODE_MODIFIERS.has(part)) {
1032
+ spec[part] = true;
1033
+ return;
1034
+ }
1035
+ spec.key = part;
1036
+ });
1037
+
1038
+ if (!spec.key) return null;
1039
+ return spec;
1040
+ };
1041
+
1042
+ const uiNormalizeShortcodeList = (rawValue) => {
1043
+ if (rawValue == null || rawValue === false) return [];
1044
+ const list = Array.isArray(rawValue) ? rawValue : [rawValue];
1045
+ return list.map((item) => {
1046
+ if (item == null || item === false) return null;
1047
+ if (typeof item === "object" && !Array.isArray(item)) {
1048
+ const parsed = uiParseShortcode(item.shortcode ?? item.shortcut ?? item.keys ?? item.key);
1049
+ return parsed ? { ...item, ...parsed } : null;
1050
+ }
1051
+ const parsed = uiParseShortcode(item);
1052
+ return parsed ? { ...parsed } : null;
1053
+ }).filter(Boolean);
1054
+ };
1055
+
1056
+ const uiResolveShortcodeList = (props = {}) =>
1057
+ uiNormalizeShortcodeList(props.shortcode ?? props.shortcut ?? props.hotkey ?? props.keys);
1058
+
1059
+ const uiIsShortcodeVisible = (props = {}) =>
1060
+ props.showShortcode === true || props.showShortcut === true;
1061
+
1062
+ const uiFormatShortcodeKey = (key) => {
1063
+ const normalized = uiNormalizeShortcodeToken(key);
1064
+ if (!normalized) return "";
1065
+ const display = { ...UI_SHORTCODE_DISPLAY };
1066
+ if (uiIsApplePlatform()) {
1067
+ display.alt = "Option";
1068
+ display.meta = "Cmd";
1069
+ }
1070
+ if (display[normalized]) return display[normalized];
1071
+ if (normalized.length === 1) return normalized.toUpperCase();
1072
+ return normalized.replace(/(^|[-_ ])(\w)/g, (_, prefix, char) => `${prefix}${char.toUpperCase()}`);
1073
+ };
1074
+
1075
+ const uiFormatShortcode = (spec) => {
1076
+ if (!spec) return "";
1077
+ const parts = [];
1078
+ if (spec.ctrl) parts.push(uiFormatShortcodeKey("ctrl"));
1079
+ if (spec.alt) parts.push(uiFormatShortcodeKey("alt"));
1080
+ if (spec.shift) parts.push(uiFormatShortcodeKey("shift"));
1081
+ if (spec.meta) parts.push(uiFormatShortcodeKey("meta"));
1082
+ parts.push(uiFormatShortcodeKey(spec.key));
1083
+ return parts.filter(Boolean).join("+");
1084
+ };
1085
+
1086
+ const uiFormatShortcodeList = (specs) =>
1087
+ (Array.isArray(specs) ? specs : []).map(uiFormatShortcode).filter(Boolean).join(" / ");
1088
+
1089
+ const uiCreateShortcodeHint = (props = {}, options = {}) => {
1090
+ const specs = uiResolveShortcodeList(props);
1091
+ if (!uiIsShortcodeVisible(props) || !specs.length || typeof document === "undefined") return null;
1092
+ const node = document.createElement("span");
1093
+ node.className = options.className || "cms-shortcode";
1094
+ node.setAttribute("aria-hidden", "true");
1095
+ node.textContent = uiFormatShortcodeList(specs);
1096
+ return node;
1097
+ };
1098
+
1099
+ const uiIsEditableEventTarget = (target) => {
1100
+ const el = target?.nodeType === 1 ? target : target?.parentElement;
1101
+ if (!el || typeof el.closest !== "function") return false;
1102
+ if (el.isContentEditable) return true;
1103
+ const editable = el.closest("input, textarea, select, [contenteditable], [contenteditable='true'], [contenteditable='plaintext-only']");
1104
+ if (!editable) return false;
1105
+ if ("disabled" in editable && editable.disabled) return false;
1106
+ return true;
1107
+ };
1108
+
1109
+ const uiEventMatchesShortcode = (event, spec) => {
1110
+ if (!event || !spec) return false;
1111
+ if (!!event.ctrlKey !== !!spec.ctrl) return false;
1112
+ if (!!event.altKey !== !!spec.alt) return false;
1113
+ if (!!event.shiftKey !== !!spec.shift) return false;
1114
+ if (!!event.metaKey !== !!spec.meta) return false;
1115
+ return uiNormalizeShortcodeToken(event.key) === spec.key;
1116
+ };
1117
+
1118
+ const uiDetachShortcodeListenerIfEmpty = () => {
1119
+ if (UI_SHORTCODE_REGISTRY.size || !uiShortcodeListenerAttached || typeof document === "undefined") return;
1120
+ document.removeEventListener("keydown", uiHandleShortcodeKeydown, true);
1121
+ uiShortcodeListenerAttached = false;
1122
+ };
1123
+
1124
+ function uiHandleShortcodeKeydown(event) {
1125
+ if (!UI_SHORTCODE_REGISTRY.size || !event || event.defaultPrevented || event.isComposing) return;
1126
+
1127
+ const isEditable = uiIsEditableEventTarget(event.target);
1128
+ const bindings = Array.from(UI_SHORTCODE_REGISTRY);
1129
+
1130
+ for (let index = bindings.length - 1; index >= 0; index -= 1) {
1131
+ const binding = bindings[index];
1132
+ const anchor = binding?.anchor;
1133
+ if (!anchor || !anchor.isConnected) {
1134
+ UI_SHORTCODE_REGISTRY.delete(binding);
1135
+ continue;
1136
+ }
1137
+ if (binding.isEnabled?.() === false) continue;
1138
+
1139
+ const matched = binding.specs.find((spec) => {
1140
+ const allowInEditable = spec.allowInEditable ?? binding.allowInEditable ?? false;
1141
+ if (isEditable && !allowInEditable) return false;
1142
+ return uiEventMatchesShortcode(event, spec);
1143
+ });
1144
+
1145
+ if (!matched) continue;
1146
+
1147
+ const shouldPreventDefault = matched.preventDefault ?? binding.preventDefault ?? true;
1148
+ const shouldStopPropagation = matched.stopPropagation ?? binding.stopPropagation ?? true;
1149
+ if (shouldPreventDefault) event.preventDefault();
1150
+ if (shouldStopPropagation) event.stopPropagation();
1151
+
1152
+ const result = binding.action?.(event, matched, anchor);
1153
+ if (result !== false) break;
1154
+ }
1155
+
1156
+ uiDetachShortcodeListenerIfEmpty();
1157
+ }
1158
+
1159
+ const uiRegisterShortcode = (anchor, props = {}, options = {}) => {
1160
+ const specs = uiResolveShortcodeList(props);
1161
+ if (!anchor || !specs.length || typeof document === "undefined") return null;
1162
+
1163
+ const binding = {
1164
+ anchor,
1165
+ specs,
1166
+ action: typeof options.action === "function" ? options.action : () => anchor.click?.(),
1167
+ isEnabled: typeof options.isEnabled === "function" ? options.isEnabled : null,
1168
+ allowInEditable: !!options.allowInEditable,
1169
+ preventDefault: options.preventDefault ?? true,
1170
+ stopPropagation: options.stopPropagation ?? true
1171
+ };
1172
+
1173
+ UI_SHORTCODE_REGISTRY.add(binding);
1174
+
1175
+ if (!uiShortcodeListenerAttached) {
1176
+ document.addEventListener("keydown", uiHandleShortcodeKeydown, true);
1177
+ uiShortcodeListenerAttached = true;
1178
+ }
1179
+
1180
+ const dispose = () => {
1181
+ UI_SHORTCODE_REGISTRY.delete(binding);
1182
+ uiDetachShortcodeListenerIfEmpty();
1183
+ };
1184
+
1185
+ JSswift._registerCleanup(anchor, dispose);
1186
+ return dispose;
1187
+ };
1188
+
1189
+ const uiFocusShortcutTarget = (target, options = {}) => {
1190
+ if (!target || typeof target.focus !== "function") return false;
1191
+ try {
1192
+ target.focus({ preventScroll: options.preventScroll ?? true });
1193
+ } catch {
1194
+ target.focus();
1195
+ }
1196
+ if (options.selectText && typeof target.select === "function") {
1197
+ try { target.select(); } catch { }
1198
+ }
1199
+ return true;
1200
+ };
1201
+
1202
+ const unitCover = (v, name = 'size') => {
1203
+ if (typeof v === "number") return v + "px";
1204
+ if (JSswift.uiSizes.includes(v)) return `var(--cms-${name}-${v})`;
1205
+ if (typeof v === "string") return v;
1206
+ return v;
1207
+ };
1208
+