mtrl-addons 0.1.2 → 0.2.2
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.
- package/AI.md +28 -230
- package/CLAUDE.md +882 -0
- package/build.js +253 -24
- package/package.json +14 -4
- package/scripts/debug/vlist-selection.ts +121 -0
- package/src/components/index.ts +5 -41
- package/src/components/{list → vlist}/config.ts +66 -95
- package/src/components/vlist/constants.ts +23 -0
- package/src/components/vlist/features/api.ts +626 -0
- package/src/components/vlist/features/index.ts +10 -0
- package/src/components/vlist/features/selection.ts +436 -0
- package/src/components/vlist/features/viewport.ts +59 -0
- package/src/components/vlist/index.ts +17 -0
- package/src/components/{list → vlist}/types.ts +242 -32
- package/src/components/vlist/vlist.ts +92 -0
- package/src/core/compose/features/gestures/index.ts +227 -0
- package/src/core/compose/features/gestures/longpress.ts +383 -0
- package/src/core/compose/features/gestures/pan.ts +424 -0
- package/src/core/compose/features/gestures/pinch.ts +475 -0
- package/src/core/compose/features/gestures/rotate.ts +485 -0
- package/src/core/compose/features/gestures/swipe.ts +492 -0
- package/src/core/compose/features/gestures/tap.ts +334 -0
- package/src/core/compose/features/index.ts +2 -38
- package/src/core/compose/index.ts +13 -29
- package/src/core/gestures/index.ts +31 -0
- package/src/core/gestures/longpress.ts +68 -0
- package/src/core/gestures/manager.ts +418 -0
- package/src/core/gestures/pan.ts +48 -0
- package/src/core/gestures/pinch.ts +58 -0
- package/src/core/gestures/rotate.ts +58 -0
- package/src/core/gestures/swipe.ts +66 -0
- package/src/core/gestures/tap.ts +45 -0
- package/src/core/gestures/types.ts +387 -0
- package/src/core/gestures/utils.ts +128 -0
- package/src/core/index.ts +27 -151
- package/src/core/layout/schema.ts +153 -72
- package/src/core/layout/types.ts +5 -2
- package/src/core/viewport/constants.ts +145 -0
- package/src/core/viewport/features/base.ts +73 -0
- package/src/core/viewport/features/collection.ts +1182 -0
- package/src/core/viewport/features/events.ts +130 -0
- package/src/core/viewport/features/index.ts +20 -0
- package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +31 -34
- package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
- package/src/core/viewport/features/momentum.ts +269 -0
- package/src/core/viewport/features/placeholders.ts +335 -0
- package/src/core/viewport/features/rendering.ts +962 -0
- package/src/core/viewport/features/scrollbar.ts +434 -0
- package/src/core/viewport/features/scrolling.ts +634 -0
- package/src/core/viewport/features/utils.ts +94 -0
- package/src/core/viewport/features/virtual.ts +525 -0
- package/src/core/viewport/index.ts +31 -0
- package/src/core/viewport/types.ts +133 -0
- package/src/core/viewport/utils/speed-tracker.ts +79 -0
- package/src/core/viewport/viewport.ts +265 -0
- package/src/index.ts +0 -7
- package/src/styles/components/_vlist.scss +352 -0
- package/src/styles/index.scss +1 -1
- package/test/components/vlist-selection.test.ts +240 -0
- package/test/components/vlist.test.ts +63 -0
- package/test/core/collection/adapter.test.ts +161 -0
- package/bun.lock +0 -792
- package/src/components/list/api.ts +0 -314
- package/src/components/list/constants.ts +0 -56
- package/src/components/list/features/api.ts +0 -428
- package/src/components/list/features/index.ts +0 -31
- package/src/components/list/features/list-manager.ts +0 -502
- package/src/components/list/index.ts +0 -39
- package/src/components/list/list.ts +0 -234
- package/src/core/collection/base-collection.ts +0 -100
- package/src/core/collection/collection-composer.ts +0 -178
- package/src/core/collection/collection.ts +0 -745
- package/src/core/collection/constants.ts +0 -172
- package/src/core/collection/events.ts +0 -428
- package/src/core/collection/features/api/loading.ts +0 -279
- package/src/core/collection/features/operations/data-operations.ts +0 -147
- package/src/core/collection/index.ts +0 -104
- package/src/core/collection/state.ts +0 -497
- package/src/core/collection/types.ts +0 -404
- package/src/core/compose/features/collection.ts +0 -119
- package/src/core/compose/features/selection.ts +0 -213
- package/src/core/compose/features/styling.ts +0 -108
- package/src/core/list-manager/api.ts +0 -599
- package/src/core/list-manager/config.ts +0 -593
- package/src/core/list-manager/constants.ts +0 -268
- package/src/core/list-manager/features/api.ts +0 -58
- package/src/core/list-manager/features/collection/collection.ts +0 -705
- package/src/core/list-manager/features/collection/index.ts +0 -17
- package/src/core/list-manager/features/viewport/constants.ts +0 -42
- package/src/core/list-manager/features/viewport/index.ts +0 -16
- package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
- package/src/core/list-manager/features/viewport/rendering.ts +0 -575
- package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
- package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
- package/src/core/list-manager/features/viewport/template.ts +0 -220
- package/src/core/list-manager/features/viewport/viewport.ts +0 -654
- package/src/core/list-manager/features/viewport/virtual.ts +0 -309
- package/src/core/list-manager/index.ts +0 -279
- package/src/core/list-manager/list-manager.ts +0 -206
- package/src/core/list-manager/types.ts +0 -439
- package/src/core/list-manager/utils/calculations.ts +0 -290
- package/src/core/list-manager/utils/range-calculator.ts +0 -349
- package/src/core/list-manager/utils/speed-tracker.ts +0 -273
- package/src/styles/components/_list.scss +0 -244
- package/src/types/mtrl.d.ts +0 -6
- package/test/components/list.test.ts +0 -256
- package/test/core/collection/failed-ranges.test.ts +0 -270
- package/test/core/compose/features.test.ts +0 -183
- package/test/core/list-manager/features/collection.test.ts +0 -704
- package/test/core/list-manager/features/viewport.test.ts +0 -698
- package/test/core/list-manager/list-manager.test.ts +0 -593
- package/test/core/list-manager/utils/calculations.test.ts +0 -433
- package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
- package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
- package/tsconfig.build.json +0 -23
- /package/src/components/{list → vlist}/features.ts +0 -0
- /package/src/core/{compose → viewport}/features/performance.ts +0 -0
|
@@ -129,7 +129,7 @@ const PREFIX_WITH_DASH = `${PREFIX}-`;
|
|
|
129
129
|
function getCachedClassName(
|
|
130
130
|
type: string,
|
|
131
131
|
property: string,
|
|
132
|
-
value: string | number
|
|
132
|
+
value: string | number,
|
|
133
133
|
): string {
|
|
134
134
|
const key = `${type}-${property}-${value}`;
|
|
135
135
|
if (!classCache.has(key)) {
|
|
@@ -138,7 +138,7 @@ function getCachedClassName(
|
|
|
138
138
|
key,
|
|
139
139
|
property === ""
|
|
140
140
|
? `layout__item--${value}`
|
|
141
|
-
: `layout__item--${property}-${value}
|
|
141
|
+
: `layout__item--${property}-${value}`,
|
|
142
142
|
);
|
|
143
143
|
} else {
|
|
144
144
|
// For layout classes, align uses layout--{type}-{value}
|
|
@@ -180,45 +180,80 @@ function releaseFragment(fragment: DocumentFragment): void {
|
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
182
|
* Optimized class processing with minimal string operations
|
|
183
|
-
* Handles arrays, strings,
|
|
183
|
+
* Handles arrays, strings, className aliases, and rawClass efficiently
|
|
184
184
|
*/
|
|
185
185
|
function processClassNames(
|
|
186
186
|
options: Record<string, any>,
|
|
187
|
-
skipPrefix = false
|
|
187
|
+
skipPrefix = false,
|
|
188
188
|
): Record<string, any> {
|
|
189
|
-
if (!options
|
|
189
|
+
if (!options) return options;
|
|
190
190
|
|
|
191
|
-
const
|
|
192
|
-
|
|
191
|
+
const hasRawClass = options.rawClass;
|
|
192
|
+
const hasRegularClass = options.class || options.className;
|
|
193
193
|
|
|
194
|
-
|
|
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;
|
|
195
201
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// Handle arrays by joining first
|
|
200
|
-
if (Array.isArray(processed.class)) {
|
|
201
|
-
classString += processed.class.join(" ");
|
|
202
|
+
// Direct assignment for simple string
|
|
203
|
+
if (typeof hasRawClass === "string") {
|
|
204
|
+
processed.class = hasRawClass;
|
|
202
205
|
} else {
|
|
203
|
-
|
|
206
|
+
// Handle array case
|
|
207
|
+
processed.class = hasRawClass.join(" ");
|
|
204
208
|
}
|
|
209
|
+
return processed;
|
|
205
210
|
}
|
|
206
|
-
|
|
207
|
-
|
|
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
|
+
}
|
|
208
238
|
}
|
|
209
239
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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;
|
|
219
251
|
}
|
|
220
252
|
|
|
253
|
+
// Clean up in one operation
|
|
221
254
|
delete processed.className;
|
|
255
|
+
delete processed.rawClass;
|
|
256
|
+
|
|
222
257
|
return processed;
|
|
223
258
|
}
|
|
224
259
|
|
|
@@ -236,7 +271,7 @@ interface ExtractedParameters {
|
|
|
236
271
|
function extractParameters(
|
|
237
272
|
schema: SchemaItem[],
|
|
238
273
|
startIndex: number,
|
|
239
|
-
defaultCreator: Function
|
|
274
|
+
defaultCreator: Function,
|
|
240
275
|
): ExtractedParameters {
|
|
241
276
|
const items = schema.slice(startIndex, startIndex + 3);
|
|
242
277
|
let creator, name, options;
|
|
@@ -272,7 +307,7 @@ function extractParameters(
|
|
|
272
307
|
return {
|
|
273
308
|
creator: creator || defaultCreator,
|
|
274
309
|
name,
|
|
275
|
-
options: options || {},
|
|
310
|
+
options: (options || {}) as Record<string, any>,
|
|
276
311
|
consumed,
|
|
277
312
|
};
|
|
278
313
|
}
|
|
@@ -344,7 +379,7 @@ function hasClass(element: HTMLElement, className: string): boolean {
|
|
|
344
379
|
*/
|
|
345
380
|
function createComponentInstance(
|
|
346
381
|
Component: any,
|
|
347
|
-
options: Record<string, any> = {}
|
|
382
|
+
options: Record<string, any> = {},
|
|
348
383
|
): any {
|
|
349
384
|
try {
|
|
350
385
|
// Destructure special configs in one operation
|
|
@@ -418,7 +453,7 @@ function createComponentInstance(
|
|
|
418
453
|
}
|
|
419
454
|
} else {
|
|
420
455
|
for (const [eventName, handler] of Object.entries(
|
|
421
|
-
finalEventsConfig
|
|
456
|
+
finalEventsConfig,
|
|
422
457
|
)) {
|
|
423
458
|
if (typeof handler === "function") {
|
|
424
459
|
element.addEventListener(eventName, handler);
|
|
@@ -446,7 +481,7 @@ function createComponentInstance(
|
|
|
446
481
|
*/
|
|
447
482
|
function applyLayoutClasses(
|
|
448
483
|
element: HTMLElement,
|
|
449
|
-
layoutConfig: LayoutConfig
|
|
484
|
+
layoutConfig: LayoutConfig,
|
|
450
485
|
): void {
|
|
451
486
|
if (!element || !layoutConfig) return;
|
|
452
487
|
|
|
@@ -462,21 +497,21 @@ function applyLayoutClasses(
|
|
|
462
497
|
addClass(
|
|
463
498
|
element,
|
|
464
499
|
PREFIX_WITH_DASH +
|
|
465
|
-
getCachedClassName(layoutType, "gap", layoutConfig.gap)
|
|
500
|
+
getCachedClassName(layoutType, "gap", layoutConfig.gap),
|
|
466
501
|
);
|
|
467
502
|
}
|
|
468
503
|
if (layoutConfig.align) {
|
|
469
504
|
addClass(
|
|
470
505
|
element,
|
|
471
506
|
PREFIX_WITH_DASH +
|
|
472
|
-
getCachedClassName(layoutType, "align", layoutConfig.align)
|
|
507
|
+
getCachedClassName(layoutType, "align", layoutConfig.align),
|
|
473
508
|
);
|
|
474
509
|
}
|
|
475
510
|
if (layoutConfig.justify) {
|
|
476
511
|
addClass(
|
|
477
512
|
element,
|
|
478
513
|
PREFIX_WITH_DASH +
|
|
479
|
-
getCachedClassName(layoutType, "justify", layoutConfig.justify)
|
|
514
|
+
getCachedClassName(layoutType, "justify", layoutConfig.justify),
|
|
480
515
|
);
|
|
481
516
|
}
|
|
482
517
|
}
|
|
@@ -487,17 +522,17 @@ function applyLayoutClasses(
|
|
|
487
522
|
addClass(
|
|
488
523
|
element,
|
|
489
524
|
PREFIX_WITH_DASH +
|
|
490
|
-
getCachedClassName("grid", "cols", layoutConfig.columns)
|
|
525
|
+
getCachedClassName("grid", "cols", layoutConfig.columns),
|
|
491
526
|
);
|
|
492
527
|
} else if (layoutConfig.columns === "auto-fill") {
|
|
493
528
|
addClass(
|
|
494
529
|
element,
|
|
495
|
-
PREFIX_WITH_DASH + getCachedClassName("grid", "fill", "auto")
|
|
530
|
+
PREFIX_WITH_DASH + getCachedClassName("grid", "fill", "auto"),
|
|
496
531
|
);
|
|
497
532
|
} else if (layoutConfig.columns === "auto-fit") {
|
|
498
533
|
addClass(
|
|
499
534
|
element,
|
|
500
|
-
PREFIX_WITH_DASH + getCachedClassName("grid", "cols", "auto-fit")
|
|
535
|
+
PREFIX_WITH_DASH + getCachedClassName("grid", "cols", "auto-fit"),
|
|
501
536
|
);
|
|
502
537
|
}
|
|
503
538
|
if (layoutConfig.dense)
|
|
@@ -534,7 +569,7 @@ function applyLayoutClasses(
|
|
|
534
569
|
*/
|
|
535
570
|
function applyLayoutItemClasses(
|
|
536
571
|
element: HTMLElement,
|
|
537
|
-
itemConfig: LayoutItemConfig
|
|
572
|
+
itemConfig: LayoutItemConfig,
|
|
538
573
|
): void {
|
|
539
574
|
if (!element || !itemConfig) return;
|
|
540
575
|
|
|
@@ -544,53 +579,53 @@ function applyLayoutItemClasses(
|
|
|
544
579
|
if (itemConfig.width && itemConfig.width >= 1 && itemConfig.width <= 12) {
|
|
545
580
|
addClass(
|
|
546
581
|
element,
|
|
547
|
-
PREFIX_WITH_DASH + getCachedClassName("item", "", itemConfig.width)
|
|
582
|
+
PREFIX_WITH_DASH + getCachedClassName("item", "", itemConfig.width),
|
|
548
583
|
);
|
|
549
584
|
}
|
|
550
585
|
if (itemConfig.sm)
|
|
551
586
|
addClass(
|
|
552
587
|
element,
|
|
553
|
-
PREFIX_WITH_DASH + getCachedClassName("item", "sm", itemConfig.sm)
|
|
588
|
+
PREFIX_WITH_DASH + getCachedClassName("item", "sm", itemConfig.sm),
|
|
554
589
|
);
|
|
555
590
|
if (itemConfig.md)
|
|
556
591
|
addClass(
|
|
557
592
|
element,
|
|
558
|
-
PREFIX_WITH_DASH + getCachedClassName("item", "md", itemConfig.md)
|
|
593
|
+
PREFIX_WITH_DASH + getCachedClassName("item", "md", itemConfig.md),
|
|
559
594
|
);
|
|
560
595
|
if (itemConfig.lg)
|
|
561
596
|
addClass(
|
|
562
597
|
element,
|
|
563
|
-
PREFIX_WITH_DASH + getCachedClassName("item", "lg", itemConfig.lg)
|
|
598
|
+
PREFIX_WITH_DASH + getCachedClassName("item", "lg", itemConfig.lg),
|
|
564
599
|
);
|
|
565
600
|
if (itemConfig.xl)
|
|
566
601
|
addClass(
|
|
567
602
|
element,
|
|
568
|
-
PREFIX_WITH_DASH + getCachedClassName("item", "xl", itemConfig.xl)
|
|
603
|
+
PREFIX_WITH_DASH + getCachedClassName("item", "xl", itemConfig.xl),
|
|
569
604
|
);
|
|
570
605
|
|
|
571
606
|
// Grid span classes
|
|
572
607
|
if (itemConfig.span)
|
|
573
608
|
addClass(
|
|
574
609
|
element,
|
|
575
|
-
PREFIX_WITH_DASH + getCachedClassName("item", "span", itemConfig.span)
|
|
610
|
+
PREFIX_WITH_DASH + getCachedClassName("item", "span", itemConfig.span),
|
|
576
611
|
);
|
|
577
612
|
if (itemConfig.rowSpan)
|
|
578
613
|
addClass(
|
|
579
614
|
element,
|
|
580
615
|
PREFIX_WITH_DASH +
|
|
581
|
-
getCachedClassName("item", "row-span", itemConfig.rowSpan)
|
|
616
|
+
getCachedClassName("item", "row-span", itemConfig.rowSpan),
|
|
582
617
|
);
|
|
583
618
|
|
|
584
619
|
// Order and alignment
|
|
585
620
|
if (itemConfig.order)
|
|
586
621
|
addClass(
|
|
587
622
|
element,
|
|
588
|
-
PREFIX_WITH_DASH + getCachedClassName("item", "order", itemConfig.order)
|
|
623
|
+
PREFIX_WITH_DASH + getCachedClassName("item", "order", itemConfig.order),
|
|
589
624
|
);
|
|
590
625
|
if (itemConfig.align)
|
|
591
626
|
addClass(
|
|
592
627
|
element,
|
|
593
|
-
PREFIX_WITH_DASH + getCachedClassName("item", "self", itemConfig.align)
|
|
628
|
+
PREFIX_WITH_DASH + getCachedClassName("item", "self", itemConfig.align),
|
|
594
629
|
);
|
|
595
630
|
if (itemConfig.auto)
|
|
596
631
|
addClass(element, `${PREFIX_WITH_DASH}layout__item--auto`);
|
|
@@ -603,10 +638,10 @@ function getLayoutType(element: HTMLElement): string {
|
|
|
603
638
|
return hasClass(element, `${PREFIX_WITH_DASH}layout--stack`)
|
|
604
639
|
? "stack"
|
|
605
640
|
: hasClass(element, `${PREFIX_WITH_DASH}layout--row`)
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
641
|
+
? "row"
|
|
642
|
+
: hasClass(element, `${PREFIX_WITH_DASH}layout--grid`)
|
|
643
|
+
? "grid"
|
|
644
|
+
: "";
|
|
610
645
|
}
|
|
611
646
|
|
|
612
647
|
// ============================================================================
|
|
@@ -618,10 +653,10 @@ function getLayoutType(element: HTMLElement): string {
|
|
|
618
653
|
* Optimized with parameter extraction and integrated configuration
|
|
619
654
|
*/
|
|
620
655
|
function processArraySchema(
|
|
621
|
-
schema: SchemaItem[],
|
|
656
|
+
schema: SchemaItem[] | any,
|
|
622
657
|
parentElement: HTMLElement | null = null,
|
|
623
658
|
level: number = 0,
|
|
624
|
-
options: LayoutOptions = {}
|
|
659
|
+
options: LayoutOptions = {},
|
|
625
660
|
): LayoutResult {
|
|
626
661
|
level++;
|
|
627
662
|
const layout: Record<string, any> = {};
|
|
@@ -633,7 +668,7 @@ function processArraySchema(
|
|
|
633
668
|
return createLayoutResult(layout);
|
|
634
669
|
}
|
|
635
670
|
|
|
636
|
-
const defaultCreator = options.creator || createElement;
|
|
671
|
+
const defaultCreator = (options as any).creator || createElement;
|
|
637
672
|
|
|
638
673
|
for (let i = 0; i < schema.length; i++) {
|
|
639
674
|
const item = schema[i];
|
|
@@ -643,6 +678,11 @@ function processArraySchema(
|
|
|
643
678
|
if (Array.isArray(item)) {
|
|
644
679
|
const container = component || parentElement;
|
|
645
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
|
+
}
|
|
646
686
|
Object.assign(layout, result.layout);
|
|
647
687
|
continue;
|
|
648
688
|
}
|
|
@@ -668,12 +708,15 @@ function processArraySchema(
|
|
|
668
708
|
// Advance index by consumed items minus 1 (loop increment handles the +1)
|
|
669
709
|
i += consumed - 1;
|
|
670
710
|
|
|
671
|
-
// Process options with prefix
|
|
711
|
+
// Process options with prefix - optimized decision logic
|
|
672
712
|
const shouldApplyPrefix =
|
|
673
713
|
"prefix" in itemOptions ? itemOptions.prefix : options.prefix !== false;
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
|
677
720
|
|
|
678
721
|
// Add name to options if needed
|
|
679
722
|
if (
|
|
@@ -734,16 +777,16 @@ function processArraySchema(
|
|
|
734
777
|
* Simplified and optimized for better performance
|
|
735
778
|
*/
|
|
736
779
|
function processObjectSchema(
|
|
737
|
-
schema: Record<string, any
|
|
780
|
+
schema: Record<string, any> | string,
|
|
738
781
|
parentElement: HTMLElement | null = null,
|
|
739
|
-
options: LayoutOptions = {}
|
|
782
|
+
options: LayoutOptions = {},
|
|
740
783
|
): LayoutResult {
|
|
741
784
|
const layout: Record<string, any> = {};
|
|
742
785
|
const defaultCreator = options.creator || createElement;
|
|
743
786
|
|
|
744
787
|
// Handle root element creation
|
|
745
|
-
if (schema.element && !parentElement) {
|
|
746
|
-
const elementDef = schema.element;
|
|
788
|
+
if ((schema as any).element && !parentElement) {
|
|
789
|
+
const elementDef = (schema as any).element;
|
|
747
790
|
const createElementFn = elementDef.creator || defaultCreator;
|
|
748
791
|
|
|
749
792
|
const elementOptions = elementDef.options || {};
|
|
@@ -754,7 +797,7 @@ function processObjectSchema(
|
|
|
754
797
|
|
|
755
798
|
const rootComponent = createComponentInstance(
|
|
756
799
|
createElementFn,
|
|
757
|
-
processedOptions
|
|
800
|
+
processedOptions,
|
|
758
801
|
);
|
|
759
802
|
layout.element = rootComponent;
|
|
760
803
|
if (elementDef.name) layout[elementDef.name] = rootComponent;
|
|
@@ -767,7 +810,7 @@ function processObjectSchema(
|
|
|
767
810
|
const childResult = processObjectSchema(
|
|
768
811
|
elementDef.children,
|
|
769
812
|
rootElement,
|
|
770
|
-
options
|
|
813
|
+
options,
|
|
771
814
|
);
|
|
772
815
|
Object.assign(layout, childResult.layout);
|
|
773
816
|
}
|
|
@@ -778,8 +821,8 @@ function processObjectSchema(
|
|
|
778
821
|
// Process normal schema elements
|
|
779
822
|
const fragment = parentElement ? createFragment() : null;
|
|
780
823
|
|
|
781
|
-
for (const key in schema) {
|
|
782
|
-
const def = schema[key];
|
|
824
|
+
for (const key in schema as Record<string, any>) {
|
|
825
|
+
const def = (schema as Record<string, any>)[key];
|
|
783
826
|
if (!def) continue;
|
|
784
827
|
|
|
785
828
|
const elementCreator = def.creator || defaultCreator;
|
|
@@ -870,14 +913,47 @@ function createLayoutResult(layout: Record<string, any>): LayoutResult {
|
|
|
870
913
|
},
|
|
871
914
|
|
|
872
915
|
destroy(): void {
|
|
873
|
-
//
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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);
|
|
878
943
|
}
|
|
879
944
|
}
|
|
880
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
|
+
|
|
881
957
|
// Remove root element from DOM
|
|
882
958
|
if (layout.element) {
|
|
883
959
|
const element = isComponent(layout.element)
|
|
@@ -887,6 +963,11 @@ function createLayoutResult(layout: Record<string, any>): LayoutResult {
|
|
|
887
963
|
element.parentNode.removeChild(element);
|
|
888
964
|
}
|
|
889
965
|
}
|
|
966
|
+
|
|
967
|
+
// Clear references to help GC
|
|
968
|
+
for (const key in layout) {
|
|
969
|
+
delete layout[key];
|
|
970
|
+
}
|
|
890
971
|
},
|
|
891
972
|
};
|
|
892
973
|
}
|
|
@@ -902,7 +983,7 @@ function createLayoutResult(layout: Record<string, any>): LayoutResult {
|
|
|
902
983
|
export function createLayout(
|
|
903
984
|
schema: any,
|
|
904
985
|
parentElement: HTMLElement | null = null,
|
|
905
|
-
options: LayoutOptions = {}
|
|
986
|
+
options: LayoutOptions = {},
|
|
906
987
|
): LayoutResult {
|
|
907
988
|
// Handle function schemas
|
|
908
989
|
if (typeof schema === "function") {
|
package/src/core/layout/types.ts
CHANGED
|
@@ -37,12 +37,15 @@ export interface ElementOptions extends Record<string, any> {
|
|
|
37
37
|
/** Layout item configuration */
|
|
38
38
|
layoutItem?: LayoutItemConfig;
|
|
39
39
|
|
|
40
|
-
/** CSS classes to apply */
|
|
40
|
+
/** CSS classes to apply (with automatic mtrl- prefix) */
|
|
41
41
|
class?: string;
|
|
42
42
|
|
|
43
43
|
/** Additional CSS classes (alias for class) */
|
|
44
44
|
className?: string;
|
|
45
45
|
|
|
46
|
+
/** CSS classes to apply without prefix */
|
|
47
|
+
rawClass?: string | string[];
|
|
48
|
+
|
|
46
49
|
/** HTML tag name for createElement */
|
|
47
50
|
tag?: string;
|
|
48
51
|
|
|
@@ -89,4 +92,4 @@ export interface Schema {
|
|
|
89
92
|
|
|
90
93
|
/** Additional elements */
|
|
91
94
|
[key: string]: ElementDefinition | undefined;
|
|
92
|
-
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// src/core/viewport/constants.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Viewport Constants
|
|
5
|
+
* Centralized constants for all viewport functionality
|
|
6
|
+
* Consolidated from viewport, viewport/features, and list-manager constants
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const VIEWPORT_CONSTANTS = {
|
|
10
|
+
// Virtual scrolling defaults
|
|
11
|
+
VIRTUAL_SCROLL: {
|
|
12
|
+
DEFAULT_ITEM_SIZE: 48,
|
|
13
|
+
OVERSCAN_BUFFER: 2,
|
|
14
|
+
SCROLL_SENSITIVITY: 0.7,
|
|
15
|
+
MAX_VIRTUAL_SIZE: 100 * 1000 * 1000, // 100M pixels - modern browsers can handle this
|
|
16
|
+
AUTO_DETECT_ITEM_SIZE: true, // Enable automatic item size detection
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
// Scrolling settings
|
|
20
|
+
SCROLLING: {
|
|
21
|
+
OVERSCAN: 1, // From features/constants
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// Rendering settings
|
|
25
|
+
RENDERING: {
|
|
26
|
+
// Element recycling
|
|
27
|
+
DEFAULT_MAX_POOL_SIZE: 100,
|
|
28
|
+
CLASSES: {
|
|
29
|
+
ITEM: "viewport-item",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
// Loading settings
|
|
34
|
+
LOADING: {
|
|
35
|
+
CANCEL_THRESHOLD: 25, // px/ms - velocity above which loads cancel
|
|
36
|
+
MAX_CONCURRENT_REQUESTS: 1, // Parallel requests allowed
|
|
37
|
+
DEFAULT_RANGE_SIZE: 20, // Items per request
|
|
38
|
+
DEBOUNCE_LOADING: 150, // Debounce delay (ms)
|
|
39
|
+
MIN_RANGE_SIZE: 10, // Minimum items per load
|
|
40
|
+
MAX_RANGE_SIZE: 100, // Maximum items per load
|
|
41
|
+
REQUEST_TIMEOUT: 5000, // Request timeout (ms)
|
|
42
|
+
RETRY_ATTEMPTS: 2, // Failed request retries
|
|
43
|
+
RETRY_DELAY: 1000, // Delay between retries (ms)
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Request queue configuration (from features/constants)
|
|
47
|
+
REQUEST_QUEUE: {
|
|
48
|
+
ENABLED: true, // Enable request queuing
|
|
49
|
+
MAX_QUEUE_SIZE: 1, // Max queued requests
|
|
50
|
+
MAX_ACTIVE_REQUESTS: 2, // Max concurrent active requests
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// Placeholder settings
|
|
54
|
+
PLACEHOLDER: {
|
|
55
|
+
MASK_CHARACTER: "X", // Updated from list-manager
|
|
56
|
+
CLASS: "viewport-item--placeholder",
|
|
57
|
+
MAX_SAMPLE_SIZE: 20,
|
|
58
|
+
PLACEHOLDER_FLAG: "_placeholder",
|
|
59
|
+
RANDOM_LENGTH_VARIANCE: true,
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Speed tracking (from list-manager)
|
|
63
|
+
SPEED_TRACKING: {
|
|
64
|
+
// Velocity calculation
|
|
65
|
+
DECELERATION_FACTOR: 0.85, // velocity decay per frame
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// Momentum settings
|
|
69
|
+
MOMENTUM: {
|
|
70
|
+
ENABLED: false, // Enable momentum by default
|
|
71
|
+
DECELERATION_FACTOR: 0.85, // How quickly velocity decreases per frame
|
|
72
|
+
MIN_VELOCITY: 0.1, // Minimum velocity before stopping (px/ms)
|
|
73
|
+
MIN_DURATION: 300, // Maximum gesture duration to trigger momentum (ms)
|
|
74
|
+
MIN_VELOCITY_THRESHOLD: 0.5, // Minimum velocity to trigger momentum (px/ms)
|
|
75
|
+
FRAME_TIME: 16, // Assumed frame time for calculations (ms)
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// Initial load configuration (from list-manager)
|
|
79
|
+
INITIAL_LOAD: {
|
|
80
|
+
STRATEGY: "placeholders", // "placeholders" | "direct" | "progressive"
|
|
81
|
+
VIEWPORT_MULTIPLIER: 1.5, // load 1.5x viewport capacity
|
|
82
|
+
MIN_ITEMS: 10,
|
|
83
|
+
MAX_ITEMS: 100,
|
|
84
|
+
PLACEHOLDER_COUNT: 20, // default placeholder count
|
|
85
|
+
SHOW_LOADING_STATE: true,
|
|
86
|
+
LOADING_DELAY: 100, // ms - delay before showing loading state
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// Selection settings
|
|
90
|
+
SELECTION: {
|
|
91
|
+
SELECTED_CLASS: "viewport-item--selected",
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// Scrollbar settings (from list-manager)
|
|
95
|
+
SCROLLBAR: {
|
|
96
|
+
// CSS classes
|
|
97
|
+
CLASSES: {
|
|
98
|
+
SCROLLBAR: "viewport__scrollbar",
|
|
99
|
+
SCROLLBAR_TRACK: "viewport__scrollbar-track",
|
|
100
|
+
SCROLLBAR_THUMB: "viewport__scrollbar-thumb",
|
|
101
|
+
SCROLLBAR_VISIBLE: "viewport__scrollbar--visible",
|
|
102
|
+
SCROLLBAR_DRAGGING: "viewport__scrollbar--dragging",
|
|
103
|
+
SCROLLBAR_THUMB_DRAGGING: "viewport__scrollbar-thumb--dragging",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// Orientation (from list-manager)
|
|
108
|
+
ORIENTATION: {
|
|
109
|
+
DEFAULT_ORIENTATION: "vertical",
|
|
110
|
+
DEFAULT_CROSS_AXIS_ALIGNMENT: "stretch",
|
|
111
|
+
REVERSE_DIRECTION: false,
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
PAGINATION: {
|
|
115
|
+
DEFAULT_STRATEGY: "offset" as "offset" | "page" | "cursor",
|
|
116
|
+
DEFAULT_LIMIT: 20,
|
|
117
|
+
STRATEGIES: {
|
|
118
|
+
PAGE: "page" as const,
|
|
119
|
+
OFFSET: "offset" as const,
|
|
120
|
+
CURSOR: "cursor" as const,
|
|
121
|
+
},
|
|
122
|
+
CURSOR_CLEANUP_INTERVAL: 60000, // Clean up old cursors every minute
|
|
123
|
+
MAX_CURSOR_MAP_SIZE: 1000, // Maximum number of cursors to keep in memory
|
|
124
|
+
// Cursor-specific virtual sizing
|
|
125
|
+
CURSOR_SCROLL_MARGIN_MULTIPLIER: 3, // Multiply rangeSize by this for scroll margin
|
|
126
|
+
CURSOR_MIN_VIRTUAL_SIZE_MULTIPLIER: 3, // Minimum virtual size as multiplier of rangeSize
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Type for overriding constants at runtime
|
|
132
|
+
*/
|
|
133
|
+
export type ViewportConstants = typeof VIEWPORT_CONSTANTS;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Helper function to merge user constants with defaults
|
|
137
|
+
*/
|
|
138
|
+
export function mergeConstants(
|
|
139
|
+
userConstants: Partial<ViewportConstants> = {},
|
|
140
|
+
): ViewportConstants {
|
|
141
|
+
return {
|
|
142
|
+
...VIEWPORT_CONSTANTS,
|
|
143
|
+
...userConstants,
|
|
144
|
+
};
|
|
145
|
+
}
|