mtrl-addons 0.2.2 → 0.2.3

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 (128) hide show
  1. package/{src/components/index.ts → dist/components/index.d.ts} +0 -2
  2. package/dist/components/vlist/config.d.ts +86 -0
  3. package/{src/components/vlist/constants.ts → dist/components/vlist/constants.d.ts} +10 -11
  4. package/dist/components/vlist/features/api.d.ts +7 -0
  5. package/{src/components/vlist/features/index.ts → dist/components/vlist/features/index.d.ts} +0 -2
  6. package/dist/components/vlist/features/selection.d.ts +6 -0
  7. package/dist/components/vlist/features/viewport.d.ts +9 -0
  8. package/dist/components/vlist/features.d.ts +31 -0
  9. package/{src/components/vlist/index.ts → dist/components/vlist/index.d.ts} +1 -10
  10. package/dist/components/vlist/types.d.ts +596 -0
  11. package/dist/components/vlist/vlist.d.ts +29 -0
  12. package/dist/core/compose/features/gestures/index.d.ts +86 -0
  13. package/dist/core/compose/features/gestures/longpress.d.ts +85 -0
  14. package/dist/core/compose/features/gestures/pan.d.ts +108 -0
  15. package/dist/core/compose/features/gestures/pinch.d.ts +111 -0
  16. package/dist/core/compose/features/gestures/rotate.d.ts +111 -0
  17. package/dist/core/compose/features/gestures/swipe.d.ts +149 -0
  18. package/dist/core/compose/features/gestures/tap.d.ts +79 -0
  19. package/{src/core/compose/features/index.ts → dist/core/compose/features/index.d.ts} +1 -2
  20. package/{src/core/compose/index.ts → dist/core/compose/index.d.ts} +2 -11
  21. package/{src/core/gestures/index.ts → dist/core/gestures/index.d.ts} +1 -20
  22. package/dist/core/gestures/longpress.d.ts +23 -0
  23. package/dist/core/gestures/manager.d.ts +14 -0
  24. package/dist/core/gestures/pan.d.ts +12 -0
  25. package/dist/core/gestures/pinch.d.ts +14 -0
  26. package/dist/core/gestures/rotate.d.ts +14 -0
  27. package/dist/core/gestures/swipe.d.ts +20 -0
  28. package/dist/core/gestures/tap.d.ts +12 -0
  29. package/dist/core/gestures/types.d.ts +320 -0
  30. package/dist/core/gestures/utils.d.ts +57 -0
  31. package/dist/core/index.d.ts +13 -0
  32. package/dist/core/layout/config.d.ts +33 -0
  33. package/dist/core/layout/index.d.ts +51 -0
  34. package/dist/core/layout/jsx.d.ts +65 -0
  35. package/dist/core/layout/schema.d.ts +112 -0
  36. package/dist/core/layout/types.d.ts +69 -0
  37. package/dist/core/viewport/constants.d.ts +105 -0
  38. package/dist/core/viewport/features/base.d.ts +14 -0
  39. package/dist/core/viewport/features/collection.d.ts +41 -0
  40. package/dist/core/viewport/features/events.d.ts +13 -0
  41. package/{src/core/viewport/features/index.ts → dist/core/viewport/features/index.d.ts} +0 -7
  42. package/dist/core/viewport/features/item-size.d.ts +30 -0
  43. package/dist/core/viewport/features/loading.d.ts +34 -0
  44. package/dist/core/viewport/features/momentum.d.ts +17 -0
  45. package/dist/core/viewport/features/performance.d.ts +53 -0
  46. package/dist/core/viewport/features/placeholders.d.ts +38 -0
  47. package/dist/core/viewport/features/rendering.d.ts +16 -0
  48. package/dist/core/viewport/features/scrollbar.d.ts +26 -0
  49. package/dist/core/viewport/features/scrolling.d.ts +16 -0
  50. package/dist/core/viewport/features/utils.d.ts +43 -0
  51. package/dist/core/viewport/features/virtual.d.ts +18 -0
  52. package/{src/core/viewport/index.ts → dist/core/viewport/index.d.ts} +1 -17
  53. package/dist/core/viewport/types.d.ts +96 -0
  54. package/dist/core/viewport/utils/speed-tracker.d.ts +22 -0
  55. package/dist/core/viewport/viewport.d.ts +11 -0
  56. package/{src/index.ts → dist/index.d.ts} +0 -4
  57. package/dist/index.js +5143 -0
  58. package/dist/index.mjs +5111 -0
  59. package/dist/styles.css +254 -0
  60. package/dist/styles.css.map +1 -0
  61. package/package.json +5 -1
  62. package/.cursorrules +0 -117
  63. package/AI.md +0 -39
  64. package/CLAUDE.md +0 -882
  65. package/build.js +0 -377
  66. package/scripts/analyze-orphaned-functions.ts +0 -387
  67. package/scripts/debug/vlist-selection.ts +0 -121
  68. package/src/components/vlist/config.ts +0 -323
  69. package/src/components/vlist/features/api.ts +0 -626
  70. package/src/components/vlist/features/selection.ts +0 -436
  71. package/src/components/vlist/features/viewport.ts +0 -59
  72. package/src/components/vlist/features.ts +0 -112
  73. package/src/components/vlist/types.ts +0 -723
  74. package/src/components/vlist/vlist.ts +0 -92
  75. package/src/core/compose/features/gestures/index.ts +0 -227
  76. package/src/core/compose/features/gestures/longpress.ts +0 -383
  77. package/src/core/compose/features/gestures/pan.ts +0 -424
  78. package/src/core/compose/features/gestures/pinch.ts +0 -475
  79. package/src/core/compose/features/gestures/rotate.ts +0 -485
  80. package/src/core/compose/features/gestures/swipe.ts +0 -492
  81. package/src/core/compose/features/gestures/tap.ts +0 -334
  82. package/src/core/gestures/longpress.ts +0 -68
  83. package/src/core/gestures/manager.ts +0 -418
  84. package/src/core/gestures/pan.ts +0 -48
  85. package/src/core/gestures/pinch.ts +0 -58
  86. package/src/core/gestures/rotate.ts +0 -58
  87. package/src/core/gestures/swipe.ts +0 -66
  88. package/src/core/gestures/tap.ts +0 -45
  89. package/src/core/gestures/types.ts +0 -387
  90. package/src/core/gestures/utils.ts +0 -128
  91. package/src/core/index.ts +0 -43
  92. package/src/core/layout/config.ts +0 -102
  93. package/src/core/layout/index.ts +0 -168
  94. package/src/core/layout/jsx.ts +0 -174
  95. package/src/core/layout/schema.ts +0 -1044
  96. package/src/core/layout/types.ts +0 -95
  97. package/src/core/viewport/constants.ts +0 -145
  98. package/src/core/viewport/features/base.ts +0 -73
  99. package/src/core/viewport/features/collection.ts +0 -1182
  100. package/src/core/viewport/features/events.ts +0 -130
  101. package/src/core/viewport/features/item-size.ts +0 -271
  102. package/src/core/viewport/features/loading.ts +0 -263
  103. package/src/core/viewport/features/momentum.ts +0 -269
  104. package/src/core/viewport/features/performance.ts +0 -161
  105. package/src/core/viewport/features/placeholders.ts +0 -335
  106. package/src/core/viewport/features/rendering.ts +0 -962
  107. package/src/core/viewport/features/scrollbar.ts +0 -434
  108. package/src/core/viewport/features/scrolling.ts +0 -634
  109. package/src/core/viewport/features/utils.ts +0 -94
  110. package/src/core/viewport/features/virtual.ts +0 -525
  111. package/src/core/viewport/types.ts +0 -133
  112. package/src/core/viewport/utils/speed-tracker.ts +0 -79
  113. package/src/core/viewport/viewport.ts +0 -265
  114. package/test/benchmarks/layout/advanced.test.ts +0 -656
  115. package/test/benchmarks/layout/comparison.test.ts +0 -519
  116. package/test/benchmarks/layout/performance-comparison.test.ts +0 -274
  117. package/test/benchmarks/layout/real-components.test.ts +0 -733
  118. package/test/benchmarks/layout/simple.test.ts +0 -321
  119. package/test/benchmarks/layout/stress.test.ts +0 -990
  120. package/test/collection/basic.test.ts +0 -304
  121. package/test/components/vlist-selection.test.ts +0 -240
  122. package/test/components/vlist.test.ts +0 -63
  123. package/test/core/collection/adapter.test.ts +0 -161
  124. package/test/core/collection/collection.test.ts +0 -394
  125. package/test/core/layout/layout.test.ts +0 -201
  126. package/test/utils/dom-helpers.ts +0 -275
  127. package/test/utils/performance-helpers.ts +0 -392
  128. package/tsconfig.json +0 -20
@@ -1,1044 +0,0 @@
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, className aliases, and rawClass efficiently
184
- */
185
- function processClassNames(
186
- options: Record<string, any>,
187
- skipPrefix = false,
188
- ): Record<string, any> {
189
- if (!options) return options;
190
-
191
- const hasRawClass = options.rawClass;
192
- const hasRegularClass = options.class || options.className;
193
-
194
- // Fast path: no class properties at all
195
- if (!hasRawClass && !hasRegularClass) return options;
196
-
197
- // Fast path: only rawClass and skipping prefix (most common rawClass scenario)
198
- if (hasRawClass && !hasRegularClass && skipPrefix) {
199
- const processed = { ...options };
200
- delete processed.rawClass;
201
-
202
- // Direct assignment for simple string
203
- if (typeof hasRawClass === "string") {
204
- processed.class = hasRawClass;
205
- } else {
206
- // Handle array case
207
- processed.class = hasRawClass.join(" ");
208
- }
209
- return processed;
210
- }
211
-
212
- // Full processing path (only when needed)
213
- const processed = { ...options };
214
- let finalClasses = "";
215
-
216
- // Handle prefixed classes only if not skipping prefix
217
- if (!skipPrefix && hasRegularClass) {
218
- let prefixedString = "";
219
-
220
- if (processed.class) {
221
- prefixedString += Array.isArray(processed.class)
222
- ? processed.class.join(" ")
223
- : processed.class;
224
- }
225
- if (processed.className) {
226
- prefixedString += (prefixedString ? " " : "") + processed.className;
227
- }
228
-
229
- if (prefixedString) {
230
- finalClasses = prefixedString
231
- .split(/\s+/)
232
- .filter(Boolean)
233
- .map((cls) =>
234
- cls.startsWith(PREFIX_WITH_DASH) ? cls : PREFIX_WITH_DASH + cls,
235
- )
236
- .join(" ");
237
- }
238
- }
239
-
240
- // Handle rawClass (always processed when present)
241
- if (hasRawClass) {
242
- const rawString = Array.isArray(hasRawClass)
243
- ? hasRawClass.filter(Boolean).join(" ")
244
- : hasRawClass;
245
-
246
- finalClasses += (finalClasses ? " " : "") + rawString;
247
- }
248
-
249
- if (finalClasses) {
250
- processed.class = finalClasses;
251
- }
252
-
253
- // Clean up in one operation
254
- delete processed.className;
255
- delete processed.rawClass;
256
-
257
- return processed;
258
- }
259
-
260
- /**
261
- * Optimized parameter extraction for array schemas
262
- * Reduces multiple array lookups with batch processing
263
- */
264
- interface ExtractedParameters {
265
- creator: Function;
266
- name?: string;
267
- options: Record<string, any>;
268
- consumed: number;
269
- }
270
-
271
- function extractParameters(
272
- schema: SchemaItem[],
273
- startIndex: number,
274
- defaultCreator: Function,
275
- ): ExtractedParameters {
276
- const items = schema.slice(startIndex, startIndex + 3);
277
- let creator, name, options;
278
- let consumed = 1;
279
-
280
- const [first, second, third] = items;
281
-
282
- if (typeof first === "function") {
283
- creator = first;
284
- if (typeof second === "string") {
285
- name = second;
286
- consumed = 2;
287
- if (isObject(third)) {
288
- options = third;
289
- consumed = 3;
290
- }
291
- } else if (isObject(second)) {
292
- options = second;
293
- consumed = 2;
294
- }
295
- } else if (typeof first === "string") {
296
- creator = defaultCreator;
297
- name = first;
298
- if (isObject(second)) {
299
- options = second;
300
- consumed = 2;
301
- }
302
- } else if (isObject(first)) {
303
- creator = defaultCreator;
304
- options = first;
305
- }
306
-
307
- return {
308
- creator: creator || defaultCreator,
309
- name,
310
- options: (options || {}) as Record<string, any>,
311
- consumed,
312
- };
313
- }
314
-
315
- /**
316
- * Simple object type check
317
- */
318
- function isObject(value: any): boolean {
319
- return value !== null && typeof value === "object" && !Array.isArray(value);
320
- }
321
-
322
- // ============================================================================
323
- // DOM UTILITIES (Simplified)
324
- // ============================================================================
325
-
326
- /**
327
- * Simple DOM element creation
328
- */
329
- function createElement(options: Record<string, any> = {}): HTMLElement {
330
- const tag = options.tag || "div";
331
- const element = document.createElement(tag);
332
-
333
- if (options.class) {
334
- element.className = options.class;
335
- }
336
-
337
- if (options.style) {
338
- if (typeof options.style === "string") {
339
- element.setAttribute("style", options.style);
340
- } else if (typeof options.style === "object") {
341
- Object.assign(element.style, options.style);
342
- }
343
- }
344
-
345
- if (options.textContent) {
346
- element.textContent = options.textContent;
347
- }
348
-
349
- if (options.text) {
350
- element.textContent = options.text;
351
- }
352
-
353
- return element;
354
- }
355
-
356
- /**
357
- * Adds a CSS class to an element
358
- */
359
- function addClass(element: HTMLElement, className: string): void {
360
- if (element && className) {
361
- element.classList.add(className);
362
- }
363
- }
364
-
365
- /**
366
- * Checks if element has a CSS class
367
- */
368
- function hasClass(element: HTMLElement, className: string): boolean {
369
- return element && element.classList.contains(className);
370
- }
371
-
372
- // ============================================================================
373
- // UNIFIED COMPONENT CREATION (with destructuring optimization)
374
- // ============================================================================
375
-
376
- /**
377
- * Creates a component instance with optimized option processing
378
- * Uses destructuring for cleaner separation of concerns
379
- */
380
- function createComponentInstance(
381
- Component: any,
382
- options: Record<string, any> = {},
383
- ): any {
384
- try {
385
- // Destructure special configs in one operation
386
- const {
387
- layout: layoutConfig,
388
- layoutItem: layoutItemConfig,
389
- style: styleConfig,
390
- attributes: attributesConfig,
391
- events: eventsConfig,
392
- event, // Legacy support
393
- ...cleanOptions
394
- } = options;
395
-
396
- // Use events over event (events is preferred)
397
- const finalEventsConfig = eventsConfig || event;
398
-
399
- // If style is a string, always pass it through to the component
400
- if (styleConfig && typeof styleConfig === "string") {
401
- cleanOptions.style = styleConfig;
402
- }
403
-
404
- // Create component
405
- const isClass =
406
- typeof Component === "function" &&
407
- Object.getOwnPropertyDescriptor(Component, "prototype")?.writable ===
408
- false;
409
-
410
- const component = isClass
411
- ? new Component(cleanOptions)
412
- : Component(cleanOptions);
413
-
414
- // Apply configurations if component has element
415
- if (component) {
416
- const element =
417
- component.element ||
418
- (component instanceof HTMLElement ? component : null);
419
- if (element) {
420
- // Apply layout classes
421
- if (layoutConfig) applyLayoutClasses(element, layoutConfig);
422
-
423
- // Apply layout item classes
424
- if (layoutItemConfig) applyLayoutItemClasses(element, layoutItemConfig);
425
-
426
- // Apply style
427
- if (styleConfig && typeof styleConfig === "object") {
428
- Object.assign(element.style, styleConfig);
429
- }
430
-
431
- // Apply attributes
432
- if (attributesConfig && typeof attributesConfig === "object") {
433
- for (const [key, value] of Object.entries(attributesConfig)) {
434
- if (value !== undefined && value !== null) {
435
- element.setAttribute(key, value.toString());
436
- }
437
- }
438
- }
439
-
440
- // Apply events
441
- if (finalEventsConfig && typeof finalEventsConfig === "object") {
442
- if (Array.isArray(finalEventsConfig)) {
443
- for (const eventDef of finalEventsConfig) {
444
- if (Array.isArray(eventDef) && eventDef.length >= 2) {
445
- const [eventName, handler] = eventDef;
446
- if (
447
- typeof eventName === "string" &&
448
- typeof handler === "function"
449
- ) {
450
- element.addEventListener(eventName, handler);
451
- }
452
- }
453
- }
454
- } else {
455
- for (const [eventName, handler] of Object.entries(
456
- finalEventsConfig,
457
- )) {
458
- if (typeof handler === "function") {
459
- element.addEventListener(eventName, handler);
460
- }
461
- }
462
- }
463
- }
464
- }
465
- }
466
-
467
- return component;
468
- } catch (error) {
469
- console.error("Error creating component instance:", error);
470
- return document.createElement("div");
471
- }
472
- }
473
-
474
- // ============================================================================
475
- // INTEGRATED LAYOUT CONFIGURATION (with caching)
476
- // ============================================================================
477
-
478
- /**
479
- * Applies layout classes based on configuration
480
- * Uses integrated caching for optimal performance
481
- */
482
- function applyLayoutClasses(
483
- element: HTMLElement,
484
- layoutConfig: LayoutConfig,
485
- ): void {
486
- if (!element || !layoutConfig) return;
487
-
488
- // Apply base layout type
489
- if (layoutConfig.type) {
490
- addClass(element, `${PREFIX_WITH_DASH}layout--${layoutConfig.type}`);
491
- }
492
-
493
- // Apply properties with caching
494
- const layoutType = layoutConfig.type || getLayoutType(element);
495
- if (layoutType) {
496
- if (layoutConfig.gap !== undefined) {
497
- addClass(
498
- element,
499
- PREFIX_WITH_DASH +
500
- getCachedClassName(layoutType, "gap", layoutConfig.gap),
501
- );
502
- }
503
- if (layoutConfig.align) {
504
- addClass(
505
- element,
506
- PREFIX_WITH_DASH +
507
- getCachedClassName(layoutType, "align", layoutConfig.align),
508
- );
509
- }
510
- if (layoutConfig.justify) {
511
- addClass(
512
- element,
513
- PREFIX_WITH_DASH +
514
- getCachedClassName(layoutType, "justify", layoutConfig.justify),
515
- );
516
- }
517
- }
518
-
519
- // Grid-specific properties
520
- if (layoutConfig.type === "grid" || getLayoutType(element) === "grid") {
521
- if (typeof layoutConfig.columns === "number") {
522
- addClass(
523
- element,
524
- PREFIX_WITH_DASH +
525
- getCachedClassName("grid", "cols", layoutConfig.columns),
526
- );
527
- } else if (layoutConfig.columns === "auto-fill") {
528
- addClass(
529
- element,
530
- PREFIX_WITH_DASH + getCachedClassName("grid", "fill", "auto"),
531
- );
532
- } else if (layoutConfig.columns === "auto-fit") {
533
- addClass(
534
- element,
535
- PREFIX_WITH_DASH + getCachedClassName("grid", "cols", "auto-fit"),
536
- );
537
- }
538
- if (layoutConfig.dense)
539
- addClass(element, `${PREFIX_WITH_DASH}layout--grid-dense`);
540
- if (layoutConfig.autoHeight)
541
- addClass(element, `${PREFIX_WITH_DASH}layout--grid-auto-height`);
542
- }
543
-
544
- // Row-specific properties
545
- if (layoutConfig.type === "row" || getLayoutType(element) === "row") {
546
- if (layoutConfig.wrap === false || layoutConfig.wrap === "nowrap") {
547
- addClass(element, `${PREFIX_WITH_DASH}layout--row-nowrap`);
548
- } else if (layoutConfig.wrap === "reverse") {
549
- addClass(element, `${PREFIX_WITH_DASH}layout--row-wrap-reverse`);
550
- }
551
- if (layoutConfig.mobileStack)
552
- addClass(element, `${PREFIX_WITH_DASH}layout--row-mobile-stack`);
553
- if (layoutConfig.mobileScroll)
554
- addClass(element, `${PREFIX_WITH_DASH}layout--row-mobile-scroll`);
555
- }
556
-
557
- // Custom classes
558
- if (layoutConfig.class) {
559
- layoutConfig.class
560
- .split(" ")
561
- .filter(Boolean)
562
- .forEach((cls) => element.classList.add(cls));
563
- }
564
- }
565
-
566
- /**
567
- * Applies layout item classes based on configuration
568
- * Uses integrated caching for optimal performance
569
- */
570
- function applyLayoutItemClasses(
571
- element: HTMLElement,
572
- itemConfig: LayoutItemConfig,
573
- ): void {
574
- if (!element || !itemConfig) return;
575
-
576
- addClass(element, `${PREFIX_WITH_DASH}layout__item`);
577
-
578
- // Width and responsive classes with caching
579
- if (itemConfig.width && itemConfig.width >= 1 && itemConfig.width <= 12) {
580
- addClass(
581
- element,
582
- PREFIX_WITH_DASH + getCachedClassName("item", "", itemConfig.width),
583
- );
584
- }
585
- if (itemConfig.sm)
586
- addClass(
587
- element,
588
- PREFIX_WITH_DASH + getCachedClassName("item", "sm", itemConfig.sm),
589
- );
590
- if (itemConfig.md)
591
- addClass(
592
- element,
593
- PREFIX_WITH_DASH + getCachedClassName("item", "md", itemConfig.md),
594
- );
595
- if (itemConfig.lg)
596
- addClass(
597
- element,
598
- PREFIX_WITH_DASH + getCachedClassName("item", "lg", itemConfig.lg),
599
- );
600
- if (itemConfig.xl)
601
- addClass(
602
- element,
603
- PREFIX_WITH_DASH + getCachedClassName("item", "xl", itemConfig.xl),
604
- );
605
-
606
- // Grid span classes
607
- if (itemConfig.span)
608
- addClass(
609
- element,
610
- PREFIX_WITH_DASH + getCachedClassName("item", "span", itemConfig.span),
611
- );
612
- if (itemConfig.rowSpan)
613
- addClass(
614
- element,
615
- PREFIX_WITH_DASH +
616
- getCachedClassName("item", "row-span", itemConfig.rowSpan),
617
- );
618
-
619
- // Order and alignment
620
- if (itemConfig.order)
621
- addClass(
622
- element,
623
- PREFIX_WITH_DASH + getCachedClassName("item", "order", itemConfig.order),
624
- );
625
- if (itemConfig.align)
626
- addClass(
627
- element,
628
- PREFIX_WITH_DASH + getCachedClassName("item", "self", itemConfig.align),
629
- );
630
- if (itemConfig.auto)
631
- addClass(element, `${PREFIX_WITH_DASH}layout__item--auto`);
632
- }
633
-
634
- /**
635
- * Gets the layout type from element classes
636
- */
637
- function getLayoutType(element: HTMLElement): string {
638
- return hasClass(element, `${PREFIX_WITH_DASH}layout--stack`)
639
- ? "stack"
640
- : hasClass(element, `${PREFIX_WITH_DASH}layout--row`)
641
- ? "row"
642
- : hasClass(element, `${PREFIX_WITH_DASH}layout--grid`)
643
- ? "grid"
644
- : "";
645
- }
646
-
647
- // ============================================================================
648
- // UNIFIED SCHEMA PROCESSORS
649
- // ============================================================================
650
-
651
- /**
652
- * Processes array-based schema definitions
653
- * Optimized with parameter extraction and integrated configuration
654
- */
655
- function processArraySchema(
656
- schema: SchemaItem[] | any,
657
- parentElement: HTMLElement | null = null,
658
- level: number = 0,
659
- options: LayoutOptions = {},
660
- ): LayoutResult {
661
- level++;
662
- const layout: Record<string, any> = {};
663
- const components: Array<[string, any]> = [];
664
- const fragment = createFragment();
665
- let component = null;
666
-
667
- if (!Array.isArray(schema)) {
668
- return createLayoutResult(layout);
669
- }
670
-
671
- const defaultCreator = (options as any).creator || createElement;
672
-
673
- for (let i = 0; i < schema.length; i++) {
674
- const item = schema[i];
675
- if (!item) continue;
676
-
677
- // Handle nested arrays
678
- if (Array.isArray(item)) {
679
- const container = component || parentElement;
680
- const result = processArraySchema(item, container, level, options);
681
- // Merge nested components array instead of overwriting
682
- if (Array.isArray(result.layout.components)) {
683
- components.push(...result.layout.components);
684
- delete result.layout.components;
685
- }
686
- Object.assign(layout, result.layout);
687
- continue;
688
- }
689
-
690
- // Use optimized parameter extraction
691
- const {
692
- creator,
693
- name,
694
- options: itemOptions,
695
- consumed,
696
- } = extractParameters(schema, i, defaultCreator);
697
-
698
- if (!creator) {
699
- console.warn("Skipping unsupported item type:", item);
700
- continue;
701
- }
702
-
703
- // Default to div for createElement
704
- if (creator === createElement && !("tag" in itemOptions)) {
705
- itemOptions.tag = "div";
706
- }
707
-
708
- // Advance index by consumed items minus 1 (loop increment handles the +1)
709
- i += consumed - 1;
710
-
711
- // Process options with prefix - optimized decision logic
712
- const shouldApplyPrefix =
713
- "prefix" in itemOptions ? itemOptions.prefix : options.prefix !== false;
714
-
715
- // Fast path: process only when needed
716
- const processedOptions =
717
- shouldApplyPrefix || itemOptions.rawClass
718
- ? processClassNames(itemOptions, !shouldApplyPrefix)
719
- : itemOptions; // No copy needed if no processing
720
-
721
- // Add name to options if needed
722
- if (
723
- name &&
724
- !("name" in processedOptions) &&
725
- !(creator === createElement || (creator as any).isElement)
726
- ) {
727
- processedOptions.name = name;
728
- }
729
-
730
- // Create component
731
- component = createComponentInstance(creator, processedOptions);
732
- const element = isComponent(component) ? component.element : component;
733
-
734
- if (level === 1) layout.element = element;
735
- if (name) {
736
- layout[name] = component;
737
- components.push([name, component]);
738
- }
739
-
740
- // Append to DOM
741
- if (component) {
742
- if ("insert" in component && typeof component.insert === "function") {
743
- component.insert(fragment);
744
- } else {
745
- fragment.appendChild(element);
746
- }
747
-
748
- if (parentElement) {
749
- component._container = parentElement;
750
- if (
751
- "onInserted" in component &&
752
- typeof component.onInserted === "function"
753
- ) {
754
- component.onInserted(parentElement);
755
- }
756
- }
757
- }
758
- }
759
-
760
- // Append fragment to parent
761
- if (parentElement && fragment.hasChildNodes()) {
762
- const wrapper = isComponent(parentElement)
763
- ? parentElement.element
764
- : parentElement;
765
- wrapper.appendChild(fragment);
766
- }
767
-
768
- // Release fragment back to pool
769
- releaseFragment(fragment);
770
-
771
- layout.components = components;
772
- return createLayoutResult(layout);
773
- }
774
-
775
- /**
776
- * Processes object-based schema definitions
777
- * Simplified and optimized for better performance
778
- */
779
- function processObjectSchema(
780
- schema: Record<string, any> | string,
781
- parentElement: HTMLElement | null = null,
782
- options: LayoutOptions = {},
783
- ): LayoutResult {
784
- const layout: Record<string, any> = {};
785
- const defaultCreator = options.creator || createElement;
786
-
787
- // Handle root element creation
788
- if ((schema as any).element && !parentElement) {
789
- const elementDef = (schema as any).element;
790
- const createElementFn = elementDef.creator || defaultCreator;
791
-
792
- const elementOptions = elementDef.options || {};
793
- const processedOptions =
794
- options.prefix !== false
795
- ? processClassNames(elementOptions)
796
- : { ...elementOptions };
797
-
798
- const rootComponent = createComponentInstance(
799
- createElementFn,
800
- processedOptions,
801
- );
802
- layout.element = rootComponent;
803
- if (elementDef.name) layout[elementDef.name] = rootComponent;
804
-
805
- // Process children
806
- if (elementDef.children) {
807
- const rootElement = isComponent(rootComponent)
808
- ? rootComponent.element
809
- : rootComponent;
810
- const childResult = processObjectSchema(
811
- elementDef.children,
812
- rootElement,
813
- options,
814
- );
815
- Object.assign(layout, childResult.layout);
816
- }
817
-
818
- return createLayoutResult(layout);
819
- }
820
-
821
- // Process normal schema elements
822
- const fragment = parentElement ? createFragment() : null;
823
-
824
- for (const key in schema as Record<string, any>) {
825
- const def = (schema as Record<string, any>)[key];
826
- if (!def) continue;
827
-
828
- const elementCreator = def.creator || defaultCreator;
829
- const elementOptions = def.options || {};
830
- const shouldApplyPrefix =
831
- "prefix" in elementOptions
832
- ? elementOptions.prefix
833
- : options.prefix !== false;
834
- const processedOptions = shouldApplyPrefix
835
- ? processClassNames(elementOptions)
836
- : { ...elementOptions };
837
-
838
- if (!def.name && key !== "element") {
839
- def.name = key;
840
- }
841
-
842
- const created = createComponentInstance(elementCreator, processedOptions);
843
- layout[key] = created;
844
- if (def.name && def.name !== key) layout[def.name] = created;
845
-
846
- const element = isComponent(created) ? created.element : created;
847
- if (fragment) fragment.appendChild(element);
848
-
849
- // Process children
850
- if (def.children) {
851
- const childResult = processObjectSchema(def.children, element, options);
852
- Object.assign(layout, childResult.layout);
853
- }
854
- }
855
-
856
- // Append to parent
857
- if (parentElement && fragment) {
858
- const parentDom = isComponent(parentElement)
859
- ? parentElement.element
860
- : parentElement;
861
- parentDom.appendChild(fragment);
862
- releaseFragment(fragment);
863
- }
864
-
865
- return createLayoutResult(layout);
866
- }
867
-
868
- // ============================================================================
869
- // LAYOUT RESULT CREATION
870
- // ============================================================================
871
-
872
- /**
873
- * Flattens a nested layout into a simple object with element and component references
874
- */
875
- function flattenLayout(layout: Record<string, any>): Record<string, any> {
876
- const flattened: Record<string, any> = {};
877
-
878
- if (!layout || typeof layout !== "object") return flattened;
879
-
880
- for (const key in layout) {
881
- const value = layout[key];
882
- if (
883
- value &&
884
- typeof value !== "function" &&
885
- (value instanceof HTMLElement ||
886
- (typeof SVGElement !== "undefined" && value instanceof SVGElement) ||
887
- isComponent(value))
888
- ) {
889
- flattened[key] = value;
890
- }
891
- }
892
-
893
- return flattened;
894
- }
895
-
896
- /**
897
- * Creates a layout result object with utility functions
898
- */
899
- function createLayoutResult(layout: Record<string, any>): LayoutResult {
900
- const flattenedComponents = flattenLayout(layout);
901
-
902
- return {
903
- layout,
904
- element: layout.element,
905
- component: flattenedComponents,
906
-
907
- get(name: string): any {
908
- return layout[name] ?? null;
909
- },
910
-
911
- getAll(): Record<string, any> {
912
- return flattenedComponents;
913
- },
914
-
915
- destroy(): void {
916
- // Track destroyed components to avoid double-destroy
917
- const destroyed = new Set<any>();
918
-
919
- // Helper to safely destroy a component
920
- const destroyComponent = (component: any): void => {
921
- if (!component || destroyed.has(component)) return;
922
-
923
- // Skip plain HTML elements and non-objects
924
- if (component instanceof HTMLElement || typeof component !== "object")
925
- return;
926
-
927
- // Check if it's a component with destroy method
928
- if (typeof component.destroy === "function") {
929
- destroyed.add(component);
930
- try {
931
- component.destroy();
932
- } catch (e) {
933
- // Ignore destroy errors - component may already be cleaned up
934
- }
935
- }
936
- };
937
-
938
- // First destroy components from the components array (if present)
939
- // This ensures all named components are destroyed even if nested
940
- if (Array.isArray(layout.components)) {
941
- for (const [name, component] of layout.components) {
942
- destroyComponent(component);
943
- }
944
- }
945
-
946
- // Then iterate over all layout keys for any missed components
947
- for (const key in layout) {
948
- if (key === "element" || key === "components") continue;
949
- destroyComponent(layout[key]);
950
- }
951
-
952
- // Clear the components array
953
- if (Array.isArray(layout.components)) {
954
- layout.components.length = 0;
955
- }
956
-
957
- // Remove root element from DOM
958
- if (layout.element) {
959
- const element = isComponent(layout.element)
960
- ? layout.element.element
961
- : layout.element;
962
- if (element && element.parentNode) {
963
- element.parentNode.removeChild(element);
964
- }
965
- }
966
-
967
- // Clear references to help GC
968
- for (const key in layout) {
969
- delete layout[key];
970
- }
971
- },
972
- };
973
- }
974
-
975
- // ============================================================================
976
- // UNIFIED ENTRY POINT
977
- // ============================================================================
978
-
979
- /**
980
- * Creates a layout from various schema formats
981
- * Unified processor for arrays, objects, JSX, and HTML strings
982
- */
983
- export function createLayout(
984
- schema: any,
985
- parentElement: HTMLElement | null = null,
986
- options: LayoutOptions = {},
987
- ): LayoutResult {
988
- // Handle function schemas
989
- if (typeof schema === "function") {
990
- schema = schema();
991
- }
992
-
993
- // Handle HTML string schemas
994
- if (typeof schema === "string") {
995
- const template = document.createElement("template");
996
- template.innerHTML = schema.trim();
997
- const fragment = template.content;
998
-
999
- if (parentElement && fragment.hasChildNodes()) {
1000
- parentElement.appendChild(fragment);
1001
- }
1002
-
1003
- const layout = { element: fragment.firstElementChild as HTMLElement };
1004
- return createLayoutResult(layout);
1005
- }
1006
-
1007
- // Handle JSX-like schemas (array with function, string, object pattern)
1008
- if (
1009
- Array.isArray(schema) &&
1010
- schema.length >= 3 &&
1011
- typeof schema[0] === "function" &&
1012
- typeof schema[1] === "string" &&
1013
- isObject(schema[2])
1014
- ) {
1015
- return processArraySchema(schema, parentElement, 0, options);
1016
- }
1017
-
1018
- // Route to appropriate processor
1019
- return Array.isArray(schema)
1020
- ? processArraySchema(schema, parentElement, 0, options)
1021
- : processObjectSchema(schema, parentElement, options);
1022
- }
1023
-
1024
- // ============================================================================
1025
- // EXPORTS
1026
- // ============================================================================
1027
-
1028
- // Clear functions for the unified system
1029
- export function clearClassCache(): void {
1030
- classCache.clear();
1031
- }
1032
-
1033
- export function clearFragmentPool(): void {
1034
- fragmentPool.clear();
1035
- }
1036
-
1037
- export {
1038
- processClassNames,
1039
- isComponent,
1040
- flattenLayout,
1041
- applyLayoutClasses,
1042
- applyLayoutItemClasses,
1043
- createLayoutResult,
1044
- };