mtrl-addons 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/.cursorrules +117 -0
  2. package/AI.md +241 -0
  3. package/build.js +170 -0
  4. package/bun.lock +792 -0
  5. package/index.ts +7 -0
  6. package/package.json +10 -17
  7. package/scripts/analyze-orphaned-functions.ts +387 -0
  8. package/src/components/index.ts +45 -0
  9. package/src/components/list/api.ts +314 -0
  10. package/src/components/list/config.ts +352 -0
  11. package/src/components/list/constants.ts +56 -0
  12. package/src/components/list/features/api.ts +428 -0
  13. package/src/components/list/features/index.ts +31 -0
  14. package/src/components/list/features/list-manager.ts +502 -0
  15. package/src/components/list/features.ts +112 -0
  16. package/src/components/list/index.ts +39 -0
  17. package/src/components/list/list.ts +234 -0
  18. package/src/components/list/types.ts +513 -0
  19. package/src/core/collection/base-collection.ts +100 -0
  20. package/src/core/collection/collection-composer.ts +178 -0
  21. package/src/core/collection/collection.ts +745 -0
  22. package/src/core/collection/constants.ts +172 -0
  23. package/src/core/collection/events.ts +428 -0
  24. package/src/core/collection/features/api/loading.ts +279 -0
  25. package/src/core/collection/features/operations/data-operations.ts +147 -0
  26. package/src/core/collection/index.ts +104 -0
  27. package/src/core/collection/state.ts +497 -0
  28. package/src/core/collection/types.ts +404 -0
  29. package/src/core/compose/features/collection.ts +119 -0
  30. package/src/core/compose/features/index.ts +39 -0
  31. package/src/core/compose/features/performance.ts +161 -0
  32. package/src/core/compose/features/selection.ts +213 -0
  33. package/src/core/compose/features/styling.ts +108 -0
  34. package/src/core/compose/index.ts +31 -0
  35. package/src/core/index.ts +167 -0
  36. package/src/core/layout/config.ts +102 -0
  37. package/src/core/layout/index.ts +168 -0
  38. package/src/core/layout/jsx.ts +174 -0
  39. package/src/core/layout/schema.ts +963 -0
  40. package/src/core/layout/types.ts +92 -0
  41. package/src/core/list-manager/api.ts +599 -0
  42. package/src/core/list-manager/config.ts +593 -0
  43. package/src/core/list-manager/constants.ts +268 -0
  44. package/src/core/list-manager/features/api.ts +58 -0
  45. package/src/core/list-manager/features/collection/collection.ts +705 -0
  46. package/src/core/list-manager/features/collection/index.ts +17 -0
  47. package/src/core/list-manager/features/viewport/constants.ts +42 -0
  48. package/src/core/list-manager/features/viewport/index.ts +16 -0
  49. package/src/core/list-manager/features/viewport/item-size.ts +274 -0
  50. package/src/core/list-manager/features/viewport/loading.ts +263 -0
  51. package/src/core/list-manager/features/viewport/placeholders.ts +281 -0
  52. package/src/core/list-manager/features/viewport/rendering.ts +575 -0
  53. package/src/core/list-manager/features/viewport/scrollbar.ts +495 -0
  54. package/src/core/list-manager/features/viewport/scrolling.ts +795 -0
  55. package/src/core/list-manager/features/viewport/template.ts +220 -0
  56. package/src/core/list-manager/features/viewport/viewport.ts +654 -0
  57. package/src/core/list-manager/features/viewport/virtual.ts +309 -0
  58. package/src/core/list-manager/index.ts +279 -0
  59. package/src/core/list-manager/list-manager.ts +206 -0
  60. package/src/core/list-manager/types.ts +439 -0
  61. package/src/core/list-manager/utils/calculations.ts +290 -0
  62. package/src/core/list-manager/utils/range-calculator.ts +349 -0
  63. package/src/core/list-manager/utils/speed-tracker.ts +273 -0
  64. package/src/index.ts +17 -0
  65. package/src/styles/components/_list.scss +244 -0
  66. package/src/styles/index.scss +12 -0
  67. package/src/types/mtrl.d.ts +6 -0
  68. package/test/benchmarks/layout/advanced.test.ts +656 -0
  69. package/test/benchmarks/layout/comparison.test.ts +519 -0
  70. package/test/benchmarks/layout/performance-comparison.test.ts +274 -0
  71. package/test/benchmarks/layout/real-components.test.ts +733 -0
  72. package/test/benchmarks/layout/simple.test.ts +321 -0
  73. package/test/benchmarks/layout/stress.test.ts +990 -0
  74. package/test/collection/basic.test.ts +304 -0
  75. package/test/components/list.test.ts +256 -0
  76. package/test/core/collection/collection.test.ts +394 -0
  77. package/test/core/collection/failed-ranges.test.ts +270 -0
  78. package/test/core/compose/features.test.ts +183 -0
  79. package/test/core/layout/layout.test.ts +201 -0
  80. package/test/core/list-manager/features/collection.test.ts +704 -0
  81. package/test/core/list-manager/features/viewport.test.ts +698 -0
  82. package/test/core/list-manager/list-manager.test.ts +593 -0
  83. package/test/core/list-manager/utils/calculations.test.ts +433 -0
  84. package/test/core/list-manager/utils/range-calculator.test.ts +569 -0
  85. package/test/core/list-manager/utils/speed-tracker.test.ts +530 -0
  86. package/test/utils/dom-helpers.ts +275 -0
  87. package/test/utils/performance-helpers.ts +392 -0
  88. package/tsconfig.build.json +14 -0
  89. package/tsconfig.json +20 -0
  90. package/dist/index.d.ts +0 -5
  91. package/dist/index.js +0 -38
  92. package/dist/index.mjs +0 -8
@@ -0,0 +1,963 @@
1
+ /**
2
+ * @module core/layout/schema
3
+ * @description Unified layout schema processor with integrated optimizations
4
+ * Consolidates array, object, JSX, and template processing with built-in performance enhancements
5
+ */
6
+
7
+ // ============================================================================
8
+ // TYPES (Essential only)
9
+ // ============================================================================
10
+
11
+ export interface LayoutConfig {
12
+ /** Base layout type */
13
+ type?: "stack" | "row" | "grid" | string;
14
+ /** Spacing between elements */
15
+ gap?: number | string;
16
+ /** Additional CSS classes */
17
+ class?: string;
18
+ /** Alignment of items along the cross axis */
19
+ align?: "start" | "center" | "end" | "stretch";
20
+ /** Alignment of items along the main axis */
21
+ justify?: "start" | "center" | "end" | "between" | "around" | "evenly";
22
+ /** Whether and how items should wrap */
23
+ wrap?: boolean | "reverse" | "nowrap";
24
+ /** Whether row items should stack vertically on mobile */
25
+ mobileStack?: boolean;
26
+ /** Whether row items should scroll horizontally on mobile */
27
+ mobileScroll?: boolean;
28
+ /** Number of columns or automatic sizing method */
29
+ columns?: number | "auto-fit" | "auto-fill";
30
+ /** Whether to use dense packing algorithm for grid */
31
+ dense?: boolean;
32
+ /** Whether grid items should adjust height automatically */
33
+ autoHeight?: boolean;
34
+ }
35
+
36
+ export interface LayoutItemConfig {
37
+ /** Column width in a 12-column grid */
38
+ width?: number;
39
+ /** Width on small screens */
40
+ sm?: number;
41
+ /** Width on medium screens */
42
+ md?: number;
43
+ /** Width on large screens */
44
+ lg?: number;
45
+ /** Width on extra-large screens */
46
+ xl?: number;
47
+ /** Number of grid columns to span */
48
+ span?: number;
49
+ /** Number of grid rows to span */
50
+ rowSpan?: number;
51
+ /** Display order */
52
+ order?: number | "first" | "last";
53
+ /** Self-alignment within container */
54
+ align?: "start" | "center" | "end" | "stretch";
55
+ /** Whether item should automatically size */
56
+ auto?: boolean;
57
+ }
58
+
59
+ export interface LayoutOptions {
60
+ /** Default creator function to use if not specified in schema */
61
+ creator?: Function;
62
+ /** Whether to apply CSS class prefix @default true */
63
+ prefix?: boolean;
64
+ /** Additional options */
65
+ [key: string]: any;
66
+ }
67
+
68
+ export interface LayoutResult {
69
+ /** The raw layout object with all components */
70
+ layout: Record<string, any>;
71
+ /** Reference to the root element for convenience */
72
+ element: HTMLElement | any;
73
+ /** Flattened component map */
74
+ component: Record<string, any>;
75
+ /** Gets a component by name */
76
+ get(name: string): any;
77
+ /** Gets all components in a flattened map */
78
+ getAll(): Record<string, any>;
79
+ /** Destroys the layout, cleaning up all components */
80
+ destroy(): void;
81
+ }
82
+
83
+ type ComponentLike = { element: HTMLElement; [key: string]: any };
84
+ type SchemaItem = Function | string | Record<string, any> | SchemaItem[];
85
+
86
+ // ============================================================================
87
+ // BUILT-IN OPTIMIZATIONS
88
+ // ============================================================================
89
+
90
+ /**
91
+ * Fragment Pool for efficient DocumentFragment reuse
92
+ * Reduces GC pressure in high-frequency layout creation scenarios
93
+ */
94
+ class FragmentPool {
95
+ private pool: DocumentFragment[] = [];
96
+ private maxSize = 8; // Optimized size for memory efficiency
97
+
98
+ get(): DocumentFragment {
99
+ return this.pool.pop() || document.createDocumentFragment();
100
+ }
101
+
102
+ release(fragment: DocumentFragment): void {
103
+ if (this.pool.length < this.maxSize && fragment.childNodes.length === 0) {
104
+ this.pool.push(fragment);
105
+ }
106
+ }
107
+
108
+ clear(): void {
109
+ this.pool.length = 0;
110
+ }
111
+ }
112
+
113
+ const fragmentPool = new FragmentPool();
114
+
115
+ /**
116
+ * Class name cache for layout configurations
117
+ * Reduces string operations by caching generated class names
118
+ */
119
+ const classCache = new Map<string, string>();
120
+
121
+ // Configuration constants
122
+ const PREFIX = "mtrl"; // TODO: Make this configurable
123
+ const PREFIX_WITH_DASH = `${PREFIX}-`;
124
+
125
+ /**
126
+ * Gets a cached class name for layout configuration
127
+ * Optimized for different class naming patterns
128
+ */
129
+ function getCachedClassName(
130
+ type: string,
131
+ property: string,
132
+ value: string | number
133
+ ): string {
134
+ const key = `${type}-${property}-${value}`;
135
+ if (!classCache.has(key)) {
136
+ if (type === "item") {
137
+ classCache.set(
138
+ key,
139
+ property === ""
140
+ ? `layout__item--${value}`
141
+ : `layout__item--${property}-${value}`
142
+ );
143
+ } else {
144
+ // For layout classes, align uses layout--{type}-{value}
145
+ // but justify and others use layout--{type}-{property}-{value}
146
+ if (property === "align") {
147
+ classCache.set(key, `layout--${type}-${value}`);
148
+ } else {
149
+ classCache.set(key, `layout--${type}-${property}-${value}`);
150
+ }
151
+ }
152
+ }
153
+ return classCache.get(key)!;
154
+ }
155
+
156
+ // ============================================================================
157
+ // OPTIMIZED UTILITIES
158
+ // ============================================================================
159
+
160
+ /**
161
+ * Checks if a value is a component object (has an element property)
162
+ */
163
+ function isComponent(value: any): value is ComponentLike {
164
+ return value && typeof value === "object" && "element" in value;
165
+ }
166
+
167
+ /**
168
+ * Creates a document fragment using pooling for better performance
169
+ */
170
+ function createFragment(): DocumentFragment {
171
+ return fragmentPool.get();
172
+ }
173
+
174
+ /**
175
+ * Releases a fragment back to the pool for reuse
176
+ */
177
+ function releaseFragment(fragment: DocumentFragment): void {
178
+ fragmentPool.release(fragment);
179
+ }
180
+
181
+ /**
182
+ * Optimized class processing with minimal string operations
183
+ * Handles arrays, strings, and className aliases efficiently
184
+ */
185
+ function processClassNames(
186
+ options: Record<string, any>,
187
+ skipPrefix = false
188
+ ): Record<string, any> {
189
+ if (!options || skipPrefix) return { ...options };
190
+
191
+ const hasClassProps = options.class || options.className;
192
+ if (!hasClassProps) return { ...options };
193
+
194
+ const processed = { ...options };
195
+
196
+ // Combine class sources efficiently
197
+ let classString = "";
198
+ if (processed.class) {
199
+ // Handle arrays by joining first
200
+ if (Array.isArray(processed.class)) {
201
+ classString += processed.class.join(" ");
202
+ } else {
203
+ classString += processed.class;
204
+ }
205
+ }
206
+ if (processed.className) {
207
+ classString += (classString ? " " : "") + processed.className;
208
+ }
209
+
210
+ if (classString) {
211
+ // Single pass prefix application
212
+ processed.class = classString
213
+ .split(/\s+/)
214
+ .filter(Boolean)
215
+ .map((cls) =>
216
+ cls && !cls.startsWith(PREFIX_WITH_DASH) ? PREFIX_WITH_DASH + cls : cls
217
+ )
218
+ .join(" ");
219
+ }
220
+
221
+ delete processed.className;
222
+ return processed;
223
+ }
224
+
225
+ /**
226
+ * Optimized parameter extraction for array schemas
227
+ * Reduces multiple array lookups with batch processing
228
+ */
229
+ interface ExtractedParameters {
230
+ creator: Function;
231
+ name?: string;
232
+ options: Record<string, any>;
233
+ consumed: number;
234
+ }
235
+
236
+ function extractParameters(
237
+ schema: SchemaItem[],
238
+ startIndex: number,
239
+ defaultCreator: Function
240
+ ): ExtractedParameters {
241
+ const items = schema.slice(startIndex, startIndex + 3);
242
+ let creator, name, options;
243
+ let consumed = 1;
244
+
245
+ const [first, second, third] = items;
246
+
247
+ if (typeof first === "function") {
248
+ creator = first;
249
+ if (typeof second === "string") {
250
+ name = second;
251
+ consumed = 2;
252
+ if (isObject(third)) {
253
+ options = third;
254
+ consumed = 3;
255
+ }
256
+ } else if (isObject(second)) {
257
+ options = second;
258
+ consumed = 2;
259
+ }
260
+ } else if (typeof first === "string") {
261
+ creator = defaultCreator;
262
+ name = first;
263
+ if (isObject(second)) {
264
+ options = second;
265
+ consumed = 2;
266
+ }
267
+ } else if (isObject(first)) {
268
+ creator = defaultCreator;
269
+ options = first;
270
+ }
271
+
272
+ return {
273
+ creator: creator || defaultCreator,
274
+ name,
275
+ options: options || {},
276
+ consumed,
277
+ };
278
+ }
279
+
280
+ /**
281
+ * Simple object type check
282
+ */
283
+ function isObject(value: any): boolean {
284
+ return value !== null && typeof value === "object" && !Array.isArray(value);
285
+ }
286
+
287
+ // ============================================================================
288
+ // DOM UTILITIES (Simplified)
289
+ // ============================================================================
290
+
291
+ /**
292
+ * Simple DOM element creation
293
+ */
294
+ function createElement(options: Record<string, any> = {}): HTMLElement {
295
+ const tag = options.tag || "div";
296
+ const element = document.createElement(tag);
297
+
298
+ if (options.class) {
299
+ element.className = options.class;
300
+ }
301
+
302
+ if (options.style) {
303
+ if (typeof options.style === "string") {
304
+ element.setAttribute("style", options.style);
305
+ } else if (typeof options.style === "object") {
306
+ Object.assign(element.style, options.style);
307
+ }
308
+ }
309
+
310
+ if (options.textContent) {
311
+ element.textContent = options.textContent;
312
+ }
313
+
314
+ if (options.text) {
315
+ element.textContent = options.text;
316
+ }
317
+
318
+ return element;
319
+ }
320
+
321
+ /**
322
+ * Adds a CSS class to an element
323
+ */
324
+ function addClass(element: HTMLElement, className: string): void {
325
+ if (element && className) {
326
+ element.classList.add(className);
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Checks if element has a CSS class
332
+ */
333
+ function hasClass(element: HTMLElement, className: string): boolean {
334
+ return element && element.classList.contains(className);
335
+ }
336
+
337
+ // ============================================================================
338
+ // UNIFIED COMPONENT CREATION (with destructuring optimization)
339
+ // ============================================================================
340
+
341
+ /**
342
+ * Creates a component instance with optimized option processing
343
+ * Uses destructuring for cleaner separation of concerns
344
+ */
345
+ function createComponentInstance(
346
+ Component: any,
347
+ options: Record<string, any> = {}
348
+ ): any {
349
+ try {
350
+ // Destructure special configs in one operation
351
+ const {
352
+ layout: layoutConfig,
353
+ layoutItem: layoutItemConfig,
354
+ style: styleConfig,
355
+ attributes: attributesConfig,
356
+ events: eventsConfig,
357
+ event, // Legacy support
358
+ ...cleanOptions
359
+ } = options;
360
+
361
+ // Use events over event (events is preferred)
362
+ const finalEventsConfig = eventsConfig || event;
363
+
364
+ // If style is a string, always pass it through to the component
365
+ if (styleConfig && typeof styleConfig === "string") {
366
+ cleanOptions.style = styleConfig;
367
+ }
368
+
369
+ // Create component
370
+ const isClass =
371
+ typeof Component === "function" &&
372
+ Object.getOwnPropertyDescriptor(Component, "prototype")?.writable ===
373
+ false;
374
+
375
+ const component = isClass
376
+ ? new Component(cleanOptions)
377
+ : Component(cleanOptions);
378
+
379
+ // Apply configurations if component has element
380
+ if (component) {
381
+ const element =
382
+ component.element ||
383
+ (component instanceof HTMLElement ? component : null);
384
+ if (element) {
385
+ // Apply layout classes
386
+ if (layoutConfig) applyLayoutClasses(element, layoutConfig);
387
+
388
+ // Apply layout item classes
389
+ if (layoutItemConfig) applyLayoutItemClasses(element, layoutItemConfig);
390
+
391
+ // Apply style
392
+ if (styleConfig && typeof styleConfig === "object") {
393
+ Object.assign(element.style, styleConfig);
394
+ }
395
+
396
+ // Apply attributes
397
+ if (attributesConfig && typeof attributesConfig === "object") {
398
+ for (const [key, value] of Object.entries(attributesConfig)) {
399
+ if (value !== undefined && value !== null) {
400
+ element.setAttribute(key, value.toString());
401
+ }
402
+ }
403
+ }
404
+
405
+ // Apply events
406
+ if (finalEventsConfig && typeof finalEventsConfig === "object") {
407
+ if (Array.isArray(finalEventsConfig)) {
408
+ for (const eventDef of finalEventsConfig) {
409
+ if (Array.isArray(eventDef) && eventDef.length >= 2) {
410
+ const [eventName, handler] = eventDef;
411
+ if (
412
+ typeof eventName === "string" &&
413
+ typeof handler === "function"
414
+ ) {
415
+ element.addEventListener(eventName, handler);
416
+ }
417
+ }
418
+ }
419
+ } else {
420
+ for (const [eventName, handler] of Object.entries(
421
+ finalEventsConfig
422
+ )) {
423
+ if (typeof handler === "function") {
424
+ element.addEventListener(eventName, handler);
425
+ }
426
+ }
427
+ }
428
+ }
429
+ }
430
+ }
431
+
432
+ return component;
433
+ } catch (error) {
434
+ console.error("Error creating component instance:", error);
435
+ return document.createElement("div");
436
+ }
437
+ }
438
+
439
+ // ============================================================================
440
+ // INTEGRATED LAYOUT CONFIGURATION (with caching)
441
+ // ============================================================================
442
+
443
+ /**
444
+ * Applies layout classes based on configuration
445
+ * Uses integrated caching for optimal performance
446
+ */
447
+ function applyLayoutClasses(
448
+ element: HTMLElement,
449
+ layoutConfig: LayoutConfig
450
+ ): void {
451
+ if (!element || !layoutConfig) return;
452
+
453
+ // Apply base layout type
454
+ if (layoutConfig.type) {
455
+ addClass(element, `${PREFIX_WITH_DASH}layout--${layoutConfig.type}`);
456
+ }
457
+
458
+ // Apply properties with caching
459
+ const layoutType = layoutConfig.type || getLayoutType(element);
460
+ if (layoutType) {
461
+ if (layoutConfig.gap !== undefined) {
462
+ addClass(
463
+ element,
464
+ PREFIX_WITH_DASH +
465
+ getCachedClassName(layoutType, "gap", layoutConfig.gap)
466
+ );
467
+ }
468
+ if (layoutConfig.align) {
469
+ addClass(
470
+ element,
471
+ PREFIX_WITH_DASH +
472
+ getCachedClassName(layoutType, "align", layoutConfig.align)
473
+ );
474
+ }
475
+ if (layoutConfig.justify) {
476
+ addClass(
477
+ element,
478
+ PREFIX_WITH_DASH +
479
+ getCachedClassName(layoutType, "justify", layoutConfig.justify)
480
+ );
481
+ }
482
+ }
483
+
484
+ // Grid-specific properties
485
+ if (layoutConfig.type === "grid" || getLayoutType(element) === "grid") {
486
+ if (typeof layoutConfig.columns === "number") {
487
+ addClass(
488
+ element,
489
+ PREFIX_WITH_DASH +
490
+ getCachedClassName("grid", "cols", layoutConfig.columns)
491
+ );
492
+ } else if (layoutConfig.columns === "auto-fill") {
493
+ addClass(
494
+ element,
495
+ PREFIX_WITH_DASH + getCachedClassName("grid", "fill", "auto")
496
+ );
497
+ } else if (layoutConfig.columns === "auto-fit") {
498
+ addClass(
499
+ element,
500
+ PREFIX_WITH_DASH + getCachedClassName("grid", "cols", "auto-fit")
501
+ );
502
+ }
503
+ if (layoutConfig.dense)
504
+ addClass(element, `${PREFIX_WITH_DASH}layout--grid-dense`);
505
+ if (layoutConfig.autoHeight)
506
+ addClass(element, `${PREFIX_WITH_DASH}layout--grid-auto-height`);
507
+ }
508
+
509
+ // Row-specific properties
510
+ if (layoutConfig.type === "row" || getLayoutType(element) === "row") {
511
+ if (layoutConfig.wrap === false || layoutConfig.wrap === "nowrap") {
512
+ addClass(element, `${PREFIX_WITH_DASH}layout--row-nowrap`);
513
+ } else if (layoutConfig.wrap === "reverse") {
514
+ addClass(element, `${PREFIX_WITH_DASH}layout--row-wrap-reverse`);
515
+ }
516
+ if (layoutConfig.mobileStack)
517
+ addClass(element, `${PREFIX_WITH_DASH}layout--row-mobile-stack`);
518
+ if (layoutConfig.mobileScroll)
519
+ addClass(element, `${PREFIX_WITH_DASH}layout--row-mobile-scroll`);
520
+ }
521
+
522
+ // Custom classes
523
+ if (layoutConfig.class) {
524
+ layoutConfig.class
525
+ .split(" ")
526
+ .filter(Boolean)
527
+ .forEach((cls) => element.classList.add(cls));
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Applies layout item classes based on configuration
533
+ * Uses integrated caching for optimal performance
534
+ */
535
+ function applyLayoutItemClasses(
536
+ element: HTMLElement,
537
+ itemConfig: LayoutItemConfig
538
+ ): void {
539
+ if (!element || !itemConfig) return;
540
+
541
+ addClass(element, `${PREFIX_WITH_DASH}layout__item`);
542
+
543
+ // Width and responsive classes with caching
544
+ if (itemConfig.width && itemConfig.width >= 1 && itemConfig.width <= 12) {
545
+ addClass(
546
+ element,
547
+ PREFIX_WITH_DASH + getCachedClassName("item", "", itemConfig.width)
548
+ );
549
+ }
550
+ if (itemConfig.sm)
551
+ addClass(
552
+ element,
553
+ PREFIX_WITH_DASH + getCachedClassName("item", "sm", itemConfig.sm)
554
+ );
555
+ if (itemConfig.md)
556
+ addClass(
557
+ element,
558
+ PREFIX_WITH_DASH + getCachedClassName("item", "md", itemConfig.md)
559
+ );
560
+ if (itemConfig.lg)
561
+ addClass(
562
+ element,
563
+ PREFIX_WITH_DASH + getCachedClassName("item", "lg", itemConfig.lg)
564
+ );
565
+ if (itemConfig.xl)
566
+ addClass(
567
+ element,
568
+ PREFIX_WITH_DASH + getCachedClassName("item", "xl", itemConfig.xl)
569
+ );
570
+
571
+ // Grid span classes
572
+ if (itemConfig.span)
573
+ addClass(
574
+ element,
575
+ PREFIX_WITH_DASH + getCachedClassName("item", "span", itemConfig.span)
576
+ );
577
+ if (itemConfig.rowSpan)
578
+ addClass(
579
+ element,
580
+ PREFIX_WITH_DASH +
581
+ getCachedClassName("item", "row-span", itemConfig.rowSpan)
582
+ );
583
+
584
+ // Order and alignment
585
+ if (itemConfig.order)
586
+ addClass(
587
+ element,
588
+ PREFIX_WITH_DASH + getCachedClassName("item", "order", itemConfig.order)
589
+ );
590
+ if (itemConfig.align)
591
+ addClass(
592
+ element,
593
+ PREFIX_WITH_DASH + getCachedClassName("item", "self", itemConfig.align)
594
+ );
595
+ if (itemConfig.auto)
596
+ addClass(element, `${PREFIX_WITH_DASH}layout__item--auto`);
597
+ }
598
+
599
+ /**
600
+ * Gets the layout type from element classes
601
+ */
602
+ function getLayoutType(element: HTMLElement): string {
603
+ return hasClass(element, `${PREFIX_WITH_DASH}layout--stack`)
604
+ ? "stack"
605
+ : hasClass(element, `${PREFIX_WITH_DASH}layout--row`)
606
+ ? "row"
607
+ : hasClass(element, `${PREFIX_WITH_DASH}layout--grid`)
608
+ ? "grid"
609
+ : "";
610
+ }
611
+
612
+ // ============================================================================
613
+ // UNIFIED SCHEMA PROCESSORS
614
+ // ============================================================================
615
+
616
+ /**
617
+ * Processes array-based schema definitions
618
+ * Optimized with parameter extraction and integrated configuration
619
+ */
620
+ function processArraySchema(
621
+ schema: SchemaItem[],
622
+ parentElement: HTMLElement | null = null,
623
+ level: number = 0,
624
+ options: LayoutOptions = {}
625
+ ): LayoutResult {
626
+ level++;
627
+ const layout: Record<string, any> = {};
628
+ const components: Array<[string, any]> = [];
629
+ const fragment = createFragment();
630
+ let component = null;
631
+
632
+ if (!Array.isArray(schema)) {
633
+ return createLayoutResult(layout);
634
+ }
635
+
636
+ const defaultCreator = options.creator || createElement;
637
+
638
+ for (let i = 0; i < schema.length; i++) {
639
+ const item = schema[i];
640
+ if (!item) continue;
641
+
642
+ // Handle nested arrays
643
+ if (Array.isArray(item)) {
644
+ const container = component || parentElement;
645
+ const result = processArraySchema(item, container, level, options);
646
+ Object.assign(layout, result.layout);
647
+ continue;
648
+ }
649
+
650
+ // Use optimized parameter extraction
651
+ const {
652
+ creator,
653
+ name,
654
+ options: itemOptions,
655
+ consumed,
656
+ } = extractParameters(schema, i, defaultCreator);
657
+
658
+ if (!creator) {
659
+ console.warn("Skipping unsupported item type:", item);
660
+ continue;
661
+ }
662
+
663
+ // Default to div for createElement
664
+ if (creator === createElement && !("tag" in itemOptions)) {
665
+ itemOptions.tag = "div";
666
+ }
667
+
668
+ // Advance index by consumed items minus 1 (loop increment handles the +1)
669
+ i += consumed - 1;
670
+
671
+ // Process options with prefix
672
+ const shouldApplyPrefix =
673
+ "prefix" in itemOptions ? itemOptions.prefix : options.prefix !== false;
674
+ const processedOptions = shouldApplyPrefix
675
+ ? processClassNames(itemOptions)
676
+ : { ...itemOptions };
677
+
678
+ // Add name to options if needed
679
+ if (
680
+ name &&
681
+ !("name" in processedOptions) &&
682
+ !(creator === createElement || (creator as any).isElement)
683
+ ) {
684
+ processedOptions.name = name;
685
+ }
686
+
687
+ // Create component
688
+ component = createComponentInstance(creator, processedOptions);
689
+ const element = isComponent(component) ? component.element : component;
690
+
691
+ if (level === 1) layout.element = element;
692
+ if (name) {
693
+ layout[name] = component;
694
+ components.push([name, component]);
695
+ }
696
+
697
+ // Append to DOM
698
+ if (component) {
699
+ if ("insert" in component && typeof component.insert === "function") {
700
+ component.insert(fragment);
701
+ } else {
702
+ fragment.appendChild(element);
703
+ }
704
+
705
+ if (parentElement) {
706
+ component._container = parentElement;
707
+ if (
708
+ "onInserted" in component &&
709
+ typeof component.onInserted === "function"
710
+ ) {
711
+ component.onInserted(parentElement);
712
+ }
713
+ }
714
+ }
715
+ }
716
+
717
+ // Append fragment to parent
718
+ if (parentElement && fragment.hasChildNodes()) {
719
+ const wrapper = isComponent(parentElement)
720
+ ? parentElement.element
721
+ : parentElement;
722
+ wrapper.appendChild(fragment);
723
+ }
724
+
725
+ // Release fragment back to pool
726
+ releaseFragment(fragment);
727
+
728
+ layout.components = components;
729
+ return createLayoutResult(layout);
730
+ }
731
+
732
+ /**
733
+ * Processes object-based schema definitions
734
+ * Simplified and optimized for better performance
735
+ */
736
+ function processObjectSchema(
737
+ schema: Record<string, any>,
738
+ parentElement: HTMLElement | null = null,
739
+ options: LayoutOptions = {}
740
+ ): LayoutResult {
741
+ const layout: Record<string, any> = {};
742
+ const defaultCreator = options.creator || createElement;
743
+
744
+ // Handle root element creation
745
+ if (schema.element && !parentElement) {
746
+ const elementDef = schema.element;
747
+ const createElementFn = elementDef.creator || defaultCreator;
748
+
749
+ const elementOptions = elementDef.options || {};
750
+ const processedOptions =
751
+ options.prefix !== false
752
+ ? processClassNames(elementOptions)
753
+ : { ...elementOptions };
754
+
755
+ const rootComponent = createComponentInstance(
756
+ createElementFn,
757
+ processedOptions
758
+ );
759
+ layout.element = rootComponent;
760
+ if (elementDef.name) layout[elementDef.name] = rootComponent;
761
+
762
+ // Process children
763
+ if (elementDef.children) {
764
+ const rootElement = isComponent(rootComponent)
765
+ ? rootComponent.element
766
+ : rootComponent;
767
+ const childResult = processObjectSchema(
768
+ elementDef.children,
769
+ rootElement,
770
+ options
771
+ );
772
+ Object.assign(layout, childResult.layout);
773
+ }
774
+
775
+ return createLayoutResult(layout);
776
+ }
777
+
778
+ // Process normal schema elements
779
+ const fragment = parentElement ? createFragment() : null;
780
+
781
+ for (const key in schema) {
782
+ const def = schema[key];
783
+ if (!def) continue;
784
+
785
+ const elementCreator = def.creator || defaultCreator;
786
+ const elementOptions = def.options || {};
787
+ const shouldApplyPrefix =
788
+ "prefix" in elementOptions
789
+ ? elementOptions.prefix
790
+ : options.prefix !== false;
791
+ const processedOptions = shouldApplyPrefix
792
+ ? processClassNames(elementOptions)
793
+ : { ...elementOptions };
794
+
795
+ if (!def.name && key !== "element") {
796
+ def.name = key;
797
+ }
798
+
799
+ const created = createComponentInstance(elementCreator, processedOptions);
800
+ layout[key] = created;
801
+ if (def.name && def.name !== key) layout[def.name] = created;
802
+
803
+ const element = isComponent(created) ? created.element : created;
804
+ if (fragment) fragment.appendChild(element);
805
+
806
+ // Process children
807
+ if (def.children) {
808
+ const childResult = processObjectSchema(def.children, element, options);
809
+ Object.assign(layout, childResult.layout);
810
+ }
811
+ }
812
+
813
+ // Append to parent
814
+ if (parentElement && fragment) {
815
+ const parentDom = isComponent(parentElement)
816
+ ? parentElement.element
817
+ : parentElement;
818
+ parentDom.appendChild(fragment);
819
+ releaseFragment(fragment);
820
+ }
821
+
822
+ return createLayoutResult(layout);
823
+ }
824
+
825
+ // ============================================================================
826
+ // LAYOUT RESULT CREATION
827
+ // ============================================================================
828
+
829
+ /**
830
+ * Flattens a nested layout into a simple object with element and component references
831
+ */
832
+ function flattenLayout(layout: Record<string, any>): Record<string, any> {
833
+ const flattened: Record<string, any> = {};
834
+
835
+ if (!layout || typeof layout !== "object") return flattened;
836
+
837
+ for (const key in layout) {
838
+ const value = layout[key];
839
+ if (
840
+ value &&
841
+ typeof value !== "function" &&
842
+ (value instanceof HTMLElement ||
843
+ (typeof SVGElement !== "undefined" && value instanceof SVGElement) ||
844
+ isComponent(value))
845
+ ) {
846
+ flattened[key] = value;
847
+ }
848
+ }
849
+
850
+ return flattened;
851
+ }
852
+
853
+ /**
854
+ * Creates a layout result object with utility functions
855
+ */
856
+ function createLayoutResult(layout: Record<string, any>): LayoutResult {
857
+ const flattenedComponents = flattenLayout(layout);
858
+
859
+ return {
860
+ layout,
861
+ element: layout.element,
862
+ component: flattenedComponents,
863
+
864
+ get(name: string): any {
865
+ return layout[name] ?? null;
866
+ },
867
+
868
+ getAll(): Record<string, any> {
869
+ return flattenedComponents;
870
+ },
871
+
872
+ destroy(): void {
873
+ // Call destroy on components
874
+ for (const key in layout) {
875
+ const component = layout[key];
876
+ if (component && typeof component.destroy === "function") {
877
+ component.destroy();
878
+ }
879
+ }
880
+
881
+ // Remove root element from DOM
882
+ if (layout.element) {
883
+ const element = isComponent(layout.element)
884
+ ? layout.element.element
885
+ : layout.element;
886
+ if (element && element.parentNode) {
887
+ element.parentNode.removeChild(element);
888
+ }
889
+ }
890
+ },
891
+ };
892
+ }
893
+
894
+ // ============================================================================
895
+ // UNIFIED ENTRY POINT
896
+ // ============================================================================
897
+
898
+ /**
899
+ * Creates a layout from various schema formats
900
+ * Unified processor for arrays, objects, JSX, and HTML strings
901
+ */
902
+ export function createLayout(
903
+ schema: any,
904
+ parentElement: HTMLElement | null = null,
905
+ options: LayoutOptions = {}
906
+ ): LayoutResult {
907
+ // Handle function schemas
908
+ if (typeof schema === "function") {
909
+ schema = schema();
910
+ }
911
+
912
+ // Handle HTML string schemas
913
+ if (typeof schema === "string") {
914
+ const template = document.createElement("template");
915
+ template.innerHTML = schema.trim();
916
+ const fragment = template.content;
917
+
918
+ if (parentElement && fragment.hasChildNodes()) {
919
+ parentElement.appendChild(fragment);
920
+ }
921
+
922
+ const layout = { element: fragment.firstElementChild as HTMLElement };
923
+ return createLayoutResult(layout);
924
+ }
925
+
926
+ // Handle JSX-like schemas (array with function, string, object pattern)
927
+ if (
928
+ Array.isArray(schema) &&
929
+ schema.length >= 3 &&
930
+ typeof schema[0] === "function" &&
931
+ typeof schema[1] === "string" &&
932
+ isObject(schema[2])
933
+ ) {
934
+ return processArraySchema(schema, parentElement, 0, options);
935
+ }
936
+
937
+ // Route to appropriate processor
938
+ return Array.isArray(schema)
939
+ ? processArraySchema(schema, parentElement, 0, options)
940
+ : processObjectSchema(schema, parentElement, options);
941
+ }
942
+
943
+ // ============================================================================
944
+ // EXPORTS
945
+ // ============================================================================
946
+
947
+ // Clear functions for the unified system
948
+ export function clearClassCache(): void {
949
+ classCache.clear();
950
+ }
951
+
952
+ export function clearFragmentPool(): void {
953
+ fragmentPool.clear();
954
+ }
955
+
956
+ export {
957
+ processClassNames,
958
+ isComponent,
959
+ flattenLayout,
960
+ applyLayoutClasses,
961
+ applyLayoutItemClasses,
962
+ createLayoutResult,
963
+ };