ux4g-components-web 1.4.0 → 1.5.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.
Files changed (124) hide show
  1. package/README.md +76 -0
  2. package/dist/__tests__/css-bundle.integration.test.d.ts +11 -0
  3. package/dist/__tests__/css-bundle.integration.test.js +1102 -0
  4. package/dist/__tests__/css-bundle.phase10.property.test.d.ts +9 -0
  5. package/dist/__tests__/css-bundle.phase10.property.test.js +64 -0
  6. package/dist/__tests__/css-bundle.phase5.property.test.d.ts +9 -0
  7. package/dist/__tests__/css-bundle.phase5.property.test.js +126 -0
  8. package/dist/__tests__/css-bundle.phase6.property.test.d.ts +9 -0
  9. package/dist/__tests__/css-bundle.phase6.property.test.js +73 -0
  10. package/dist/__tests__/css-bundle.phase7.property.test.d.ts +9 -0
  11. package/dist/__tests__/css-bundle.phase7.property.test.js +76 -0
  12. package/dist/__tests__/css-bundle.phase8.property.test.d.ts +9 -0
  13. package/dist/__tests__/css-bundle.phase8.property.test.js +67 -0
  14. package/dist/__tests__/css-bundle.phase9.property.test.d.ts +9 -0
  15. package/dist/__tests__/css-bundle.phase9.property.test.js +93 -0
  16. package/dist/__tests__/css-bundle.property.test.d.ts +14 -0
  17. package/dist/__tests__/css-bundle.property.test.js +393 -0
  18. package/dist/__tests__/dom-generators.determinism.property.test.d.ts +1 -0
  19. package/dist/__tests__/dom-generators.determinism.property.test.js +71 -0
  20. package/dist/__tests__/dom-generators.id.property.test.d.ts +1 -0
  21. package/dist/__tests__/dom-generators.id.property.test.js +99 -0
  22. package/dist/__tests__/dom-generators.otp.property.test.d.ts +1 -0
  23. package/dist/__tests__/dom-generators.property.test.d.ts +1 -0
  24. package/dist/__tests__/dom-generators.property.test.js +205 -0
  25. package/dist/__tests__/dom-generators.states.property.test.d.ts +1 -0
  26. package/dist/__tests__/dom-generators.table.property.test.d.ts +1 -0
  27. package/dist/__tests__/dom-generators.tier1.property.test.d.ts +1 -0
  28. package/dist/__tests__/dom-generators.tier1.property.test.js +403 -0
  29. package/dist/__tests__/dom-generators.validation.property.test.d.ts +1 -0
  30. package/dist/__tests__/dom-generators.validation.property.test.js +327 -0
  31. package/dist/__tests__/megamenu.classbuilder.property.test.d.ts +1 -0
  32. package/dist/__tests__/megamenu.classbuilder.property.test.js +88 -0
  33. package/dist/__tests__/smoke.test.d.ts +1 -0
  34. package/dist/__tests__/smoke.test.js +65 -0
  35. package/dist/__tests__/types.phase10.property.test.d.ts +1 -0
  36. package/dist/__tests__/types.phase10.property.test.js +166 -0
  37. package/dist/__tests__/types.phase10.test.d.ts +1 -0
  38. package/dist/__tests__/types.phase10.test.js +76 -0
  39. package/dist/__tests__/types.phase3.property.test.d.ts +1 -0
  40. package/dist/__tests__/types.phase3.property.test.js +83 -0
  41. package/dist/__tests__/types.phase3.test.d.ts +1 -0
  42. package/dist/__tests__/types.phase3.test.js +76 -0
  43. package/dist/__tests__/types.phase4.property.test.d.ts +1 -0
  44. package/dist/__tests__/types.phase4.property.test.js +119 -0
  45. package/dist/__tests__/types.phase4.test.d.ts +1 -0
  46. package/dist/__tests__/types.phase4.test.js +70 -0
  47. package/dist/__tests__/types.phase5.property.test.d.ts +1 -0
  48. package/dist/__tests__/types.phase5.property.test.js +120 -0
  49. package/dist/__tests__/types.phase5.test.d.ts +1 -0
  50. package/dist/__tests__/types.phase5.test.js +64 -0
  51. package/dist/__tests__/types.phase6.property.test.d.ts +1 -0
  52. package/dist/__tests__/types.phase6.property.test.js +189 -0
  53. package/dist/__tests__/types.phase6.test.d.ts +1 -0
  54. package/dist/__tests__/types.phase6.test.js +121 -0
  55. package/dist/__tests__/types.phase7.property.test.d.ts +1 -0
  56. package/dist/__tests__/types.phase7.property.test.js +217 -0
  57. package/dist/__tests__/types.phase7.test.d.ts +1 -0
  58. package/dist/__tests__/types.phase7.test.js +106 -0
  59. package/dist/__tests__/types.phase8.property.test.d.ts +1 -0
  60. package/dist/__tests__/types.phase8.property.test.js +224 -0
  61. package/dist/__tests__/types.phase8.test.d.ts +1 -0
  62. package/dist/__tests__/types.phase8.test.js +114 -0
  63. package/dist/__tests__/types.phase9.property.test.d.ts +1 -0
  64. package/dist/__tests__/types.phase9.property.test.js +347 -0
  65. package/dist/__tests__/types.phase9.test.d.ts +1 -0
  66. package/dist/__tests__/types.phase9.test.js +226 -0
  67. package/dist/__tests__/types.restructure.property.test.d.ts +1 -0
  68. package/dist/__tests__/types.restructure.property.test.js +76 -0
  69. package/dist/__tests__/types.test.d.ts +1 -0
  70. package/dist/__tests__/types.test.js +175 -0
  71. package/dist/dom-generators/accordion.d.ts +23 -0
  72. package/dist/dom-generators/avatar.d.ts +19 -0
  73. package/dist/dom-generators/carousel.d.ts +20 -0
  74. package/dist/dom-generators/chip.d.ts +18 -0
  75. package/dist/dom-generators/combobox.d.ts +28 -0
  76. package/dist/dom-generators/date-picker.d.ts +19 -0
  77. package/dist/dom-generators/dom-generators/accordion.d.ts +21 -0
  78. package/dist/dom-generators/dom-generators/avatar.d.ts +17 -0
  79. package/dist/dom-generators/dom-generators/carousel.d.ts +19 -0
  80. package/dist/dom-generators/dom-generators/chip.d.ts +16 -0
  81. package/dist/dom-generators/dom-generators/combobox.d.ts +26 -0
  82. package/dist/dom-generators/dom-generators/date-picker.d.ts +18 -0
  83. package/dist/dom-generators/dom-generators/drawer.d.ts +17 -0
  84. package/dist/dom-generators/dom-generators/dropdown.d.ts +26 -0
  85. package/dist/dom-generators/dom-generators/file-upload.d.ts +20 -0
  86. package/dist/dom-generators/dom-generators/id-generator.d.ts +9 -0
  87. package/dist/dom-generators/dom-generators/index.d.ts +27 -0
  88. package/dist/dom-generators/dom-generators/modal.d.ts +19 -0
  89. package/dist/dom-generators/dom-generators/otp.d.ts +16 -0
  90. package/dist/dom-generators/dom-generators/popover.d.ts +17 -0
  91. package/dist/dom-generators/dom-generators/progress.d.ts +16 -0
  92. package/dist/dom-generators/dom-generators/search.d.ts +20 -0
  93. package/dist/dom-generators/dom-generators/stepper.d.ts +21 -0
  94. package/dist/dom-generators/dom-generators/table.d.ts +23 -0
  95. package/dist/dom-generators/dom-generators/tabs.d.ts +21 -0
  96. package/dist/dom-generators/dom-generators/time-picker.d.ts +18 -0
  97. package/dist/dom-generators/dom-generators/tooltip.d.ts +17 -0
  98. package/dist/dom-generators/dom-generators/types.d.ts +27 -0
  99. package/dist/dom-generators/dom-generators/validate.d.ts +20 -0
  100. package/dist/dom-generators/drawer.d.ts +19 -0
  101. package/dist/dom-generators/dropdown.d.ts +28 -0
  102. package/dist/dom-generators/file-upload.d.ts +22 -0
  103. package/dist/dom-generators/id-generator.d.ts +9 -0
  104. package/dist/dom-generators/index.bundled.d.ts +654 -0
  105. package/dist/dom-generators/index.cjs +2029 -0
  106. package/dist/dom-generators/index.d.ts +27 -0
  107. package/dist/dom-generators/index.mjs +2001 -0
  108. package/dist/dom-generators/modal.d.ts +21 -0
  109. package/dist/dom-generators/otp.d.ts +18 -0
  110. package/dist/dom-generators/popover.d.ts +19 -0
  111. package/dist/dom-generators/progress.d.ts +18 -0
  112. package/dist/dom-generators/search.d.ts +22 -0
  113. package/dist/dom-generators/stepper.d.ts +23 -0
  114. package/dist/dom-generators/table.d.ts +25 -0
  115. package/dist/dom-generators/tabs.d.ts +23 -0
  116. package/dist/dom-generators/time-picker.d.ts +19 -0
  117. package/dist/dom-generators/tooltip.d.ts +19 -0
  118. package/dist/dom-generators/types.d.ts +155 -0
  119. package/dist/dom-generators/validate.d.ts +20 -0
  120. package/dist/runtime/bootstrap.js +59 -0
  121. package/dist/runtime/index.js +55 -0
  122. package/dist/types.d.ts +155 -0
  123. package/dist/types.js +552 -0
  124. package/package.json +12 -2
@@ -0,0 +1,2029 @@
1
+ 'use strict';
2
+
3
+ // ux4g-components-web/src/dom-generators/types.ts
4
+ // Framework-agnostic structural descriptor for DOM generation.
5
+ /**
6
+ * Error thrown when a DOM Generator receives an invalid prop value.
7
+ * Provides detailed context about which component, prop, and value caused the issue.
8
+ */
9
+ class DOMGeneratorValidationError extends Error {
10
+ constructor(componentName, propName, propValue, expectedType) {
11
+ super(`[UX4G DOM Generator] Invalid prop "${propName}" on ${componentName}: ` +
12
+ `received ${JSON.stringify(propValue)}, expected ${expectedType}`);
13
+ this.componentName = componentName;
14
+ this.propName = propName;
15
+ this.propValue = propValue;
16
+ this.expectedType = expectedType;
17
+ this.name = 'DOMGeneratorValidationError';
18
+ }
19
+ }
20
+
21
+ // ux4g-components-web/src/dom-generators/id-generator.ts
22
+ // Monotonically incrementing ID generator for document-unique element IDs.
23
+ let counter = 0;
24
+ /**
25
+ * Generates a unique ID by combining the given prefix with an incrementing counter.
26
+ * Produces IDs like `ux4g-dropdown-1`, `ux4g-tab-panel-2`, etc.
27
+ */
28
+ function generateId(prefix) {
29
+ return `${prefix}-${++counter}`;
30
+ }
31
+ /**
32
+ * Resets the ID counter to zero. Useful for testing to ensure deterministic output.
33
+ */
34
+ function resetIdCounter() {
35
+ counter = 0;
36
+ }
37
+
38
+ // ux4g-components-web/src/dom-generators/validate.ts
39
+ // Prop validation helpers for DOM Generator functions.
40
+ /**
41
+ * Throws if the value is null or undefined.
42
+ */
43
+ function validateRequired(componentName, propName, value) {
44
+ if (value === null || value === undefined) {
45
+ throw new DOMGeneratorValidationError(componentName, propName, value, 'a non-null/undefined value');
46
+ }
47
+ }
48
+ /**
49
+ * Throws if typeof value does not match the expected type string.
50
+ */
51
+ function validateType(componentName, propName, value, expectedType) {
52
+ if (typeof value !== expectedType) {
53
+ throw new DOMGeneratorValidationError(componentName, propName, value, expectedType);
54
+ }
55
+ }
56
+ /**
57
+ * Throws if the value is not a member of the allowed values set.
58
+ */
59
+ function validateEnum(componentName, propName, value, allowedValues) {
60
+ if (!allowedValues.includes(value)) {
61
+ throw new DOMGeneratorValidationError(componentName, propName, value, `one of [${allowedValues.map((v) => JSON.stringify(v)).join(', ')}]`);
62
+ }
63
+ }
64
+ /**
65
+ * Throws if the numeric value is outside the given [min, max] range (inclusive).
66
+ */
67
+ function validateRange(componentName, propName, value, min, max) {
68
+ if (typeof value !== 'number' || value < min || value > max) {
69
+ throw new DOMGeneratorValidationError(componentName, propName, value, `a number between ${min} and ${max}`);
70
+ }
71
+ }
72
+ /**
73
+ * Throws if the value is not an array.
74
+ */
75
+ function validateArray(componentName, propName, value) {
76
+ if (!Array.isArray(value)) {
77
+ throw new DOMGeneratorValidationError(componentName, propName, value, 'an array');
78
+ }
79
+ }
80
+
81
+ // Maps variant → CSS class
82
+ function buildAvatarClasses(avatarType, size, extra) {
83
+ if (avatarType === 'group') {
84
+ return extra ? `ux4g-avatar-group ${extra}` : 'ux4g-avatar-group';
85
+ }
86
+ const parts = ['ux4g-avatar', `ux4g-avatar-${avatarType}`];
87
+ if (size)
88
+ parts.push(`ux4g-avatar-${size}`);
89
+ return parts.join(' ');
90
+ }
91
+ function buildChipClasses(chipType, size, active, extra) {
92
+ // xs is only valid for input chips; filter and choice clamp to sm
93
+ const effectiveSize = (chipType !== 'input' && size === 'xs') ? 'sm' : size;
94
+ const parts = [`ux4g-${chipType}-chip-${effectiveSize}`];
95
+ if (active)
96
+ parts.push('active');
97
+ return parts.join(' ');
98
+ }
99
+ function buildDropdownClasses(type, mode, size, state, open, extra) {
100
+ const parts = [
101
+ 'ux4g-dropdown',
102
+ `ux4g-dropdown-${type}`,
103
+ `ux4g-dropdown-${mode}`,
104
+ `ux4g-dropdown-${size}`,
105
+ `ux4g-dropdown-${state}`,
106
+ ];
107
+ if (open)
108
+ parts.push('is-open');
109
+ if (extra)
110
+ parts.push(extra);
111
+ return parts.join(' ');
112
+ }
113
+ function buildComboboxClasses(type, size, state, open, extra) {
114
+ const parts = [
115
+ 'ux4g-combobox',
116
+ `ux4g-combobox-${type}`,
117
+ `ux4g-combobox-${size}`,
118
+ `ux4g-combobox-${state}`,
119
+ ];
120
+ if (open)
121
+ parts.push('is-open');
122
+ return parts.join(' ');
123
+ }
124
+ function buildModalBackdropClasses(opacity, blur, open, extra) {
125
+ const parts = ['ux4g-modal-backdrop', `ux4g-modal-backdrop-${opacity}`];
126
+ if (blur)
127
+ parts.push('ux4g-modal-backdrop-blur');
128
+ if (open)
129
+ parts.push('is-open');
130
+ return parts.join(' ');
131
+ }
132
+ function buildModalBoxClasses(size, centerContent, extra) {
133
+ const parts = ['ux4g-modal-box', `ux4g-modal-${size}`];
134
+ return parts.join(' ');
135
+ }
136
+ function buildSearchContainerClasses(size, extra) {
137
+ const base = `ux4g-search-container ux4g-search-${size}`;
138
+ return extra ? `${base} ${extra}` : base;
139
+ }
140
+ function buildTableClasses(size, divider, zebra, interactive, sortable, resizable, headerBrand, extra) {
141
+ const parts = ['ux4g-table', `ux4g-table-${size}`];
142
+ if (divider === 'column')
143
+ parts.push('ux4g-table-column-dividers');
144
+ else if (divider === 'none')
145
+ parts.push('ux4g-table-no-row-dividers');
146
+ if (zebra === 'rows')
147
+ parts.push('ux4g-table-zebra-rows');
148
+ else if (zebra === 'cols')
149
+ parts.push('ux4g-table-zebra-cols');
150
+ if (interactive)
151
+ parts.push('ux4g-table-interactive');
152
+ if (sortable)
153
+ parts.push('ux4g-table-sortable');
154
+ return parts.join(' ');
155
+ }
156
+ function buildPopoverClasses(placement, show, extra) {
157
+ const parts = ['ux4g-popover', `ux4g-popover-${placement}`];
158
+ if (show)
159
+ parts.push('show');
160
+ return parts.join(' ');
161
+ }
162
+ function buildTooltipClasses(placement, size, extra) {
163
+ const parts = ['ux4g-tooltip', `ux4g-tooltip-${placement}`, `ux4g-tooltip-${size}`];
164
+ return parts.join(' ');
165
+ }
166
+ function buildTabClasses(variant, size, vertical, extra) {
167
+ const parts = ['ux4g-tab', `ux4g-tab-${variant}`, `ux4g-tab-${size}`];
168
+ if (vertical)
169
+ parts.push('ux4g-tab-vertical');
170
+ return parts.join(' ');
171
+ }
172
+ function buildAccordionClasses(arrowPosition, variant, extra) {
173
+ const parts = ['ux4g-accordion'];
174
+ if (arrowPosition === 'right')
175
+ parts.push('ux4g-accordion-arrow-right');
176
+ else if (arrowPosition === 'left')
177
+ parts.push('ux4g-accordion-arrow-left');
178
+ if (variant === 'bordered')
179
+ parts.push('ux4g-accordion-bordered');
180
+ return parts.join(' ');
181
+ }
182
+ function buildStepperClasses(orientation, alignment, variant, size, extra) {
183
+ const parts = ['ux4g-stepper'];
184
+ if (orientation === 'vertical') {
185
+ parts.push('ux4g-stepper-vertical');
186
+ }
187
+ if (variant === 'bottom-line')
188
+ parts.push('ux4g-stepper-bottom-line');
189
+ else if (variant === 'bottom-line-fill') {
190
+ parts.push('ux4g-stepper-bottom-line');
191
+ parts.push('ux4g-stepper-bottom-line-fill');
192
+ }
193
+ else if (variant === 'mobile')
194
+ parts.push('ux4g-stepper-mobile');
195
+ else if (variant === 'progress')
196
+ parts.push('ux4g-stepper-progress');
197
+ if (size === 's')
198
+ parts.push('ux4g-stepper-s');
199
+ return parts.join(' ');
200
+ }
201
+ function buildDrawerClasses(placement, open, extra) {
202
+ const parts = ['ux4g-drawer', `ux4g-drawer-${placement}`];
203
+ if (open)
204
+ parts.push('ux4g-drawer-open');
205
+ return parts.join(' ');
206
+ }
207
+ function buildDateTimePickerClasses(mode, extra) {
208
+ const base = mode === 'date' ? 'ux4g-date-picker-container' : 'ux4g-time-picker-container';
209
+ return extra ? `${base} ${extra}` : base;
210
+ }
211
+ function buildOtpInputClasses(state, extra) {
212
+ const parts = ['ux4g-otp'];
213
+ if (state && state !== 'default')
214
+ parts.push(`ux4g-otp-${state}`);
215
+ return parts.join(' ');
216
+ }
217
+ function buildFileUploadClasses(state, extra) {
218
+ const parts = ['ux4g-upload'];
219
+ if (state)
220
+ parts.push(`ux4g-upload-state-${state}`);
221
+ return parts.join(' ');
222
+ }
223
+ function buildProgressIndicatorClasses(type, extra) {
224
+ const base = type === 'bar' ? 'ux4g-progress-bar' : 'ux4g-progress-circle';
225
+ return base;
226
+ }
227
+ // -- Carousel -----------------------------------------------------------------
228
+ function buildCarouselClasses(extra) {
229
+ return extra ? `ux4g-carousel ${extra}` : 'ux4g-carousel';
230
+ }
231
+
232
+ // ux4g-components-web/src/dom-generators/modal.ts
233
+ // Framework-agnostic DOM Generator for the Modal component.
234
+ /**
235
+ * Generates a complete Modal DOM descriptor tree.
236
+ *
237
+ * Produces a backdrop element and a modal dialog box with `role="dialog"`,
238
+ * `aria-modal="true"`, an optional close button with `data-ux4g-modal-close`,
239
+ * and an optional title heading. Compatible with Runtime_JS
240
+ * MutationObserver-based initialization.
241
+ */
242
+ function generateModalDOM(props) {
243
+ const { open = false, size = 'm', title, closable = true, backdropOpacity = '50', backdropBlur = false, } = props;
244
+ // Generate unique IDs
245
+ const modalId = generateId('ux4g-modal');
246
+ const titleId = generateId('ux4g-modal-title');
247
+ // Build backdrop element
248
+ const backdropClasses = buildModalBackdropClasses(backdropOpacity, backdropBlur, open);
249
+ const backdropNode = {
250
+ tag: 'div',
251
+ props: {
252
+ className: backdropClasses,
253
+ },
254
+ children: [],
255
+ };
256
+ // Build modal box children
257
+ const modalBoxChildren = [];
258
+ // Close button (if closable)
259
+ if (closable) {
260
+ const closeButton = {
261
+ tag: 'button',
262
+ props: {
263
+ className: 'ux4g-modal-close',
264
+ type: 'button',
265
+ 'data-ux4g-modal-close': 'true',
266
+ 'aria-label': 'Close',
267
+ },
268
+ children: [],
269
+ };
270
+ modalBoxChildren.push(closeButton);
271
+ }
272
+ // Title heading (if title provided)
273
+ if (title) {
274
+ const titleNode = {
275
+ tag: 'h2',
276
+ props: {
277
+ className: 'ux4g-modal-title',
278
+ id: titleId,
279
+ },
280
+ children: [title],
281
+ };
282
+ modalBoxChildren.push(titleNode);
283
+ }
284
+ // Build modal box element
285
+ const modalBoxClasses = buildModalBoxClasses(size);
286
+ const modalBoxProps = {
287
+ className: modalBoxClasses,
288
+ id: modalId,
289
+ role: 'dialog',
290
+ 'aria-modal': 'true',
291
+ };
292
+ // Link title for accessible labelling
293
+ if (title) {
294
+ modalBoxProps['aria-labelledby'] = titleId;
295
+ }
296
+ const modalBoxNode = {
297
+ tag: 'div',
298
+ props: modalBoxProps,
299
+ children: modalBoxChildren,
300
+ };
301
+ // Build root wrapper containing backdrop and modal box
302
+ const rootNode = {
303
+ tag: 'div',
304
+ props: {
305
+ className: 'ux4g-modal',
306
+ },
307
+ children: [backdropNode, modalBoxNode],
308
+ };
309
+ return rootNode;
310
+ }
311
+
312
+ // ux4g-components-web/src/dom-generators/carousel.ts
313
+ // Framework-agnostic DOM Generator for the Carousel component.
314
+ /**
315
+ * Generates a complete Carousel DOM descriptor tree.
316
+ *
317
+ * Produces a root element with `aria-roledescription="carousel"`, slide items with
318
+ * `role="group"` and `aria-roledescription="slide"`, navigation buttons with
319
+ * `data-ux4g-carousel-prev` and `data-ux4g-carousel-next` for Runtime_JS, and
320
+ * indicator dots for slide navigation.
321
+ */
322
+ function generateCarouselDOM(props) {
323
+ const { slides, activeIndex = 0, showIndicators = true, showNavigation = true, } = props;
324
+ // Validate required props
325
+ validateArray('Carousel', 'slides', slides);
326
+ // Generate unique IDs for each slide
327
+ const slideIds = slides.map(() => generateId('ux4g-carousel-slide'));
328
+ // Build slide items
329
+ const slideItems = slides.map((slide, index) => {
330
+ const isActive = index === activeIndex;
331
+ return {
332
+ tag: 'div',
333
+ props: {
334
+ className: 'ux4g-carousel-slide',
335
+ id: slideIds[index],
336
+ role: 'group',
337
+ 'aria-roledescription': 'slide',
338
+ 'aria-label': slide.label || `Slide ${index + 1}`,
339
+ ...(isActive ? {} : { 'aria-hidden': 'true' }),
340
+ },
341
+ children: [slide.content],
342
+ };
343
+ });
344
+ // Build slide container
345
+ const slideContainer = {
346
+ tag: 'div',
347
+ props: {
348
+ className: 'ux4g-carousel-slides',
349
+ },
350
+ children: slideItems,
351
+ };
352
+ // Build root children
353
+ const rootChildren = [slideContainer];
354
+ // Add navigation buttons if enabled
355
+ if (showNavigation) {
356
+ const prevButton = {
357
+ tag: 'button',
358
+ props: {
359
+ className: 'ux4g-carousel-prev',
360
+ type: 'button',
361
+ 'data-ux4g-carousel-prev': 'true',
362
+ 'aria-label': 'Previous slide',
363
+ },
364
+ children: [],
365
+ };
366
+ const nextButton = {
367
+ tag: 'button',
368
+ props: {
369
+ className: 'ux4g-carousel-next',
370
+ type: 'button',
371
+ 'data-ux4g-carousel-next': 'true',
372
+ 'aria-label': 'Next slide',
373
+ },
374
+ children: [],
375
+ };
376
+ rootChildren.push(prevButton, nextButton);
377
+ }
378
+ // Add indicator dots if enabled
379
+ if (showIndicators) {
380
+ const indicatorDots = slides.map((_, index) => {
381
+ const isActive = index === activeIndex;
382
+ return {
383
+ tag: 'button',
384
+ props: {
385
+ className: 'ux4g-carousel-indicator',
386
+ type: 'button',
387
+ 'aria-label': `Go to slide ${index + 1}`,
388
+ ...(isActive ? { 'aria-current': 'true' } : {}),
389
+ },
390
+ children: [],
391
+ };
392
+ });
393
+ const indicatorContainer = {
394
+ tag: 'div',
395
+ props: {
396
+ className: 'ux4g-carousel-indicators',
397
+ role: 'tablist',
398
+ },
399
+ children: indicatorDots,
400
+ };
401
+ rootChildren.push(indicatorContainer);
402
+ }
403
+ // Build root element
404
+ const rootNode = {
405
+ tag: 'div',
406
+ props: {
407
+ className: buildCarouselClasses(),
408
+ 'aria-roledescription': 'carousel',
409
+ 'aria-label': 'Carousel',
410
+ },
411
+ children: rootChildren,
412
+ };
413
+ return rootNode;
414
+ }
415
+
416
+ // ux4g-components-web/src/dom-generators/dropdown.ts
417
+ // Framework-agnostic DOM Generator for the Dropdown component.
418
+ /**
419
+ * Generates a complete Dropdown DOM descriptor tree.
420
+ *
421
+ * Produces a trigger button with `data-ux4g-dropdown-toggle`, a menu container
422
+ * with `role="listbox"`, and option items with `role="option"` and `aria-selected`.
423
+ * Compatible with Runtime_JS MutationObserver-based initialization.
424
+ */
425
+ function generateDropdownDOM(props) {
426
+ const { options, value, placeholder = 'Select...', searchable = false, multiple = false, disabled = false, size = 'md', state = 'default', open = false, } = props;
427
+ // Validate required props
428
+ validateRequired('Dropdown', 'options', options);
429
+ validateArray('Dropdown', 'options', options);
430
+ // Generate unique IDs
431
+ const triggerId = generateId('ux4g-dropdown-trigger');
432
+ const menuId = generateId('ux4g-dropdown-menu');
433
+ // Determine the mode based on multiple prop
434
+ const mode = multiple ? 'multi' : 'single';
435
+ // Build CSS classes via Class Builder
436
+ const dropdownClasses = buildDropdownClasses('selection', mode, size, state, open, disabled ? 'ux4g-dropdown-disabled' : undefined);
437
+ // Determine display text for the trigger
438
+ const selectedValues = Array.isArray(value) ? value : value != null ? [value] : [];
439
+ const selectedLabels = options
440
+ .filter((opt) => selectedValues.includes(opt.value))
441
+ .map((opt) => opt.label);
442
+ const displayText = selectedLabels.length > 0 ? selectedLabels.join(', ') : placeholder;
443
+ // Build trigger button
444
+ const triggerNode = {
445
+ tag: 'button',
446
+ props: {
447
+ className: 'ux4g-dropdown-trigger',
448
+ id: triggerId,
449
+ 'data-ux4g-dropdown-toggle': 'true',
450
+ 'aria-haspopup': 'listbox',
451
+ 'aria-expanded': String(open),
452
+ 'aria-controls': menuId,
453
+ type: 'button',
454
+ ...(disabled ? { 'aria-disabled': 'true', disabled: true } : {}),
455
+ },
456
+ children: [displayText],
457
+ };
458
+ // Build option items
459
+ const optionItems = options.map((opt) => {
460
+ const isSelected = selectedValues.includes(opt.value);
461
+ const optionProps = {
462
+ className: 'ux4g-dropdown-option',
463
+ role: 'option',
464
+ 'aria-selected': String(isSelected),
465
+ 'data-value': opt.value,
466
+ };
467
+ if (opt.disabled) {
468
+ optionProps['aria-disabled'] = 'true';
469
+ }
470
+ return {
471
+ tag: 'div',
472
+ props: optionProps,
473
+ children: [opt.label],
474
+ };
475
+ });
476
+ // Build menu children (search input if searchable, then options)
477
+ const menuChildren = [];
478
+ if (searchable) {
479
+ const searchInput = {
480
+ tag: 'input',
481
+ props: {
482
+ className: 'ux4g-dropdown-search',
483
+ type: 'text',
484
+ role: 'searchbox',
485
+ 'aria-label': 'Filter options',
486
+ placeholder: 'Search...',
487
+ },
488
+ children: [],
489
+ };
490
+ menuChildren.push(searchInput);
491
+ }
492
+ menuChildren.push(...optionItems);
493
+ // Build menu container
494
+ const menuNode = {
495
+ tag: 'div',
496
+ props: {
497
+ className: 'ux4g-dropdown-menu',
498
+ id: menuId,
499
+ role: 'listbox',
500
+ 'aria-multiselectable': multiple ? 'true' : undefined,
501
+ },
502
+ children: menuChildren,
503
+ };
504
+ // Build root container
505
+ const rootNode = {
506
+ tag: 'div',
507
+ props: {
508
+ className: dropdownClasses,
509
+ },
510
+ children: [triggerNode, menuNode],
511
+ };
512
+ return rootNode;
513
+ }
514
+
515
+ // ux4g-components-web/src/dom-generators/tabs.ts
516
+ // Framework-agnostic DOM Generator for the Tabs component.
517
+ /**
518
+ * Generates a complete Tabs DOM descriptor tree.
519
+ *
520
+ * Produces a tablist container with `role="tablist"`, individual tabs with
521
+ * `role="tab"` and `aria-selected`, and corresponding panels with
522
+ * `role="tabpanel"` and `aria-labelledby`.
523
+ */
524
+ function generateTabsDOM(props) {
525
+ const { tabs, activeIndex = 0, variant = 'underline', size = 'md', vertical = false, } = props;
526
+ // Validate required props
527
+ validateArray('Tabs', 'tabs', tabs);
528
+ // Generate paired IDs for each tab/panel
529
+ const tabIds = [];
530
+ const panelIds = [];
531
+ for (let i = 0; i < tabs.length; i++) {
532
+ tabIds.push(generateId('ux4g-tab'));
533
+ panelIds.push(generateId('ux4g-tab-panel'));
534
+ }
535
+ // Build tab elements
536
+ const tabNodes = tabs.map((tab, index) => {
537
+ const isActive = index === activeIndex;
538
+ const tabProps = {
539
+ className: 'ux4g-tab-item',
540
+ id: tabIds[index],
541
+ role: 'tab',
542
+ 'aria-selected': String(isActive),
543
+ 'aria-controls': panelIds[index],
544
+ tabIndex: isActive ? 0 : -1,
545
+ };
546
+ if (tab.disabled) {
547
+ tabProps['aria-disabled'] = 'true';
548
+ }
549
+ return {
550
+ tag: 'button',
551
+ props: tabProps,
552
+ children: [tab.label],
553
+ };
554
+ });
555
+ // Build tablist container
556
+ const tablistNode = {
557
+ tag: 'div',
558
+ props: {
559
+ className: 'ux4g-tablist',
560
+ role: 'tablist',
561
+ 'aria-orientation': vertical ? 'vertical' : 'horizontal',
562
+ },
563
+ children: tabNodes,
564
+ };
565
+ // Build panel elements
566
+ const panelNodes = tabs.map((tab, index) => {
567
+ const isActive = index === activeIndex;
568
+ const panelProps = {
569
+ className: 'ux4g-tab-panel',
570
+ id: panelIds[index],
571
+ role: 'tabpanel',
572
+ 'aria-labelledby': tabIds[index],
573
+ tabIndex: 0,
574
+ };
575
+ if (!isActive) {
576
+ panelProps['aria-hidden'] = 'true';
577
+ }
578
+ return {
579
+ tag: 'div',
580
+ props: panelProps,
581
+ children: [tab.content],
582
+ };
583
+ });
584
+ // Build CSS classes via Class Builder
585
+ const rootClasses = buildTabClasses(variant, size, vertical);
586
+ // Build root container
587
+ const rootNode = {
588
+ tag: 'div',
589
+ props: {
590
+ className: rootClasses,
591
+ },
592
+ children: [tablistNode, ...panelNodes],
593
+ };
594
+ return rootNode;
595
+ }
596
+
597
+ // ux4g-components-web/src/dom-generators/accordion.ts
598
+ // DOM Generator for the Accordion component.
599
+ /**
600
+ * Generates a complete Accordion DOM descriptor tree.
601
+ *
602
+ * Produces headers with `data-ux4g-accordion-toggle` for Runtime_JS,
603
+ * panels with unique IDs, aria-expanded on toggles, aria-controls
604
+ * referencing panel IDs, and role="region" on panels.
605
+ */
606
+ function generateAccordionDOM(props) {
607
+ const { items, arrowPosition, variant } = props;
608
+ validateArray('Accordion', 'items', items);
609
+ const accordionItems = items.map((item) => {
610
+ const headerId = generateId('ux4g-accordion-header');
611
+ const panelId = generateId('ux4g-accordion-panel');
612
+ const isExpanded = item.expanded ?? false;
613
+ const headerButtonProps = {
614
+ className: 'ux4g-accordion-button',
615
+ id: headerId,
616
+ 'data-ux4g-accordion-toggle': 'true',
617
+ 'aria-expanded': String(isExpanded),
618
+ 'aria-controls': panelId,
619
+ };
620
+ if (item.disabled) {
621
+ headerButtonProps['aria-disabled'] = 'true';
622
+ }
623
+ const headerButton = {
624
+ tag: 'button',
625
+ props: headerButtonProps,
626
+ children: [item.header],
627
+ };
628
+ const header = {
629
+ tag: 'div',
630
+ props: {
631
+ className: 'ux4g-accordion-header',
632
+ },
633
+ children: [headerButton],
634
+ };
635
+ const panel = {
636
+ tag: 'div',
637
+ props: {
638
+ className: 'ux4g-accordion-panel',
639
+ id: panelId,
640
+ role: 'region',
641
+ 'aria-labelledby': headerId,
642
+ },
643
+ children: [item.content],
644
+ };
645
+ return {
646
+ tag: 'div',
647
+ props: {
648
+ className: 'ux4g-accordion-item',
649
+ },
650
+ children: [header, panel],
651
+ };
652
+ });
653
+ return {
654
+ tag: 'div',
655
+ props: {
656
+ className: buildAccordionClasses(arrowPosition, variant),
657
+ },
658
+ children: accordionItems,
659
+ };
660
+ }
661
+
662
+ // ux4g-components-web/src/dom-generators/time-picker.ts
663
+ // Framework-agnostic DOM Generator for the TimePicker component.
664
+ /**
665
+ * Parses a time string in HH:mm format and returns hour and minute values.
666
+ * Returns defaults (0, 0) for invalid or missing values.
667
+ */
668
+ function parseTimeValue(value) {
669
+ if (!value) {
670
+ return { hour: 0, minute: 0 };
671
+ }
672
+ const parts = value.split(':');
673
+ if (parts.length < 2) {
674
+ return { hour: 0, minute: 0 };
675
+ }
676
+ const hour = parseInt(parts[0], 10);
677
+ const minute = parseInt(parts[1], 10);
678
+ return {
679
+ hour: isNaN(hour) ? 0 : hour,
680
+ minute: isNaN(minute) ? 0 : minute,
681
+ };
682
+ }
683
+ /**
684
+ * Generates a complete TimePicker DOM descriptor tree.
685
+ *
686
+ * Produces hour, minute, and (for 12h format) period spinbutton elements
687
+ * with appropriate ARIA attributes: `role="spinbutton"`, `aria-valuemin`,
688
+ * `aria-valuemax`, `aria-valuenow`, and `aria-label`.
689
+ *
690
+ * The root element uses CSS classes from `buildDateTimePickerClasses('time')`.
691
+ */
692
+ function generateTimePickerDOM(props) {
693
+ const { value, format = '24h', } = props;
694
+ const { hour, minute } = parseTimeValue(value);
695
+ // Generate unique IDs for each spinbutton
696
+ const hourId = generateId('ux4g-time-picker-hour');
697
+ const minuteId = generateId('ux4g-time-picker-minute');
698
+ // Determine hour range based on format
699
+ const is12h = format === '12h';
700
+ const hourMin = is12h ? 1 : 0;
701
+ const hourMax = is12h ? 12 : 23;
702
+ // Convert 24h hour to 12h display value
703
+ let displayHour = hour;
704
+ let periodValue = 0; // 0 = AM, 1 = PM
705
+ if (is12h) {
706
+ periodValue = hour >= 12 ? 1 : 0;
707
+ displayHour = hour % 12;
708
+ if (displayHour === 0) {
709
+ displayHour = 12;
710
+ }
711
+ }
712
+ // Build hour spinbutton
713
+ const hourSpinbutton = {
714
+ tag: 'div',
715
+ props: {
716
+ className: 'ux4g-time-picker-hour',
717
+ id: hourId,
718
+ role: 'spinbutton',
719
+ tabIndex: 0,
720
+ 'aria-valuemin': hourMin,
721
+ 'aria-valuemax': hourMax,
722
+ 'aria-valuenow': displayHour,
723
+ 'aria-label': 'Hours',
724
+ },
725
+ children: [String(displayHour)],
726
+ };
727
+ // Build minute spinbutton
728
+ const minuteSpinbutton = {
729
+ tag: 'div',
730
+ props: {
731
+ className: 'ux4g-time-picker-minute',
732
+ id: minuteId,
733
+ role: 'spinbutton',
734
+ tabIndex: 0,
735
+ 'aria-valuemin': 0,
736
+ 'aria-valuemax': 59,
737
+ 'aria-valuenow': minute,
738
+ 'aria-label': 'Minutes',
739
+ },
740
+ children: [String(minute)],
741
+ };
742
+ // Build children array
743
+ const children = [hourSpinbutton, minuteSpinbutton];
744
+ // For 12h format, add period selector
745
+ if (is12h) {
746
+ const periodId = generateId('ux4g-time-picker-period');
747
+ const periodSpinbutton = {
748
+ tag: 'div',
749
+ props: {
750
+ className: 'ux4g-time-picker-period',
751
+ id: periodId,
752
+ role: 'spinbutton',
753
+ tabIndex: 0,
754
+ 'aria-valuemin': 0,
755
+ 'aria-valuemax': 1,
756
+ 'aria-valuenow': periodValue,
757
+ 'aria-label': 'Period',
758
+ },
759
+ children: [periodValue === 0 ? 'AM' : 'PM'],
760
+ };
761
+ children.push(periodSpinbutton);
762
+ }
763
+ // Build root element with class from buildDateTimePickerClasses('time')
764
+ const rootNode = {
765
+ tag: 'div',
766
+ props: {
767
+ className: buildDateTimePickerClasses('time'),
768
+ },
769
+ children,
770
+ };
771
+ return rootNode;
772
+ }
773
+
774
+ // ux4g-components-web/src/dom-generators/search.ts
775
+ // Framework-agnostic DOM Generator for the Search component.
776
+ /**
777
+ * Generates a complete Search DOM descriptor tree.
778
+ *
779
+ * Produces a search input with `role="searchbox"`, a clear button visible when
780
+ * there is a value, and a suggestions listbox with `role="listbox"`.
781
+ * Compatible with Runtime_JS MutationObserver-based initialization.
782
+ */
783
+ function generateSearchDOM(props) {
784
+ const { value = '', placeholder = 'Search...', suggestions = [], showSuggestions = false, size = 'm', } = props;
785
+ // Generate unique IDs
786
+ const inputId = generateId('ux4g-search-input');
787
+ const suggestionsId = generateId('ux4g-search-suggestions');
788
+ // Build CSS classes via Class Builder
789
+ const containerClasses = buildSearchContainerClasses(size);
790
+ // Determine if suggestions should be visible
791
+ const hasSuggestions = suggestions.length > 0;
792
+ const suggestionsVisible = showSuggestions && hasSuggestions;
793
+ // Build search input
794
+ const inputProps = {
795
+ className: 'ux4g-search-input',
796
+ id: inputId,
797
+ role: 'searchbox',
798
+ 'aria-label': 'Search',
799
+ placeholder,
800
+ type: 'text',
801
+ };
802
+ if (hasSuggestions) {
803
+ inputProps['aria-controls'] = suggestionsId;
804
+ }
805
+ inputProps['aria-expanded'] = String(suggestionsVisible);
806
+ if (value) {
807
+ inputProps['value'] = value;
808
+ }
809
+ const inputNode = {
810
+ tag: 'input',
811
+ props: inputProps,
812
+ children: [],
813
+ };
814
+ // Build clear button (visible when value has content)
815
+ const clearButtonNode = {
816
+ tag: 'button',
817
+ props: {
818
+ className: 'ux4g-search-clear',
819
+ type: 'button',
820
+ 'aria-label': 'Clear search',
821
+ ...(value ? {} : { 'aria-hidden': 'true' }),
822
+ },
823
+ children: ['×'],
824
+ };
825
+ // Build suggestion items
826
+ const suggestionItems = suggestions.map((suggestion) => ({
827
+ tag: 'div',
828
+ props: {
829
+ className: 'ux4g-search-suggestion',
830
+ role: 'option',
831
+ 'data-value': suggestion.value,
832
+ },
833
+ children: [suggestion.label],
834
+ }));
835
+ // Build suggestions listbox
836
+ const suggestionsNode = {
837
+ tag: 'div',
838
+ props: {
839
+ className: 'ux4g-search-suggestions',
840
+ id: suggestionsId,
841
+ role: 'listbox',
842
+ 'aria-label': 'Search suggestions',
843
+ ...(!suggestionsVisible ? { 'aria-hidden': 'true' } : {}),
844
+ },
845
+ children: suggestionItems,
846
+ };
847
+ // Build root container
848
+ const rootNode = {
849
+ tag: 'div',
850
+ props: {
851
+ className: containerClasses,
852
+ },
853
+ children: [inputNode, clearButtonNode, suggestionsNode],
854
+ };
855
+ return rootNode;
856
+ }
857
+
858
+ // ux4g-components-web/src/dom-generators/drawer.ts
859
+ // Framework-agnostic DOM Generator for the Drawer component.
860
+ /**
861
+ * Generates a complete Drawer DOM descriptor tree.
862
+ *
863
+ * Produces a backdrop element and a drawer panel with `role="dialog"`,
864
+ * `aria-modal="true"`, an optional close button with `data-ux4g-drawer-close`,
865
+ * and an optional title heading. Compatible with Runtime_JS
866
+ * MutationObserver-based initialization.
867
+ */
868
+ function generateDrawerDOM(props) {
869
+ const { open = false, placement = 'right', title, closable = true, } = props;
870
+ // Generate unique IDs
871
+ const drawerId = generateId('ux4g-drawer');
872
+ const titleId = generateId('ux4g-drawer-title');
873
+ // Build backdrop element
874
+ const backdropNode = {
875
+ tag: 'div',
876
+ props: {
877
+ className: 'ux4g-drawer-backdrop',
878
+ },
879
+ children: [],
880
+ };
881
+ // Build drawer panel children
882
+ const drawerPanelChildren = [];
883
+ // Close button (if closable)
884
+ if (closable) {
885
+ const closeButton = {
886
+ tag: 'button',
887
+ props: {
888
+ className: 'ux4g-drawer-close',
889
+ type: 'button',
890
+ 'data-ux4g-drawer-close': 'true',
891
+ 'aria-label': 'Close',
892
+ },
893
+ children: [],
894
+ };
895
+ drawerPanelChildren.push(closeButton);
896
+ }
897
+ // Title heading (if title provided)
898
+ if (title) {
899
+ const titleNode = {
900
+ tag: 'h2',
901
+ props: {
902
+ className: 'ux4g-drawer-title',
903
+ id: titleId,
904
+ },
905
+ children: [title],
906
+ };
907
+ drawerPanelChildren.push(titleNode);
908
+ }
909
+ // Build drawer panel element
910
+ const drawerPanelClasses = buildDrawerClasses(placement, open);
911
+ const drawerPanelProps = {
912
+ className: drawerPanelClasses,
913
+ id: drawerId,
914
+ role: 'dialog',
915
+ 'aria-modal': 'true',
916
+ };
917
+ // Link title for accessible labelling
918
+ if (title) {
919
+ drawerPanelProps['aria-labelledby'] = titleId;
920
+ }
921
+ const drawerPanelNode = {
922
+ tag: 'div',
923
+ props: drawerPanelProps,
924
+ children: drawerPanelChildren,
925
+ };
926
+ // Build root wrapper containing backdrop + drawer panel
927
+ const rootNode = {
928
+ tag: 'div',
929
+ props: {
930
+ className: 'ux4g-drawer-wrapper',
931
+ },
932
+ children: [backdropNode, drawerPanelNode],
933
+ };
934
+ return rootNode;
935
+ }
936
+
937
+ // ux4g-components-web/src/dom-generators/combobox.ts
938
+ // Framework-agnostic DOM Generator for the Combobox component.
939
+ /**
940
+ * Generates a complete Combobox DOM descriptor tree.
941
+ *
942
+ * Produces an input with `role="combobox"` and `aria-autocomplete="list"`,
943
+ * a listbox with `role="listbox"`, and option items with `role="option"`.
944
+ * Supports `aria-activedescendant` pointing to the first selected option.
945
+ * Compatible with Runtime_JS MutationObserver-based initialization.
946
+ */
947
+ function generateComboboxDOM(props) {
948
+ const { options, value, inputValue = '', placeholder = '', multiple = false, disabled = false, size = 'md', state = 'default', open = false, } = props;
949
+ // Validate required props
950
+ validateRequired('Combobox', 'options', options);
951
+ validateArray('Combobox', 'options', options);
952
+ // Generate unique IDs
953
+ const inputId = generateId('ux4g-combobox-input');
954
+ const listboxId = generateId('ux4g-combobox-listbox');
955
+ // Determine selected values
956
+ const selectedValues = Array.isArray(value) ? value : value != null ? [value] : [];
957
+ // Generate option IDs and find active descendant
958
+ const optionIds = options.map(() => generateId('ux4g-combobox-option'));
959
+ // Active descendant is the first selected option's ID (if any)
960
+ let activeDescendantId;
961
+ for (let i = 0; i < options.length; i++) {
962
+ if (selectedValues.includes(options[i].value)) {
963
+ activeDescendantId = optionIds[i];
964
+ break;
965
+ }
966
+ }
967
+ // Determine combobox type based on multiple prop
968
+ const type = multiple ? 'multi' : 'single';
969
+ // Build CSS classes via Class Builder
970
+ const comboboxClasses = buildComboboxClasses(type, size, state, open);
971
+ // Build input element
972
+ const inputNode = {
973
+ tag: 'input',
974
+ props: {
975
+ className: 'ux4g-combobox-input',
976
+ id: inputId,
977
+ role: 'combobox',
978
+ 'aria-autocomplete': 'list',
979
+ 'aria-expanded': String(open),
980
+ 'aria-controls': listboxId,
981
+ ...(activeDescendantId ? { 'aria-activedescendant': activeDescendantId } : {}),
982
+ ...(placeholder ? { placeholder } : {}),
983
+ ...(inputValue ? { value: inputValue } : {}),
984
+ type: 'text',
985
+ ...(disabled ? { 'aria-disabled': 'true', disabled: true } : {}),
986
+ },
987
+ children: [],
988
+ };
989
+ // Build option items
990
+ const optionItems = options.map((opt, index) => {
991
+ const isSelected = selectedValues.includes(opt.value);
992
+ const optionProps = {
993
+ className: 'ux4g-combobox-option',
994
+ id: optionIds[index],
995
+ role: 'option',
996
+ 'aria-selected': String(isSelected),
997
+ 'data-value': opt.value,
998
+ };
999
+ if (opt.disabled) {
1000
+ optionProps['aria-disabled'] = 'true';
1001
+ }
1002
+ return {
1003
+ tag: 'div',
1004
+ props: optionProps,
1005
+ children: [opt.label],
1006
+ };
1007
+ });
1008
+ // Build listbox container
1009
+ const listboxNode = {
1010
+ tag: 'div',
1011
+ props: {
1012
+ className: 'ux4g-combobox-listbox',
1013
+ id: listboxId,
1014
+ role: 'listbox',
1015
+ ...(multiple ? { 'aria-multiselectable': 'true' } : {}),
1016
+ },
1017
+ children: optionItems,
1018
+ };
1019
+ // Build root container
1020
+ const rootNode = {
1021
+ tag: 'div',
1022
+ props: {
1023
+ className: comboboxClasses,
1024
+ },
1025
+ children: [inputNode, listboxNode],
1026
+ };
1027
+ return rootNode;
1028
+ }
1029
+
1030
+ // ux4g-components-web/src/dom-generators/date-picker.ts
1031
+ // Framework-agnostic DOM Generator for the DatePicker component.
1032
+ /**
1033
+ * Returns the number of days in a given month/year.
1034
+ */
1035
+ function getDaysInMonth(year, month) {
1036
+ return new Date(year, month + 1, 0).getDate();
1037
+ }
1038
+ /**
1039
+ * Returns the day of the week (0=Sun, 6=Sat) for the first day of the given month.
1040
+ */
1041
+ function getFirstDayOfWeek(year, month) {
1042
+ return new Date(year, month, 1).getDay();
1043
+ }
1044
+ /**
1045
+ * Formats a date as a full human-readable string for aria-label (e.g., "January 15, 2024").
1046
+ */
1047
+ function formatDateLabel(year, month, day, locale) {
1048
+ const date = new Date(year, month, day);
1049
+ try {
1050
+ return date.toLocaleDateString(locale, {
1051
+ year: 'numeric',
1052
+ month: 'long',
1053
+ day: 'numeric',
1054
+ });
1055
+ }
1056
+ catch {
1057
+ // Fallback for invalid locale
1058
+ return date.toLocaleDateString('en-US', {
1059
+ year: 'numeric',
1060
+ month: 'long',
1061
+ day: 'numeric',
1062
+ });
1063
+ }
1064
+ }
1065
+ /**
1066
+ * Returns the month name for display in the header.
1067
+ */
1068
+ function getMonthName(year, month, locale) {
1069
+ const date = new Date(year, month, 1);
1070
+ try {
1071
+ return date.toLocaleDateString(locale, { month: 'long' });
1072
+ }
1073
+ catch {
1074
+ return date.toLocaleDateString('en-US', { month: 'long' });
1075
+ }
1076
+ }
1077
+ /**
1078
+ * Returns the abbreviated weekday names starting from Sunday.
1079
+ */
1080
+ function getWeekdayHeaders(locale) {
1081
+ const weekdays = [];
1082
+ // Jan 4, 2024 is a Thursday — use a known Sunday: Jan 7, 2024
1083
+ const baseSunday = new Date(2024, 0, 7); // Sunday
1084
+ for (let i = 0; i < 7; i++) {
1085
+ const d = new Date(baseSunday);
1086
+ d.setDate(baseSunday.getDate() + i);
1087
+ try {
1088
+ weekdays.push(d.toLocaleDateString(locale, { weekday: 'short' }));
1089
+ }
1090
+ catch {
1091
+ weekdays.push(d.toLocaleDateString('en-US', { weekday: 'short' }));
1092
+ }
1093
+ }
1094
+ return weekdays;
1095
+ }
1096
+ /**
1097
+ * Checks if a date string (YYYY-MM-DD) represents a date that is outside the min/max range.
1098
+ */
1099
+ function isDateDisabled(year, month, day, min, max) {
1100
+ const dateValue = new Date(year, month, day).getTime();
1101
+ if (min) {
1102
+ const minParts = min.split('-');
1103
+ const minDate = new Date(parseInt(minParts[0], 10), parseInt(minParts[1], 10) - 1, parseInt(minParts[2], 10)).getTime();
1104
+ if (dateValue < minDate)
1105
+ return true;
1106
+ }
1107
+ if (max) {
1108
+ const maxParts = max.split('-');
1109
+ const maxDate = new Date(parseInt(maxParts[0], 10), parseInt(maxParts[1], 10) - 1, parseInt(maxParts[2], 10)).getTime();
1110
+ if (dateValue > maxDate)
1111
+ return true;
1112
+ }
1113
+ return false;
1114
+ }
1115
+ /**
1116
+ * Generates a complete DatePicker DOM descriptor tree.
1117
+ *
1118
+ * Produces a calendar grid with `role="grid"`, day cells with `role="gridcell"`,
1119
+ * month/year navigation buttons, weekday column headers with `role="columnheader"`,
1120
+ * and full accessibility attributes including `aria-label` on each day cell and
1121
+ * `aria-selected`/`aria-disabled` states.
1122
+ */
1123
+ function generateDatePickerDOM(props) {
1124
+ const { value, min, max, locale = 'en-US', } = props;
1125
+ // Default displayMonth/displayYear to current date or value date
1126
+ const now = new Date();
1127
+ let displayMonth = props.displayMonth;
1128
+ let displayYear = props.displayYear;
1129
+ if (displayMonth === undefined || displayYear === undefined) {
1130
+ if (value) {
1131
+ const valueParts = value.split('-');
1132
+ displayMonth = displayMonth ?? (parseInt(valueParts[1], 10) - 1);
1133
+ displayYear = displayYear ?? parseInt(valueParts[0], 10);
1134
+ }
1135
+ else {
1136
+ displayMonth = displayMonth ?? now.getMonth();
1137
+ displayYear = displayYear ?? now.getFullYear();
1138
+ }
1139
+ }
1140
+ // Generate unique IDs
1141
+ const gridId = generateId('ux4g-date-picker-grid');
1142
+ const headerId = generateId('ux4g-date-picker-header');
1143
+ // Build header with month/year display and navigation
1144
+ const monthName = getMonthName(displayYear, displayMonth, locale);
1145
+ const prevButton = {
1146
+ tag: 'button',
1147
+ props: {
1148
+ className: 'ux4g-date-picker-prev',
1149
+ type: 'button',
1150
+ 'aria-label': 'Previous month',
1151
+ },
1152
+ children: [],
1153
+ };
1154
+ const nextButton = {
1155
+ tag: 'button',
1156
+ props: {
1157
+ className: 'ux4g-date-picker-next',
1158
+ type: 'button',
1159
+ 'aria-label': 'Next month',
1160
+ },
1161
+ children: [],
1162
+ };
1163
+ const headerTitle = {
1164
+ tag: 'span',
1165
+ props: {
1166
+ className: 'ux4g-date-picker-title',
1167
+ },
1168
+ children: [`${monthName} ${displayYear}`],
1169
+ };
1170
+ const header = {
1171
+ tag: 'div',
1172
+ props: {
1173
+ className: 'ux4g-date-picker-header',
1174
+ id: headerId,
1175
+ },
1176
+ children: [prevButton, headerTitle, nextButton],
1177
+ };
1178
+ // Build weekday column headers
1179
+ const weekdays = getWeekdayHeaders(locale);
1180
+ const weekdayHeaders = weekdays.map((day) => ({
1181
+ tag: 'div',
1182
+ props: {
1183
+ className: 'ux4g-date-picker-weekday',
1184
+ role: 'columnheader',
1185
+ 'aria-label': day,
1186
+ },
1187
+ children: [day],
1188
+ }));
1189
+ const weekdayRow = {
1190
+ tag: 'div',
1191
+ props: {
1192
+ className: 'ux4g-date-picker-weekdays',
1193
+ role: 'row',
1194
+ },
1195
+ children: weekdayHeaders,
1196
+ };
1197
+ // Build calendar grid with day cells
1198
+ const daysInMonth = getDaysInMonth(displayYear, displayMonth);
1199
+ const firstDayOfWeek = getFirstDayOfWeek(displayYear, displayMonth);
1200
+ // Parse selected value for comparison
1201
+ let selectedYear;
1202
+ let selectedMonth;
1203
+ let selectedDay;
1204
+ if (value) {
1205
+ const parts = value.split('-');
1206
+ selectedYear = parseInt(parts[0], 10);
1207
+ selectedMonth = parseInt(parts[1], 10) - 1;
1208
+ selectedDay = parseInt(parts[2], 10);
1209
+ }
1210
+ // Build week rows
1211
+ const weekRows = [];
1212
+ let currentDay = 1;
1213
+ let weekCells = [];
1214
+ // Fill empty cells before the first day
1215
+ for (let i = 0; i < firstDayOfWeek; i++) {
1216
+ weekCells.push({
1217
+ tag: 'div',
1218
+ props: {
1219
+ className: 'ux4g-date-picker-cell ux4g-date-picker-cell-empty',
1220
+ role: 'gridcell',
1221
+ },
1222
+ children: [],
1223
+ });
1224
+ }
1225
+ // Fill days
1226
+ while (currentDay <= daysInMonth) {
1227
+ const isSelected = selectedYear === displayYear &&
1228
+ selectedMonth === displayMonth &&
1229
+ selectedDay === currentDay;
1230
+ const isDisabled = isDateDisabled(displayYear, displayMonth, currentDay, min, max);
1231
+ const dateLabel = formatDateLabel(displayYear, displayMonth, currentDay, locale);
1232
+ const cellProps = {
1233
+ className: 'ux4g-date-picker-cell',
1234
+ role: 'gridcell',
1235
+ 'aria-label': dateLabel,
1236
+ };
1237
+ if (isSelected) {
1238
+ cellProps['aria-selected'] = 'true';
1239
+ }
1240
+ if (isDisabled) {
1241
+ cellProps['aria-disabled'] = 'true';
1242
+ }
1243
+ weekCells.push({
1244
+ tag: 'div',
1245
+ props: cellProps,
1246
+ children: [`${currentDay}`],
1247
+ });
1248
+ // If we've filled a week (7 cells), start a new row
1249
+ if (weekCells.length === 7) {
1250
+ weekRows.push({
1251
+ tag: 'div',
1252
+ props: {
1253
+ className: 'ux4g-date-picker-row',
1254
+ role: 'row',
1255
+ },
1256
+ children: weekCells,
1257
+ });
1258
+ weekCells = [];
1259
+ }
1260
+ currentDay++;
1261
+ }
1262
+ // Fill remaining empty cells in the last row
1263
+ if (weekCells.length > 0) {
1264
+ while (weekCells.length < 7) {
1265
+ weekCells.push({
1266
+ tag: 'div',
1267
+ props: {
1268
+ className: 'ux4g-date-picker-cell ux4g-date-picker-cell-empty',
1269
+ role: 'gridcell',
1270
+ },
1271
+ children: [],
1272
+ });
1273
+ }
1274
+ weekRows.push({
1275
+ tag: 'div',
1276
+ props: {
1277
+ className: 'ux4g-date-picker-row',
1278
+ role: 'row',
1279
+ },
1280
+ children: weekCells,
1281
+ });
1282
+ }
1283
+ // Build the grid
1284
+ const grid = {
1285
+ tag: 'div',
1286
+ props: {
1287
+ className: 'ux4g-date-picker-grid',
1288
+ id: gridId,
1289
+ role: 'grid',
1290
+ 'aria-labelledby': headerId,
1291
+ },
1292
+ children: [weekdayRow, ...weekRows],
1293
+ };
1294
+ // Build root element
1295
+ const rootNode = {
1296
+ tag: 'div',
1297
+ props: {
1298
+ className: buildDateTimePickerClasses('date'),
1299
+ },
1300
+ children: [header, grid],
1301
+ };
1302
+ return rootNode;
1303
+ }
1304
+
1305
+ // ux4g-components-web/src/dom-generators/stepper.ts
1306
+ // Framework-agnostic DOM Generator for the Stepper component.
1307
+ /**
1308
+ * Generates a complete Stepper DOM descriptor tree.
1309
+ *
1310
+ * Produces step indicators with completed/current/pending states.
1311
+ * Steps before `activeStep` are marked as completed, the step at
1312
+ * `activeStep` has `aria-current="step"`, and subsequent steps
1313
+ * are marked as pending.
1314
+ */
1315
+ function generateStepperDOM(props) {
1316
+ const { steps, activeStep, orientation = 'horizontal', variant = 'default', size = 'default', } = props;
1317
+ // Validate steps is an array
1318
+ validateArray('Stepper', 'steps', steps);
1319
+ // Validate activeStep is in range [0, steps.length - 1]
1320
+ validateRange('Stepper', 'activeStep', activeStep, 0, steps.length - 1);
1321
+ // Build root className from buildStepperClasses
1322
+ const rootClassName = buildStepperClasses(orientation, undefined, variant, size);
1323
+ // Generate step indicator elements
1324
+ const stepChildren = steps.map((step, index) => {
1325
+ let stepClassName;
1326
+ const stepProps = {};
1327
+ if (index < activeStep) {
1328
+ // Completed step
1329
+ stepClassName = 'ux4g-stepper-item completed';
1330
+ stepProps['aria-label'] = `Completed: ${step.label}`;
1331
+ }
1332
+ else if (index === activeStep) {
1333
+ // Current step
1334
+ stepClassName = 'ux4g-stepper-item current';
1335
+ stepProps['aria-current'] = 'step';
1336
+ stepProps['aria-label'] = step.label;
1337
+ }
1338
+ else {
1339
+ // Pending step
1340
+ stepClassName = 'ux4g-stepper-item pending';
1341
+ stepProps['aria-label'] = step.label;
1342
+ }
1343
+ stepProps.className = stepClassName;
1344
+ // Build children for the step indicator
1345
+ const stepItemChildren = [
1346
+ {
1347
+ tag: 'span',
1348
+ props: {
1349
+ className: 'ux4g-stepper-indicator',
1350
+ },
1351
+ children: [`${index + 1}`],
1352
+ },
1353
+ {
1354
+ tag: 'span',
1355
+ props: {
1356
+ className: 'ux4g-stepper-label',
1357
+ },
1358
+ children: step.description
1359
+ ? [
1360
+ step.label,
1361
+ {
1362
+ tag: 'span',
1363
+ props: {
1364
+ className: 'ux4g-stepper-description',
1365
+ },
1366
+ children: [step.description],
1367
+ },
1368
+ ]
1369
+ : [step.label],
1370
+ },
1371
+ ];
1372
+ return {
1373
+ tag: 'div',
1374
+ props: stepProps,
1375
+ children: stepItemChildren,
1376
+ };
1377
+ });
1378
+ // Build root element
1379
+ const rootNode = {
1380
+ tag: 'div',
1381
+ props: {
1382
+ className: rootClassName,
1383
+ role: 'list',
1384
+ 'aria-label': 'Progress',
1385
+ },
1386
+ children: stepChildren,
1387
+ };
1388
+ return rootNode;
1389
+ }
1390
+
1391
+ // ux4g-components-web/src/dom-generators/file-upload.ts
1392
+ // Framework-agnostic DOM Generator for the FileUpload component.
1393
+ /**
1394
+ * Formats a byte value into a human-readable string (e.g., "1.5 KB", "2.3 MB").
1395
+ */
1396
+ function formatFileSize(bytes) {
1397
+ if (bytes < 1024)
1398
+ return `${bytes} B`;
1399
+ if (bytes < 1024 * 1024)
1400
+ return `${(bytes / 1024).toFixed(1)} KB`;
1401
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1402
+ }
1403
+ /**
1404
+ * Generates a complete FileUpload DOM descriptor tree.
1405
+ *
1406
+ * Produces a drop zone with `role="button"`, a visually hidden file input,
1407
+ * and a file list displaying name and size of each selected file.
1408
+ * Compatible with Runtime_JS MutationObserver-based initialization.
1409
+ */
1410
+ function generateFileUploadDOM(props) {
1411
+ const { files = [], accept, maxSizeBytes, multiple = false, state, } = props;
1412
+ // Generate unique IDs
1413
+ const inputId = generateId('ux4g-file-upload-input');
1414
+ const dropZoneId = generateId('ux4g-file-upload-zone');
1415
+ // Build CSS classes via Class Builder
1416
+ const rootClasses = buildFileUploadClasses(state);
1417
+ // Build hidden file input
1418
+ const inputProps = {
1419
+ className: 'ux4g-upload-input',
1420
+ id: inputId,
1421
+ type: 'file',
1422
+ 'aria-hidden': 'true',
1423
+ tabIndex: -1,
1424
+ };
1425
+ if (accept) {
1426
+ inputProps['accept'] = accept;
1427
+ }
1428
+ if (multiple) {
1429
+ inputProps['multiple'] = true;
1430
+ }
1431
+ const inputNode = {
1432
+ tag: 'input',
1433
+ props: inputProps,
1434
+ children: [],
1435
+ };
1436
+ // Build drop zone
1437
+ const dropZoneNode = {
1438
+ tag: 'div',
1439
+ props: {
1440
+ className: 'ux4g-upload-drop-zone',
1441
+ id: dropZoneId,
1442
+ role: 'button',
1443
+ tabIndex: 0,
1444
+ 'aria-label': 'Drop files here or click to upload',
1445
+ 'aria-controls': inputId,
1446
+ },
1447
+ children: [
1448
+ {
1449
+ tag: 'span',
1450
+ props: {
1451
+ className: 'ux4g-upload-drop-zone-text',
1452
+ },
1453
+ children: ['Drop files here or click to upload'],
1454
+ },
1455
+ ],
1456
+ };
1457
+ // Build file list items
1458
+ const fileItems = files.map((file) => ({
1459
+ tag: 'div',
1460
+ props: {
1461
+ className: 'ux4g-upload-file-item',
1462
+ },
1463
+ children: [
1464
+ {
1465
+ tag: 'span',
1466
+ props: {
1467
+ className: 'ux4g-upload-file-name',
1468
+ },
1469
+ children: [file.name],
1470
+ },
1471
+ {
1472
+ tag: 'span',
1473
+ props: {
1474
+ className: 'ux4g-upload-file-size',
1475
+ },
1476
+ children: [formatFileSize(file.size)],
1477
+ },
1478
+ ],
1479
+ }));
1480
+ // Build file list container
1481
+ const fileListNode = {
1482
+ tag: 'div',
1483
+ props: {
1484
+ className: 'ux4g-upload-file-list',
1485
+ role: 'list',
1486
+ 'aria-label': 'Uploaded files',
1487
+ },
1488
+ children: fileItems,
1489
+ };
1490
+ // Build root container
1491
+ const rootNode = {
1492
+ tag: 'div',
1493
+ props: {
1494
+ className: rootClasses,
1495
+ },
1496
+ children: [dropZoneNode, inputNode, fileListNode],
1497
+ };
1498
+ return rootNode;
1499
+ }
1500
+
1501
+ // ux4g-components-web/src/dom-generators/progress.ts
1502
+ // Framework-agnostic DOM Generator for the Progress Indicator component.
1503
+ const VALID_TYPES$1 = ['bar', 'circle'];
1504
+ const VALID_SIZES$2 = ['xs', 's', 'm', 'l', 'xl', '2xl', '3xl'];
1505
+ /**
1506
+ * Generates a complete Progress Indicator DOM descriptor tree.
1507
+ *
1508
+ * Produces a progress element with `role="progressbar"`,
1509
+ * `aria-valuenow`, `aria-valuemin`, and `aria-valuemax`.
1510
+ * Supports both 'bar' and 'circle' visualization types.
1511
+ */
1512
+ function generateProgressDOM(props) {
1513
+ const { value, type = 'bar', size, label, } = props;
1514
+ // Validate props
1515
+ validateRange('Progress', 'value', value, 0, 100);
1516
+ if (type !== undefined) {
1517
+ validateEnum('Progress', 'type', type, VALID_TYPES$1);
1518
+ }
1519
+ if (size !== undefined) {
1520
+ validateEnum('Progress', 'size', size, VALID_SIZES$2);
1521
+ }
1522
+ const ariaLabel = label || 'Progress';
1523
+ // Build CSS class string from the class builder
1524
+ const className = buildProgressIndicatorClasses(type);
1525
+ if (type === 'circle') {
1526
+ return generateCircleProgress(value, className, size, ariaLabel);
1527
+ }
1528
+ return generateBarProgress(value, className, size, ariaLabel);
1529
+ }
1530
+ /**
1531
+ * Generates a bar-type progress indicator.
1532
+ */
1533
+ function generateBarProgress(value, className, size, ariaLabel) {
1534
+ // The fill element represents the completed portion
1535
+ const fillNode = {
1536
+ tag: 'div',
1537
+ props: {
1538
+ className: 'ux4g-progress-bar-fill',
1539
+ 'data-value': String(value),
1540
+ },
1541
+ children: [],
1542
+ };
1543
+ // The track element contains the fill
1544
+ const trackNode = {
1545
+ tag: 'div',
1546
+ props: {
1547
+ className: 'ux4g-progress-bar-track',
1548
+ },
1549
+ children: [fillNode],
1550
+ };
1551
+ // Root element with ARIA progressbar role
1552
+ const rootProps = {
1553
+ className,
1554
+ role: 'progressbar',
1555
+ 'aria-valuenow': value,
1556
+ 'aria-valuemin': 0,
1557
+ 'aria-valuemax': 100,
1558
+ 'aria-label': ariaLabel,
1559
+ };
1560
+ if (size) {
1561
+ rootProps['data-ux-size'] = size;
1562
+ }
1563
+ const rootNode = {
1564
+ tag: 'div',
1565
+ props: rootProps,
1566
+ children: [trackNode],
1567
+ };
1568
+ return rootNode;
1569
+ }
1570
+ /**
1571
+ * Generates a circle-type progress indicator.
1572
+ */
1573
+ function generateCircleProgress(value, className, size, ariaLabel) {
1574
+ // The circle container element
1575
+ const circleContainer = {
1576
+ tag: 'div',
1577
+ props: {
1578
+ className: 'ux4g-progress-circle-container',
1579
+ 'data-value': String(value),
1580
+ },
1581
+ children: [],
1582
+ };
1583
+ // Root element with ARIA progressbar role
1584
+ const rootProps = {
1585
+ className,
1586
+ role: 'progressbar',
1587
+ 'aria-valuenow': value,
1588
+ 'aria-valuemin': 0,
1589
+ 'aria-valuemax': 100,
1590
+ 'aria-label': ariaLabel,
1591
+ };
1592
+ if (size) {
1593
+ rootProps['data-ux-size'] = size;
1594
+ }
1595
+ const rootNode = {
1596
+ tag: 'div',
1597
+ props: rootProps,
1598
+ children: [circleContainer],
1599
+ };
1600
+ return rootNode;
1601
+ }
1602
+
1603
+ // ux4g-components-web/src/dom-generators/table.ts
1604
+ // Framework-agnostic DOM Generator for the Table component.
1605
+ /**
1606
+ * Generates a complete Table DOM descriptor tree.
1607
+ *
1608
+ * Produces a `<table>` with `<thead>` containing one `<th>` per column
1609
+ * and `<tbody>` containing one `<tr>` per data row, each with one `<td>` per column.
1610
+ * Sortable columns receive `aria-sort="none"` on their `<th>`.
1611
+ */
1612
+ function generateTableDOM(props) {
1613
+ const { columns, data, size = 'm', divider, zebra, interactive = false, } = props;
1614
+ // Validate required props
1615
+ validateArray('Table', 'columns', columns);
1616
+ // Determine if any column is sortable (for buildTableClasses sortable param)
1617
+ const hasSortable = columns.some((col) => col.sortable);
1618
+ // Build CSS classes via Class Builder
1619
+ const tableClasses = buildTableClasses(size, divider, zebra, interactive, hasSortable);
1620
+ // Build thead with th elements
1621
+ const thElements = columns.map((col) => {
1622
+ const thProps = {
1623
+ scope: 'col',
1624
+ };
1625
+ if (col.sortable) {
1626
+ thProps['aria-sort'] = 'none';
1627
+ }
1628
+ if (col.width) {
1629
+ thProps['style'] = `width: ${col.width}`;
1630
+ }
1631
+ return {
1632
+ tag: 'th',
1633
+ props: thProps,
1634
+ children: [col.label],
1635
+ };
1636
+ });
1637
+ const theadRow = {
1638
+ tag: 'tr',
1639
+ props: {},
1640
+ children: thElements,
1641
+ };
1642
+ const thead = {
1643
+ tag: 'thead',
1644
+ props: {},
1645
+ children: [theadRow],
1646
+ };
1647
+ // Build tbody with tr/td elements
1648
+ const bodyRows = (data || []).map((row) => {
1649
+ const cells = columns.map((col) => {
1650
+ const cellValue = row[col.key];
1651
+ const cellText = cellValue != null ? String(cellValue) : '';
1652
+ return {
1653
+ tag: 'td',
1654
+ props: {},
1655
+ children: [cellText],
1656
+ };
1657
+ });
1658
+ return {
1659
+ tag: 'tr',
1660
+ props: {},
1661
+ children: cells,
1662
+ };
1663
+ });
1664
+ const tbody = {
1665
+ tag: 'tbody',
1666
+ props: {},
1667
+ children: bodyRows,
1668
+ };
1669
+ // Build root table element
1670
+ const rootNode = {
1671
+ tag: 'table',
1672
+ props: {
1673
+ className: tableClasses,
1674
+ },
1675
+ children: [thead, tbody],
1676
+ };
1677
+ return rootNode;
1678
+ }
1679
+
1680
+ // ux4g-components-web/src/dom-generators/otp.ts
1681
+ // Framework-agnostic DOM Generator for the OTP Input component.
1682
+ /**
1683
+ * Generates a complete OTP Input DOM descriptor tree.
1684
+ *
1685
+ * Produces individual `<input>` cells each accepting a single character,
1686
+ * with maxLength=1. The root container gets CSS classes from `buildOtpInputClasses(state)`.
1687
+ * When disabled, each cell receives `aria-disabled="true"` and `disabled=true`.
1688
+ */
1689
+ function generateOtpDOM(props) {
1690
+ const { length, values = [], state, disabled = false, } = props;
1691
+ // Validate length is in range [4, 8]
1692
+ validateRange('OTP', 'length', length, 4, 8);
1693
+ // Build CSS classes via Class Builder
1694
+ const containerClasses = buildOtpInputClasses(state);
1695
+ // Generate individual input cells
1696
+ const cells = [];
1697
+ for (let i = 0; i < length; i++) {
1698
+ const cellId = generateId('ux4g-otp-cell');
1699
+ const cellProps = {
1700
+ className: 'ux4g-otp-cell',
1701
+ id: cellId,
1702
+ type: 'text',
1703
+ maxLength: '1',
1704
+ 'aria-label': `Digit ${i + 1}`,
1705
+ };
1706
+ // Set value if provided
1707
+ if (values[i] !== undefined) {
1708
+ cellProps['value'] = values[i];
1709
+ }
1710
+ // Apply disabled state
1711
+ if (disabled) {
1712
+ cellProps['aria-disabled'] = 'true';
1713
+ cellProps['disabled'] = true;
1714
+ }
1715
+ cells.push({
1716
+ tag: 'input',
1717
+ props: cellProps,
1718
+ children: [],
1719
+ });
1720
+ }
1721
+ // Build root container
1722
+ const rootNode = {
1723
+ tag: 'div',
1724
+ props: {
1725
+ className: containerClasses,
1726
+ },
1727
+ children: cells,
1728
+ };
1729
+ return rootNode;
1730
+ }
1731
+
1732
+ // ux4g-components-web/src/dom-generators/avatar.ts
1733
+ // Framework-agnostic DOM Generator for the Avatar (native) component.
1734
+ const VALID_SIZES$1 = ['xs', 's', 'm', 'l', 'xl', '2xl', '3xl'];
1735
+ const VALID_STATUSES = ['online', 'offline', 'busy', 'away'];
1736
+ /**
1737
+ * Generates a complete Avatar DOM descriptor tree.
1738
+ *
1739
+ * Produces a container with avatar classes from buildAvatarClasses(),
1740
+ * an img element with alt text (or fallback initials span),
1741
+ * and an optional status indicator element.
1742
+ */
1743
+ function generateAvatarDOM(props) {
1744
+ const { src, alt = '', size, status, fallbackInitials, } = props;
1745
+ // Validate props
1746
+ if (size !== undefined) {
1747
+ validateEnum('Avatar', 'size', size, VALID_SIZES$1);
1748
+ }
1749
+ if (status !== undefined) {
1750
+ validateEnum('Avatar', 'status', status, VALID_STATUSES);
1751
+ }
1752
+ // Use 'status' avatar type when a status prop is provided, otherwise 'profile'
1753
+ const avatarType = status ? 'status' : 'profile';
1754
+ // Build CSS class string from the class builder
1755
+ const className = buildAvatarClasses(avatarType, size);
1756
+ // Build children
1757
+ const children = [];
1758
+ // Add img element or fallback initials
1759
+ if (src) {
1760
+ const imgNode = {
1761
+ tag: 'img',
1762
+ props: {
1763
+ className: 'ux4g-avatar-img',
1764
+ src,
1765
+ alt,
1766
+ },
1767
+ children: [],
1768
+ };
1769
+ children.push(imgNode);
1770
+ }
1771
+ else if (fallbackInitials) {
1772
+ const initialsNode = {
1773
+ tag: 'span',
1774
+ props: {
1775
+ className: 'ux4g-avatar-initials',
1776
+ 'aria-label': alt || fallbackInitials,
1777
+ },
1778
+ children: [fallbackInitials],
1779
+ };
1780
+ children.push(initialsNode);
1781
+ }
1782
+ // Add optional status indicator
1783
+ if (status) {
1784
+ const statusNode = {
1785
+ tag: 'span',
1786
+ props: {
1787
+ className: `ux4g-avatar-status-indicator ux4g-avatar-status-${status}`,
1788
+ 'aria-label': status,
1789
+ },
1790
+ children: [],
1791
+ };
1792
+ children.push(statusNode);
1793
+ }
1794
+ // Root container element
1795
+ const rootNode = {
1796
+ tag: 'div',
1797
+ props: {
1798
+ className,
1799
+ role: 'img',
1800
+ 'aria-label': alt || fallbackInitials || 'Avatar',
1801
+ },
1802
+ children,
1803
+ };
1804
+ return rootNode;
1805
+ }
1806
+
1807
+ // ux4g-components-web/src/dom-generators/tooltip.ts
1808
+ // Framework-agnostic DOM Generator for the Tooltip component.
1809
+ /**
1810
+ * Generates a complete Tooltip DOM descriptor tree.
1811
+ *
1812
+ * Produces a tooltip container with `role="tooltip"`, an arrow element,
1813
+ * `data-ux4g-tooltip` on the trigger element, `data-placement` for positioning,
1814
+ * and `aria-describedby` on the trigger referencing the tooltip ID.
1815
+ * Compatible with Runtime_JS MutationObserver-based initialization.
1816
+ */
1817
+ function generateTooltipDOM(props) {
1818
+ const { text, placement = 'top-center', size = 's', triggerId, } = props;
1819
+ // Validate required props
1820
+ validateRequired('Tooltip', 'text', text);
1821
+ validateType('Tooltip', 'text', text, 'string');
1822
+ // Generate unique IDs
1823
+ const tooltipId = generateId('ux4g-tooltip');
1824
+ const triggerElId = triggerId || generateId('ux4g-tooltip-trigger');
1825
+ // Build CSS classes via Class Builder
1826
+ const tooltipClassName = buildTooltipClasses(placement, size);
1827
+ // Map placement to the simplified data-placement value expected by Runtime_JS
1828
+ // Runtime_JS expects: top, bottom, left, right
1829
+ let dataPlacement;
1830
+ if (placement.startsWith('top')) {
1831
+ dataPlacement = 'top';
1832
+ }
1833
+ else if (placement.startsWith('bottom')) {
1834
+ dataPlacement = 'bottom';
1835
+ }
1836
+ else if (placement.startsWith('left')) {
1837
+ dataPlacement = 'left';
1838
+ }
1839
+ else {
1840
+ dataPlacement = 'right';
1841
+ }
1842
+ // Build the arrow element
1843
+ const arrowNode = {
1844
+ tag: 'span',
1845
+ props: {
1846
+ className: 'ux4g-tooltip-arrow',
1847
+ },
1848
+ children: [],
1849
+ };
1850
+ // Build the tooltip content element
1851
+ const tooltipNode = {
1852
+ tag: 'div',
1853
+ props: {
1854
+ className: tooltipClassName,
1855
+ id: tooltipId,
1856
+ role: 'tooltip',
1857
+ 'data-placement': dataPlacement,
1858
+ },
1859
+ children: [arrowNode, text],
1860
+ };
1861
+ // Build the trigger element with data-ux4g-tooltip and aria-describedby
1862
+ const triggerNode = {
1863
+ tag: 'span',
1864
+ props: {
1865
+ className: 'ux4g-tooltip-trigger',
1866
+ id: triggerElId,
1867
+ 'data-ux4g-tooltip': 'true',
1868
+ 'aria-describedby': tooltipId,
1869
+ tabIndex: 0,
1870
+ },
1871
+ children: [],
1872
+ };
1873
+ // Build root wrapper containing trigger and tooltip
1874
+ const rootNode = {
1875
+ tag: 'div',
1876
+ props: {
1877
+ className: 'ux4g-tooltip-wrapper',
1878
+ },
1879
+ children: [triggerNode, tooltipNode],
1880
+ };
1881
+ return rootNode;
1882
+ }
1883
+
1884
+ // ux4g-components-web/src/dom-generators/chip.ts
1885
+ // Framework-agnostic DOM Generator for the Chip (native) component.
1886
+ const VALID_TYPES = ['filter', 'choice', 'input'];
1887
+ const VALID_SIZES = ['md', 'sm', 'xs'];
1888
+ /**
1889
+ * Generates a complete Chip DOM descriptor tree.
1890
+ *
1891
+ * Produces a chip container with CSS classes from `buildChipClasses()`,
1892
+ * a label span, and an optional dismiss button with `aria-label="Remove {label}"`.
1893
+ */
1894
+ function generateChipDOM(props) {
1895
+ const { label, type = 'filter', size = 'md', active = false, dismissible = false, } = props;
1896
+ // Validate required props
1897
+ validateRequired('Chip', 'label', label);
1898
+ validateType('Chip', 'label', label, 'string');
1899
+ if (type !== undefined) {
1900
+ validateEnum('Chip', 'type', type, VALID_TYPES);
1901
+ }
1902
+ if (size !== undefined) {
1903
+ validateEnum('Chip', 'size', size, VALID_SIZES);
1904
+ }
1905
+ // Build CSS class string from the class builder
1906
+ const className = buildChipClasses(type, size, active);
1907
+ // Label span
1908
+ const labelNode = {
1909
+ tag: 'span',
1910
+ props: {
1911
+ className: 'ux4g-chip-label',
1912
+ },
1913
+ children: [label],
1914
+ };
1915
+ // Build children array
1916
+ const children = [labelNode];
1917
+ // Conditional dismiss button
1918
+ if (dismissible) {
1919
+ const dismissButton = {
1920
+ tag: 'button',
1921
+ props: {
1922
+ className: 'ux4g-chip-dismiss',
1923
+ type: 'button',
1924
+ 'aria-label': `Remove ${label}`,
1925
+ },
1926
+ children: [],
1927
+ };
1928
+ children.push(dismissButton);
1929
+ }
1930
+ // Root container
1931
+ const rootNode = {
1932
+ tag: 'div',
1933
+ props: {
1934
+ className,
1935
+ role: 'option',
1936
+ },
1937
+ children,
1938
+ };
1939
+ return rootNode;
1940
+ }
1941
+
1942
+ // ux4g-components-web/src/dom-generators/popover.ts
1943
+ // Framework-agnostic DOM Generator for the Popover component.
1944
+ const VALID_PLACEMENTS = [
1945
+ 'top', 'top-start', 'top-end',
1946
+ 'bottom', 'bottom-start', 'bottom-end',
1947
+ 'left', 'left-start', 'left-end',
1948
+ 'right', 'right-start', 'right-end',
1949
+ ];
1950
+ /**
1951
+ * Generates a complete Popover DOM descriptor tree.
1952
+ *
1953
+ * Produces a trigger wrapper element with `data-ux4g-popover` and
1954
+ * `aria-describedby` referencing the popover content container.
1955
+ * The popover container uses CSS classes from `buildPopoverClasses()`
1956
+ * for placement and visibility. Compatible with Runtime_JS
1957
+ * MutationObserver-based initialization.
1958
+ */
1959
+ function generatePopoverDOM(props) {
1960
+ const { placement = 'bottom', show = false, triggerId, } = props;
1961
+ // Validate placement prop if provided explicitly
1962
+ if (props.placement !== undefined) {
1963
+ validateEnum('Popover', 'placement', placement, VALID_PLACEMENTS);
1964
+ }
1965
+ // Generate unique IDs
1966
+ const resolvedTriggerId = triggerId ?? generateId('ux4g-popover-trigger');
1967
+ const popoverId = generateId('ux4g-popover-content');
1968
+ // Build CSS classes via Class Builder
1969
+ const popoverClasses = buildPopoverClasses(placement, show);
1970
+ // Build trigger element
1971
+ const triggerNode = {
1972
+ tag: 'span',
1973
+ props: {
1974
+ className: 'ux4g-popover-trigger',
1975
+ id: resolvedTriggerId,
1976
+ 'data-ux4g-popover': 'true',
1977
+ 'aria-describedby': popoverId,
1978
+ },
1979
+ children: [],
1980
+ };
1981
+ // Build popover content container
1982
+ const popoverContentNode = {
1983
+ tag: 'div',
1984
+ props: {
1985
+ className: popoverClasses,
1986
+ id: popoverId,
1987
+ role: 'tooltip',
1988
+ 'data-placement': placement,
1989
+ },
1990
+ children: [],
1991
+ };
1992
+ // Build root wrapper containing trigger + popover content
1993
+ const rootNode = {
1994
+ tag: 'div',
1995
+ props: {
1996
+ className: 'ux4g-popover-wrapper',
1997
+ },
1998
+ children: [triggerNode, popoverContentNode],
1999
+ };
2000
+ return rootNode;
2001
+ }
2002
+
2003
+ exports.DOMGeneratorValidationError = DOMGeneratorValidationError;
2004
+ exports.generateAccordionDOM = generateAccordionDOM;
2005
+ exports.generateAvatarDOM = generateAvatarDOM;
2006
+ exports.generateCarouselDOM = generateCarouselDOM;
2007
+ exports.generateChipDOM = generateChipDOM;
2008
+ exports.generateComboboxDOM = generateComboboxDOM;
2009
+ exports.generateDatePickerDOM = generateDatePickerDOM;
2010
+ exports.generateDrawerDOM = generateDrawerDOM;
2011
+ exports.generateDropdownDOM = generateDropdownDOM;
2012
+ exports.generateFileUploadDOM = generateFileUploadDOM;
2013
+ exports.generateId = generateId;
2014
+ exports.generateModalDOM = generateModalDOM;
2015
+ exports.generateOtpDOM = generateOtpDOM;
2016
+ exports.generatePopoverDOM = generatePopoverDOM;
2017
+ exports.generateProgressDOM = generateProgressDOM;
2018
+ exports.generateSearchDOM = generateSearchDOM;
2019
+ exports.generateStepperDOM = generateStepperDOM;
2020
+ exports.generateTableDOM = generateTableDOM;
2021
+ exports.generateTabsDOM = generateTabsDOM;
2022
+ exports.generateTimePickerDOM = generateTimePickerDOM;
2023
+ exports.generateTooltipDOM = generateTooltipDOM;
2024
+ exports.resetIdCounter = resetIdCounter;
2025
+ exports.validateArray = validateArray;
2026
+ exports.validateEnum = validateEnum;
2027
+ exports.validateRange = validateRange;
2028
+ exports.validateRequired = validateRequired;
2029
+ exports.validateType = validateType;