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.
Files changed (117) hide show
  1. package/AI.md +28 -230
  2. package/CLAUDE.md +882 -0
  3. package/build.js +253 -24
  4. package/package.json +14 -4
  5. package/scripts/debug/vlist-selection.ts +121 -0
  6. package/src/components/index.ts +5 -41
  7. package/src/components/{list → vlist}/config.ts +66 -95
  8. package/src/components/vlist/constants.ts +23 -0
  9. package/src/components/vlist/features/api.ts +626 -0
  10. package/src/components/vlist/features/index.ts +10 -0
  11. package/src/components/vlist/features/selection.ts +436 -0
  12. package/src/components/vlist/features/viewport.ts +59 -0
  13. package/src/components/vlist/index.ts +17 -0
  14. package/src/components/{list → vlist}/types.ts +242 -32
  15. package/src/components/vlist/vlist.ts +92 -0
  16. package/src/core/compose/features/gestures/index.ts +227 -0
  17. package/src/core/compose/features/gestures/longpress.ts +383 -0
  18. package/src/core/compose/features/gestures/pan.ts +424 -0
  19. package/src/core/compose/features/gestures/pinch.ts +475 -0
  20. package/src/core/compose/features/gestures/rotate.ts +485 -0
  21. package/src/core/compose/features/gestures/swipe.ts +492 -0
  22. package/src/core/compose/features/gestures/tap.ts +334 -0
  23. package/src/core/compose/features/index.ts +2 -38
  24. package/src/core/compose/index.ts +13 -29
  25. package/src/core/gestures/index.ts +31 -0
  26. package/src/core/gestures/longpress.ts +68 -0
  27. package/src/core/gestures/manager.ts +418 -0
  28. package/src/core/gestures/pan.ts +48 -0
  29. package/src/core/gestures/pinch.ts +58 -0
  30. package/src/core/gestures/rotate.ts +58 -0
  31. package/src/core/gestures/swipe.ts +66 -0
  32. package/src/core/gestures/tap.ts +45 -0
  33. package/src/core/gestures/types.ts +387 -0
  34. package/src/core/gestures/utils.ts +128 -0
  35. package/src/core/index.ts +27 -151
  36. package/src/core/layout/schema.ts +153 -72
  37. package/src/core/layout/types.ts +5 -2
  38. package/src/core/viewport/constants.ts +145 -0
  39. package/src/core/viewport/features/base.ts +73 -0
  40. package/src/core/viewport/features/collection.ts +1182 -0
  41. package/src/core/viewport/features/events.ts +130 -0
  42. package/src/core/viewport/features/index.ts +20 -0
  43. package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +31 -34
  44. package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
  45. package/src/core/viewport/features/momentum.ts +269 -0
  46. package/src/core/viewport/features/placeholders.ts +335 -0
  47. package/src/core/viewport/features/rendering.ts +962 -0
  48. package/src/core/viewport/features/scrollbar.ts +434 -0
  49. package/src/core/viewport/features/scrolling.ts +634 -0
  50. package/src/core/viewport/features/utils.ts +94 -0
  51. package/src/core/viewport/features/virtual.ts +525 -0
  52. package/src/core/viewport/index.ts +31 -0
  53. package/src/core/viewport/types.ts +133 -0
  54. package/src/core/viewport/utils/speed-tracker.ts +79 -0
  55. package/src/core/viewport/viewport.ts +265 -0
  56. package/src/index.ts +0 -7
  57. package/src/styles/components/_vlist.scss +352 -0
  58. package/src/styles/index.scss +1 -1
  59. package/test/components/vlist-selection.test.ts +240 -0
  60. package/test/components/vlist.test.ts +63 -0
  61. package/test/core/collection/adapter.test.ts +161 -0
  62. package/bun.lock +0 -792
  63. package/src/components/list/api.ts +0 -314
  64. package/src/components/list/constants.ts +0 -56
  65. package/src/components/list/features/api.ts +0 -428
  66. package/src/components/list/features/index.ts +0 -31
  67. package/src/components/list/features/list-manager.ts +0 -502
  68. package/src/components/list/index.ts +0 -39
  69. package/src/components/list/list.ts +0 -234
  70. package/src/core/collection/base-collection.ts +0 -100
  71. package/src/core/collection/collection-composer.ts +0 -178
  72. package/src/core/collection/collection.ts +0 -745
  73. package/src/core/collection/constants.ts +0 -172
  74. package/src/core/collection/events.ts +0 -428
  75. package/src/core/collection/features/api/loading.ts +0 -279
  76. package/src/core/collection/features/operations/data-operations.ts +0 -147
  77. package/src/core/collection/index.ts +0 -104
  78. package/src/core/collection/state.ts +0 -497
  79. package/src/core/collection/types.ts +0 -404
  80. package/src/core/compose/features/collection.ts +0 -119
  81. package/src/core/compose/features/selection.ts +0 -213
  82. package/src/core/compose/features/styling.ts +0 -108
  83. package/src/core/list-manager/api.ts +0 -599
  84. package/src/core/list-manager/config.ts +0 -593
  85. package/src/core/list-manager/constants.ts +0 -268
  86. package/src/core/list-manager/features/api.ts +0 -58
  87. package/src/core/list-manager/features/collection/collection.ts +0 -705
  88. package/src/core/list-manager/features/collection/index.ts +0 -17
  89. package/src/core/list-manager/features/viewport/constants.ts +0 -42
  90. package/src/core/list-manager/features/viewport/index.ts +0 -16
  91. package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
  92. package/src/core/list-manager/features/viewport/rendering.ts +0 -575
  93. package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
  94. package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
  95. package/src/core/list-manager/features/viewport/template.ts +0 -220
  96. package/src/core/list-manager/features/viewport/viewport.ts +0 -654
  97. package/src/core/list-manager/features/viewport/virtual.ts +0 -309
  98. package/src/core/list-manager/index.ts +0 -279
  99. package/src/core/list-manager/list-manager.ts +0 -206
  100. package/src/core/list-manager/types.ts +0 -439
  101. package/src/core/list-manager/utils/calculations.ts +0 -290
  102. package/src/core/list-manager/utils/range-calculator.ts +0 -349
  103. package/src/core/list-manager/utils/speed-tracker.ts +0 -273
  104. package/src/styles/components/_list.scss +0 -244
  105. package/src/types/mtrl.d.ts +0 -6
  106. package/test/components/list.test.ts +0 -256
  107. package/test/core/collection/failed-ranges.test.ts +0 -270
  108. package/test/core/compose/features.test.ts +0 -183
  109. package/test/core/list-manager/features/collection.test.ts +0 -704
  110. package/test/core/list-manager/features/viewport.test.ts +0 -698
  111. package/test/core/list-manager/list-manager.test.ts +0 -593
  112. package/test/core/list-manager/utils/calculations.test.ts +0 -433
  113. package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
  114. package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
  115. package/tsconfig.build.json +0 -23
  116. /package/src/components/{list → vlist}/features.ts +0 -0
  117. /package/src/core/{compose → viewport}/features/performance.ts +0 -0
@@ -1,28 +1,40 @@
1
1
  /**
2
- * Types for the mtrl-addons list component
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
- CollectionItem,
9
- CollectionConfig,
10
- Collection,
11
- TemplateDefinition,
12
- } from "../../core/collection";
13
- import type { BaseComponent, ElementComponent } from "mtrl/src/core/compose";
14
- import type {
15
- ListManager,
16
- ListManagerConfig,
17
- } from "../../core/list-manager/types";
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 = TemplateDefinition;
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 = any> extends ListStyleConfig {
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<ListManagerConfig>;
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 = any> {
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 = any>
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: 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
+ };