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
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Types
|
|
3
|
-
*
|
|
4
|
-
* Extends collection types with list-specific functionality
|
|
2
|
+
* VList Types - Virtual List with direct viewport integration
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
|
-
import type {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
import type { BaseComponent, ElementComponent } from "mtrl";
|
|
6
|
+
|
|
7
|
+
/** Options for removeItemById */
|
|
8
|
+
export interface RemoveItemOptions {
|
|
9
|
+
/** Track as pending removal to filter from future fetches (default: true) */
|
|
10
|
+
trackPending?: boolean;
|
|
11
|
+
/** Timeout in ms to clear pending removal (default: 5000) */
|
|
12
|
+
pendingTimeout?: number;
|
|
13
|
+
}
|
|
14
|
+
// Collection types are not exposed by mtrl; define minimal interfaces locally
|
|
15
|
+
export interface CollectionItem {
|
|
16
|
+
id: string | number;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CollectionConfig<T = any> {
|
|
21
|
+
adapter?: {
|
|
22
|
+
read(params?: any): Promise<{ items: T[]; meta?: any; error?: any }>;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Collection<T = any> {
|
|
27
|
+
loadMissingRanges?: (
|
|
28
|
+
range: { start: number; end: number },
|
|
29
|
+
reason?: string,
|
|
30
|
+
) => Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
import type { ViewportComponent } from "../../core/viewport/types";
|
|
18
33
|
|
|
19
34
|
/**
|
|
20
35
|
* List item interface - extends collection item
|
|
21
36
|
*/
|
|
22
|
-
export interface ListItem extends CollectionItem {
|
|
23
|
-
id: string;
|
|
24
|
-
[key: string]: any;
|
|
25
|
-
}
|
|
37
|
+
export interface ListItem extends CollectionItem {}
|
|
26
38
|
|
|
27
39
|
/**
|
|
28
40
|
* List adapter interface - extends collection adapter
|
|
@@ -71,14 +83,14 @@ export interface ListAdapterResponse<T extends ListItem = ListItem> {
|
|
|
71
83
|
/**
|
|
72
84
|
* List template definition
|
|
73
85
|
*/
|
|
74
|
-
export type ListTemplate =
|
|
86
|
+
export type ListTemplate = (item: any, index: number) => string | HTMLElement;
|
|
75
87
|
|
|
76
88
|
/**
|
|
77
89
|
* List item template function
|
|
78
90
|
*/
|
|
79
91
|
export type ListItemTemplate<T = any> = (
|
|
80
92
|
item: T,
|
|
81
|
-
index: number
|
|
93
|
+
index: number,
|
|
82
94
|
) => string | HTMLElement;
|
|
83
95
|
|
|
84
96
|
/**
|
|
@@ -89,8 +101,6 @@ export interface ListScrollConfig {
|
|
|
89
101
|
virtual?: boolean;
|
|
90
102
|
/** Item size (fixed) or 'auto' for dynamic - height for vertical, width for horizontal */
|
|
91
103
|
itemSize?: number | "auto";
|
|
92
|
-
/** Estimated item size for dynamic sizing */
|
|
93
|
-
estimatedItemSize?: number;
|
|
94
104
|
/** Number of items to render outside viewport */
|
|
95
105
|
overscan?: number;
|
|
96
106
|
/** Enable scroll animations */
|
|
@@ -145,6 +155,8 @@ export interface ListSelectionConfig {
|
|
|
145
155
|
selectedIndices?: number[];
|
|
146
156
|
/** Selection change callback */
|
|
147
157
|
onSelectionChange?: (selectedItems: any[], selectedIndices: number[]) => void;
|
|
158
|
+
/** Require keyboard modifiers for multi-select (default: false) */
|
|
159
|
+
requireModifiers?: boolean;
|
|
148
160
|
}
|
|
149
161
|
|
|
150
162
|
/**
|
|
@@ -174,12 +186,13 @@ export interface ListPaginationConfig {
|
|
|
174
186
|
/**
|
|
175
187
|
* Complete List component configuration
|
|
176
188
|
*/
|
|
177
|
-
export interface ListConfig<T =
|
|
189
|
+
export interface ListConfig<T extends ListItem = ListItem>
|
|
190
|
+
extends ListStyleConfig {
|
|
178
191
|
// Data layer (Collection) configuration
|
|
179
192
|
collection?: Partial<CollectionConfig<T>>;
|
|
180
193
|
|
|
181
194
|
// Performance layer (List Manager) configuration
|
|
182
|
-
listManager?: Partial<
|
|
195
|
+
listManager?: Partial<any>;
|
|
183
196
|
|
|
184
197
|
// Pagination configuration
|
|
185
198
|
pagination?: ListPaginationConfig;
|
|
@@ -320,12 +333,25 @@ export interface ListEvents<T = any> {
|
|
|
320
333
|
"render:complete": {
|
|
321
334
|
renderRange: { start: number; end: number; count: number };
|
|
322
335
|
};
|
|
336
|
+
|
|
337
|
+
/** Item updated */
|
|
338
|
+
"item:updated": {
|
|
339
|
+
item: T;
|
|
340
|
+
index: number;
|
|
341
|
+
previousItem: T;
|
|
342
|
+
wasVisible: boolean;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
/** Viewport range loaded (data available for range) */
|
|
346
|
+
"viewport:range-loaded": {
|
|
347
|
+
range: { start: number; end: number };
|
|
348
|
+
};
|
|
323
349
|
}
|
|
324
350
|
|
|
325
351
|
/**
|
|
326
352
|
* List component API
|
|
327
353
|
*/
|
|
328
|
-
export interface ListAPI<T =
|
|
354
|
+
export interface ListAPI<T extends ListItem = ListItem> {
|
|
329
355
|
// Data management
|
|
330
356
|
/** Load data */
|
|
331
357
|
loadData(): Promise<void>;
|
|
@@ -342,9 +368,80 @@ export interface ListAPI<T = any> {
|
|
|
342
368
|
/** Remove items by indices */
|
|
343
369
|
removeItems(indices: number[]): void;
|
|
344
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Remove item at a specific index
|
|
373
|
+
* Removes the item from the collection, updates totalItems, and triggers re-render
|
|
374
|
+
* @param index - The index of the item to remove
|
|
375
|
+
* @returns true if item was found and removed, false otherwise
|
|
376
|
+
*/
|
|
377
|
+
removeItem(index: number): boolean;
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Remove item by ID
|
|
381
|
+
* Finds the item in the collection by its ID and removes it
|
|
382
|
+
* Updates totalItems and triggers re-render of visible items
|
|
383
|
+
* Optionally tracks as pending removal to filter from future fetches
|
|
384
|
+
* @param id - The item ID to find and remove
|
|
385
|
+
* @param options - Remove options (trackPending, pendingTimeout)
|
|
386
|
+
* @returns true if item was found and removed, false otherwise
|
|
387
|
+
*/
|
|
388
|
+
removeItemById(id: string | number, options?: RemoveItemOptions): boolean;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Check if an item ID is pending removal
|
|
392
|
+
* @param id - The item ID to check
|
|
393
|
+
* @returns true if the item is pending removal
|
|
394
|
+
*/
|
|
395
|
+
isPendingRemoval(id: string | number): boolean;
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Get all pending removal IDs
|
|
399
|
+
* @returns Set of pending removal IDs
|
|
400
|
+
*/
|
|
401
|
+
getPendingRemovals(): Set<string | number>;
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Clear a specific pending removal
|
|
405
|
+
* @param id - The item ID to clear from pending removals
|
|
406
|
+
*/
|
|
407
|
+
clearPendingRemoval(id: string | number): void;
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Clear all pending removals
|
|
411
|
+
*/
|
|
412
|
+
clearAllPendingRemovals(): void;
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Filter items array to exclude pending removals
|
|
416
|
+
* Utility method for use in collection adapters
|
|
417
|
+
* @param items - Array of items to filter
|
|
418
|
+
* @returns Filtered array without pending removal items
|
|
419
|
+
*/
|
|
420
|
+
filterPendingRemovals<I extends { id?: any; _id?: any }>(items: I[]): I[];
|
|
421
|
+
|
|
345
422
|
/** Update item at index */
|
|
346
423
|
updateItem(index: number, item: T): void;
|
|
347
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Update item by ID
|
|
427
|
+
* Finds the item in the collection by its ID and updates it with new data
|
|
428
|
+
* Re-renders the item if currently visible in the viewport
|
|
429
|
+
* @param id - The item ID to find
|
|
430
|
+
* @param data - Partial data to merge with existing item, or full item replacement
|
|
431
|
+
* @param options - Update options
|
|
432
|
+
* @returns true if item was found and updated, false otherwise
|
|
433
|
+
*/
|
|
434
|
+
updateItemById(
|
|
435
|
+
id: string | number,
|
|
436
|
+
data: Partial<T>,
|
|
437
|
+
options?: {
|
|
438
|
+
/** If true, replace the entire item instead of merging (default: false) */
|
|
439
|
+
replace?: boolean;
|
|
440
|
+
/** If true, re-render even if not visible (default: false) */
|
|
441
|
+
forceRender?: boolean;
|
|
442
|
+
},
|
|
443
|
+
): boolean;
|
|
444
|
+
|
|
348
445
|
/** Get item at index */
|
|
349
446
|
getItem(index: number): T | undefined;
|
|
350
447
|
|
|
@@ -358,7 +455,7 @@ export interface ListAPI<T = any> {
|
|
|
358
455
|
/** Scroll to item index */
|
|
359
456
|
scrollToIndex(
|
|
360
457
|
index: number,
|
|
361
|
-
alignment?: "start" | "center" | "end"
|
|
458
|
+
alignment?: "start" | "center" | "end",
|
|
362
459
|
): Promise<void>;
|
|
363
460
|
|
|
364
461
|
/** Scroll to top */
|
|
@@ -399,6 +496,32 @@ export interface ListAPI<T = any> {
|
|
|
399
496
|
/** Check if item is selected */
|
|
400
497
|
isSelected(index: number): boolean;
|
|
401
498
|
|
|
499
|
+
/** Select an item by its ID
|
|
500
|
+
* @param id - The ID of the item to select
|
|
501
|
+
* @param silent - If true, selection won't emit change event (default: false)
|
|
502
|
+
*/
|
|
503
|
+
selectById(id: string | number, silent?: boolean): boolean;
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Select item at index, scrolling and waiting for data if needed
|
|
507
|
+
* Handles virtual scrolling by loading data before selecting
|
|
508
|
+
*/
|
|
509
|
+
selectAtIndex(index: number): Promise<boolean>;
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Select next item relative to current selection
|
|
513
|
+
* Handles virtual scrolling by loading data before selecting
|
|
514
|
+
* @returns Promise resolving to true if selection changed, false if at end
|
|
515
|
+
*/
|
|
516
|
+
selectNext(): Promise<boolean>;
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Select previous item relative to current selection
|
|
520
|
+
* Handles virtual scrolling by loading data before selecting
|
|
521
|
+
* @returns Promise resolving to true if selection changed, false if at start
|
|
522
|
+
*/
|
|
523
|
+
selectPrevious(): Promise<boolean>;
|
|
524
|
+
|
|
402
525
|
// State
|
|
403
526
|
/** Get current list state */
|
|
404
527
|
getState(): ListState;
|
|
@@ -437,7 +560,7 @@ export interface ListAPI<T = any> {
|
|
|
437
560
|
|
|
438
561
|
/** Set error template */
|
|
439
562
|
setErrorTemplate(
|
|
440
|
-
template: string | ((error: Error) => string | HTMLElement)
|
|
563
|
+
template: string | ((error: Error) => string | HTMLElement),
|
|
441
564
|
): void;
|
|
442
565
|
|
|
443
566
|
// Configuration
|
|
@@ -451,7 +574,7 @@ export interface ListAPI<T = any> {
|
|
|
451
574
|
/**
|
|
452
575
|
* List component interface (extends mtrl component patterns)
|
|
453
576
|
*/
|
|
454
|
-
export interface ListComponent<T =
|
|
577
|
+
export interface ListComponent<T extends ListItem = ListItem>
|
|
455
578
|
extends BaseComponent,
|
|
456
579
|
ElementComponent,
|
|
457
580
|
ListAPI<T> {
|
|
@@ -459,7 +582,7 @@ export interface ListComponent<T = any>
|
|
|
459
582
|
collection: Collection<T>;
|
|
460
583
|
|
|
461
584
|
/** List manager instance (performance layer) */
|
|
462
|
-
listManager:
|
|
585
|
+
listManager: any;
|
|
463
586
|
|
|
464
587
|
/** Current list state */
|
|
465
588
|
state: ListState;
|
|
@@ -477,15 +600,15 @@ export interface ListComponent<T = any>
|
|
|
477
600
|
/** Event system (inherited from BaseComponent) */
|
|
478
601
|
on<K extends keyof ListEvents<T>>(
|
|
479
602
|
event: K,
|
|
480
|
-
handler: (payload: ListEvents<T>[K]) => void
|
|
603
|
+
handler: (payload: ListEvents<T>[K]) => void,
|
|
481
604
|
): void;
|
|
482
605
|
emit<K extends keyof ListEvents<T>>(
|
|
483
606
|
event: K,
|
|
484
|
-
payload: ListEvents<T>[K]
|
|
607
|
+
payload: ListEvents<T>[K],
|
|
485
608
|
): void;
|
|
486
609
|
off<K extends keyof ListEvents<T>>(
|
|
487
610
|
event: K,
|
|
488
|
-
handler?: (payload: ListEvents<T>[K]) => void
|
|
611
|
+
handler?: (payload: ListEvents<T>[K]) => void,
|
|
489
612
|
): void;
|
|
490
613
|
}
|
|
491
614
|
|
|
@@ -511,3 +634,90 @@ export interface ListFeatures {
|
|
|
511
634
|
styling?: boolean;
|
|
512
635
|
performance?: boolean;
|
|
513
636
|
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* VList configuration interface
|
|
640
|
+
*/
|
|
641
|
+
export interface VListConfig<T extends ListItem = ListItem> {
|
|
642
|
+
// Container
|
|
643
|
+
parent?: HTMLElement | string;
|
|
644
|
+
container?: HTMLElement | string; // Also support container
|
|
645
|
+
|
|
646
|
+
// Basic properties
|
|
647
|
+
class?: string;
|
|
648
|
+
className?: string; // Also support className
|
|
649
|
+
prefix?: string;
|
|
650
|
+
ariaLabel?: string;
|
|
651
|
+
debug?: boolean;
|
|
652
|
+
|
|
653
|
+
// Initial scroll position (0-based index)
|
|
654
|
+
// When set, VList will start loading from this position instead of 0
|
|
655
|
+
initialScrollIndex?: number;
|
|
656
|
+
|
|
657
|
+
// ID of item to select after initial load completes
|
|
658
|
+
// Works with initialScrollIndex to scroll to position and then select the item
|
|
659
|
+
selectId?: string | number;
|
|
660
|
+
|
|
661
|
+
// Whether to automatically load data on initialization (default: true)
|
|
662
|
+
// Set to false to defer loading until manually triggered
|
|
663
|
+
autoLoad?: boolean;
|
|
664
|
+
|
|
665
|
+
// Data source
|
|
666
|
+
items?: T[];
|
|
667
|
+
|
|
668
|
+
// Template for rendering items
|
|
669
|
+
template?: (
|
|
670
|
+
item: T,
|
|
671
|
+
index: number,
|
|
672
|
+
) => string | HTMLElement | any[] | Record<string, any>;
|
|
673
|
+
|
|
674
|
+
// Collection configuration
|
|
675
|
+
collection?: {
|
|
676
|
+
adapter?: ListAdapter<T>;
|
|
677
|
+
transform?: (item: T) => T;
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// Pagination configuration
|
|
681
|
+
pagination?: {
|
|
682
|
+
strategy?: "page" | "offset" | "cursor";
|
|
683
|
+
limit?: number;
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
// Virtual scrolling configuration
|
|
687
|
+
virtual?: {
|
|
688
|
+
itemSize?: number;
|
|
689
|
+
overscan?: number;
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
// Scrolling configuration
|
|
693
|
+
scrolling?: {
|
|
694
|
+
orientation?: "vertical" | "horizontal";
|
|
695
|
+
animation?: boolean;
|
|
696
|
+
measureItems?: boolean;
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
// Performance settings
|
|
700
|
+
performance?: {
|
|
701
|
+
recycleElements?: boolean;
|
|
702
|
+
bufferSize?: number;
|
|
703
|
+
renderDebounce?: number;
|
|
704
|
+
maxConcurrentRequests?: number;
|
|
705
|
+
/** Velocity threshold (px/ms) above which data loading is cancelled and placeholders are shown. Default: 2 */
|
|
706
|
+
cancelLoadThreshold?: number;
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// Selection configuration
|
|
710
|
+
selection?: ListSelectionConfig;
|
|
711
|
+
|
|
712
|
+
// Event handlers
|
|
713
|
+
on?: ListEventHandlers<T>;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
export type VListComponent<T extends ListItem = ListItem> = ListComponent<T> & {
|
|
717
|
+
viewport: ViewportComponent["viewport"];
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
export type VListItem = ListItem;
|
|
721
|
+
export type VListAPI<T extends ListItem = ListItem> = ListAPI<T>;
|
|
722
|
+
export type VListState = ListState;
|
|
723
|
+
export type VListEvents<T = any> = ListEvents<T>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/components/vlist/vlist.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* VList Component - Virtual List with direct viewport integration
|
|
5
|
+
*
|
|
6
|
+
* A simplified virtual list that uses the viewport feature directly
|
|
7
|
+
* without the list-manager abstraction layer.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { VListConfig, VListComponent, VListItem } from "./types";
|
|
11
|
+
|
|
12
|
+
// Import mtrl compose system
|
|
13
|
+
import { pipe } from "mtrl";
|
|
14
|
+
import { createBase, withElement } from "mtrl";
|
|
15
|
+
import { withEvents, withLifecycle } from "mtrl";
|
|
16
|
+
|
|
17
|
+
// Import viewport feature
|
|
18
|
+
import { withViewport } from "./features/viewport";
|
|
19
|
+
import { withAPI } from "./features/api";
|
|
20
|
+
import { withSelection } from "./features/selection";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a new VList component using direct viewport integration
|
|
24
|
+
*
|
|
25
|
+
* @param {VListConfig} config - List configuration options
|
|
26
|
+
* @returns {VListComponent} A fully configured virtual list component
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const vlist = createVList({
|
|
31
|
+
* container: '#my-list',
|
|
32
|
+
* collection: myAdapter,
|
|
33
|
+
* rangeSize: 20,
|
|
34
|
+
* paginationStrategy: 'page',
|
|
35
|
+
* template: (item, index) => [
|
|
36
|
+
* { class: 'viewport-item', attributes: { 'data-id': item.id }},
|
|
37
|
+
* [{ class: 'viewport-item__name', text: item.name }],
|
|
38
|
+
* [{ class: 'viewport-item__value', text: item.value }]
|
|
39
|
+
* ]
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export const createVList = <T extends VListItem = VListItem>(
|
|
44
|
+
config: VListConfig<T> = {}
|
|
45
|
+
): VListComponent<T> => {
|
|
46
|
+
try {
|
|
47
|
+
// console.log(`📋 Creating VList component with direct viewport integration`);
|
|
48
|
+
|
|
49
|
+
// Note: Transform should be applied by the collection feature in viewport
|
|
50
|
+
// VList should not intercept collection reads as it bypasses the loading manager
|
|
51
|
+
|
|
52
|
+
// Create the component through functional composition
|
|
53
|
+
const enhancers = [
|
|
54
|
+
// 1. Foundation layer
|
|
55
|
+
createBase,
|
|
56
|
+
withEvents(),
|
|
57
|
+
withElement({
|
|
58
|
+
tag: "div",
|
|
59
|
+
className: config.className || "mtrl-vlist",
|
|
60
|
+
attributes: {
|
|
61
|
+
role: "list",
|
|
62
|
+
"aria-label": config.ariaLabel || "Virtual List",
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
// 2. Viewport integration
|
|
67
|
+
withViewport(config),
|
|
68
|
+
|
|
69
|
+
// 3. Component lifecycle
|
|
70
|
+
withLifecycle(),
|
|
71
|
+
|
|
72
|
+
// 4. Public API layer
|
|
73
|
+
withAPI(config),
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// 4.5. Selection capabilities (if enabled) - must be after API
|
|
77
|
+
if (config.selection?.enabled) {
|
|
78
|
+
enhancers.push(withSelection(config));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const component = pipe(...enhancers)({
|
|
82
|
+
...config,
|
|
83
|
+
componentName: "vlist",
|
|
84
|
+
prefix: config.prefix || "mtrl",
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return component as VListComponent<T>;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error("❌ [VLIST] Failed to create VList component:", error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// src/core/compose/features/gestures.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module core/compose/features
|
|
4
|
+
* @description Adds gesture recognition capabilities to components
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BaseComponent, ElementComponent } from "mtrl";
|
|
8
|
+
import {
|
|
9
|
+
createGestureManager,
|
|
10
|
+
GestureManager,
|
|
11
|
+
GestureConfig,
|
|
12
|
+
GestureHandler,
|
|
13
|
+
AnyGestureEvent,
|
|
14
|
+
} from "../../../gestures";
|
|
15
|
+
import { hasLifecycle, hasEmit } from "mtrl";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Configuration for gestures feature
|
|
19
|
+
*/
|
|
20
|
+
export interface GesturesFeatureConfig extends GestureConfig {
|
|
21
|
+
/**
|
|
22
|
+
* Whether to enable gesture recognition immediately
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
enableGestures?: boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Initial gesture event handlers
|
|
29
|
+
*/
|
|
30
|
+
gestureHandlers?: Record<string, GestureHandler>;
|
|
31
|
+
|
|
32
|
+
[key: string]: any;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Component with gesture recognition capabilities
|
|
37
|
+
*/
|
|
38
|
+
export interface GesturesComponent extends BaseComponent {
|
|
39
|
+
/**
|
|
40
|
+
* Gesture manager instance
|
|
41
|
+
*/
|
|
42
|
+
gestures: GestureManager;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Add a gesture event handler
|
|
46
|
+
* @param eventType - Type of gesture event
|
|
47
|
+
* @param handler - Event handler function
|
|
48
|
+
* @returns GesturesComponent for chaining
|
|
49
|
+
*/
|
|
50
|
+
onGesture: (eventType: string, handler: GestureHandler) => GesturesComponent;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Remove a gesture event handler
|
|
54
|
+
* @param eventType - Type of gesture event
|
|
55
|
+
* @param handler - Event handler function
|
|
56
|
+
* @returns GesturesComponent for chaining
|
|
57
|
+
*/
|
|
58
|
+
offGesture: (eventType: string, handler: GestureHandler) => GesturesComponent;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if a gesture type is supported on the current device
|
|
62
|
+
* @param gestureType - Type of gesture to check
|
|
63
|
+
* @returns Whether the gesture is supported
|
|
64
|
+
*/
|
|
65
|
+
isGestureSupported: (gestureType: string) => boolean;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Enable gesture recognition
|
|
69
|
+
* @returns GesturesComponent for chaining
|
|
70
|
+
*/
|
|
71
|
+
enableGestures: () => GesturesComponent;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Disable gesture recognition
|
|
75
|
+
* @returns GesturesComponent for chaining
|
|
76
|
+
*/
|
|
77
|
+
disableGestures: () => GesturesComponent;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Adds gesture recognition capabilities to a component.
|
|
82
|
+
* This is a comprehensive gesture feature that adds support for all gesture types.
|
|
83
|
+
* For more lightweight, specific gestures, use the individual gesture features.
|
|
84
|
+
*
|
|
85
|
+
* @param config - Configuration object containing gesture settings
|
|
86
|
+
* @returns Function that enhances a component with gesture capabilities
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // Add gesture recognition to a component
|
|
91
|
+
* const component = pipe(
|
|
92
|
+
* createBase,
|
|
93
|
+
* withElement(...),
|
|
94
|
+
* withGestures({
|
|
95
|
+
* swipeThreshold: 50,
|
|
96
|
+
* gestureHandlers: {
|
|
97
|
+
* 'tap': (e) => handleTap(e),
|
|
98
|
+
* 'swipeleft': (e) => navigateForward(e),
|
|
99
|
+
* 'swiperight': (e) => navigateBack(e)
|
|
100
|
+
* }
|
|
101
|
+
* })
|
|
102
|
+
* )(config);
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export const withGestures =
|
|
106
|
+
(config: GesturesFeatureConfig = {}) =>
|
|
107
|
+
<C extends ElementComponent>(component: C): C & GesturesComponent => {
|
|
108
|
+
if (!component.element) {
|
|
109
|
+
console.warn("Cannot add gesture recognition: missing element");
|
|
110
|
+
return component as C & GesturesComponent;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Default configuration
|
|
114
|
+
const {
|
|
115
|
+
enableGestures = true,
|
|
116
|
+
gestureHandlers = {},
|
|
117
|
+
...gestureConfig
|
|
118
|
+
} = config;
|
|
119
|
+
|
|
120
|
+
// Create gesture manager
|
|
121
|
+
const gestureManager = createGestureManager(
|
|
122
|
+
component.element,
|
|
123
|
+
gestureConfig
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Add initial gesture handlers
|
|
127
|
+
Object.entries(gestureHandlers).forEach(([eventType, handler]) => {
|
|
128
|
+
gestureManager.on(eventType, handler);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Enable/disable based on config
|
|
132
|
+
if (!enableGestures) {
|
|
133
|
+
gestureManager.disable();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Connect with existing event system if available
|
|
137
|
+
if (hasEmit(component)) {
|
|
138
|
+
// Forward gesture events to the component's event system
|
|
139
|
+
const forwardGestureEvents = (event: AnyGestureEvent) => {
|
|
140
|
+
component.emit(event.type, event);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Register forwarder for common gesture types
|
|
144
|
+
[
|
|
145
|
+
"tap",
|
|
146
|
+
"swipe",
|
|
147
|
+
"swipeleft",
|
|
148
|
+
"swiperight",
|
|
149
|
+
"swipeup",
|
|
150
|
+
"swipedown",
|
|
151
|
+
"longpress",
|
|
152
|
+
"pinch",
|
|
153
|
+
"rotate",
|
|
154
|
+
"pan",
|
|
155
|
+
].forEach((type) => {
|
|
156
|
+
gestureManager.on(type, forwardGestureEvents);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle lifecycle integration
|
|
161
|
+
if (hasLifecycle(component)) {
|
|
162
|
+
const originalDestroy = component.lifecycle.destroy;
|
|
163
|
+
|
|
164
|
+
component.lifecycle.destroy = () => {
|
|
165
|
+
// Clean up gesture manager
|
|
166
|
+
gestureManager.destroy();
|
|
167
|
+
|
|
168
|
+
// Call original destroy method
|
|
169
|
+
originalDestroy.call(component.lifecycle);
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Create enhanced component
|
|
174
|
+
return {
|
|
175
|
+
...component,
|
|
176
|
+
gestures: gestureManager,
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Add a gesture event handler
|
|
180
|
+
* @param eventType - Type of gesture event
|
|
181
|
+
* @param handler - Event handler function
|
|
182
|
+
* @returns GesturesComponent for chaining
|
|
183
|
+
*/
|
|
184
|
+
onGesture(eventType: string, handler: GestureHandler) {
|
|
185
|
+
gestureManager.on(eventType, handler);
|
|
186
|
+
return this;
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Remove a gesture event handler
|
|
191
|
+
* @param eventType - Type of gesture event
|
|
192
|
+
* @param handler - Event handler function
|
|
193
|
+
* @returns GesturesComponent for chaining
|
|
194
|
+
*/
|
|
195
|
+
offGesture(eventType: string, handler: GestureHandler) {
|
|
196
|
+
gestureManager.off(eventType, handler);
|
|
197
|
+
return this;
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Check if a gesture type is supported on the current device
|
|
202
|
+
* @param gestureType - Type of gesture to check
|
|
203
|
+
* @returns Whether the gesture is supported
|
|
204
|
+
*/
|
|
205
|
+
isGestureSupported(gestureType: string) {
|
|
206
|
+
return gestureManager.isSupported(gestureType);
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Enable gesture recognition
|
|
211
|
+
* @returns GesturesComponent for chaining
|
|
212
|
+
*/
|
|
213
|
+
enableGestures() {
|
|
214
|
+
gestureManager.enable();
|
|
215
|
+
return this;
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Disable gesture recognition
|
|
220
|
+
* @returns GesturesComponent for chaining
|
|
221
|
+
*/
|
|
222
|
+
disableGestures() {
|
|
223
|
+
gestureManager.disable();
|
|
224
|
+
return this;
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
};
|