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
@@ -0,0 +1,492 @@
1
+ // src/core/compose/features/gestures/swipe.ts
2
+ /**
3
+ * @module core/compose/features/gestures
4
+ * @description Adds swipe gesture recognition to components
5
+ */
6
+
7
+ import type { BaseComponent, ElementComponent } from "mtrl";
8
+ import {
9
+ SwipeEvent,
10
+ SWIPE_DIRECTIONS,
11
+ GestureHandler,
12
+ } from "../../../gestures";
13
+ import { hasLifecycle, hasEmit } from "mtrl";
14
+
15
+ /**
16
+ * Configuration for swipe gesture feature
17
+ */
18
+ export interface SwipeGestureConfig {
19
+ /**
20
+ * Minimum distance (in pixels) to recognize a swipe
21
+ * @default 30
22
+ */
23
+ swipeThreshold?: number;
24
+
25
+ /**
26
+ * Maximum time (in ms) in which a swipe must be completed
27
+ * @default 300
28
+ */
29
+ swipeTimeThreshold?: number;
30
+
31
+ /**
32
+ * Whether to prevent default behaviors on touch events
33
+ * @default true
34
+ */
35
+ preventDefault?: boolean;
36
+
37
+ /**
38
+ * Handler for any swipe direction
39
+ */
40
+ onSwipe?: GestureHandler;
41
+
42
+ /**
43
+ * Handler specifically for left swipes
44
+ */
45
+ onSwipeLeft?: GestureHandler;
46
+
47
+ /**
48
+ * Handler specifically for right swipes
49
+ */
50
+ onSwipeRight?: GestureHandler;
51
+
52
+ /**
53
+ * Handler specifically for up swipes
54
+ */
55
+ onSwipeUp?: GestureHandler;
56
+
57
+ /**
58
+ * Handler specifically for down swipes
59
+ */
60
+ onSwipeDown?: GestureHandler;
61
+
62
+ /**
63
+ * Whether to enable swipe recognition immediately
64
+ * @default true
65
+ */
66
+ enabled?: boolean;
67
+
68
+ [key: string]: any;
69
+ }
70
+
71
+ /**
72
+ * Component with swipe gesture recognition capabilities
73
+ */
74
+ export interface SwipeGestureComponent extends BaseComponent {
75
+ /**
76
+ * Add a handler for any swipe direction
77
+ * @param handler - Event handler function
78
+ * @returns Component for chaining
79
+ */
80
+ onSwipe: (handler: (event: SwipeEvent) => void) => SwipeGestureComponent;
81
+
82
+ /**
83
+ * Add a handler specifically for left swipes
84
+ * @param handler - Event handler function
85
+ * @returns Component for chaining
86
+ */
87
+ onSwipeLeft: (handler: (event: SwipeEvent) => void) => SwipeGestureComponent;
88
+
89
+ /**
90
+ * Add a handler specifically for right swipes
91
+ * @param handler - Event handler function
92
+ * @returns Component for chaining
93
+ */
94
+ onSwipeRight: (handler: (event: SwipeEvent) => void) => SwipeGestureComponent;
95
+
96
+ /**
97
+ * Add a handler specifically for up swipes
98
+ * @param handler - Event handler function
99
+ * @returns Component for chaining
100
+ */
101
+ onSwipeUp: (handler: (event: SwipeEvent) => void) => SwipeGestureComponent;
102
+
103
+ /**
104
+ * Add a handler specifically for down swipes
105
+ * @param handler - Event handler function
106
+ * @returns Component for chaining
107
+ */
108
+ onSwipeDown: (handler: (event: SwipeEvent) => void) => SwipeGestureComponent;
109
+
110
+ /**
111
+ * Remove a swipe event handler for any direction
112
+ * @param handler - Event handler function
113
+ * @returns Component for chaining
114
+ */
115
+ offSwipe: (handler: (event: SwipeEvent) => void) => SwipeGestureComponent;
116
+
117
+ /**
118
+ * Remove a left swipe event handler
119
+ * @param handler - Event handler function
120
+ * @returns Component for chaining
121
+ */
122
+ offSwipeLeft: (handler: (event: SwipeEvent) => void) => SwipeGestureComponent;
123
+
124
+ /**
125
+ * Remove a right swipe event handler
126
+ * @param handler - Event handler function
127
+ * @returns Component for chaining
128
+ */
129
+ offSwipeRight: (
130
+ handler: (event: SwipeEvent) => void
131
+ ) => SwipeGestureComponent;
132
+
133
+ /**
134
+ * Remove an up swipe event handler
135
+ * @param handler - Event handler function
136
+ * @returns Component for chaining
137
+ */
138
+ offSwipeUp: (handler: (event: SwipeEvent) => void) => SwipeGestureComponent;
139
+
140
+ /**
141
+ * Remove a down swipe event handler
142
+ * @param handler - Event handler function
143
+ * @returns Component for chaining
144
+ */
145
+ offSwipeDown: (handler: (event: SwipeEvent) => void) => SwipeGestureComponent;
146
+
147
+ /**
148
+ * Enable swipe recognition
149
+ * @returns Component for chaining
150
+ */
151
+ enableSwipe: () => SwipeGestureComponent;
152
+
153
+ /**
154
+ * Disable swipe recognition
155
+ * @returns Component for chaining
156
+ */
157
+ disableSwipe: () => SwipeGestureComponent;
158
+ }
159
+
160
+ /**
161
+ * Determine swipe direction based on delta X and Y
162
+ *
163
+ * @param deltaX - Distance moved in X direction
164
+ * @param deltaY - Distance moved in Y direction
165
+ * @returns Direction of the swipe
166
+ */
167
+ function getSwipeDirection(deltaX: number, deltaY: number): SWIPE_DIRECTIONS {
168
+ if (Math.abs(deltaX) > Math.abs(deltaY)) {
169
+ return deltaX > 0 ? SWIPE_DIRECTIONS.RIGHT : SWIPE_DIRECTIONS.LEFT;
170
+ }
171
+ return deltaY > 0 ? SWIPE_DIRECTIONS.DOWN : SWIPE_DIRECTIONS.UP;
172
+ }
173
+
174
+ /**
175
+ * Adds swipe gesture recognition to a component.
176
+ * This is a lightweight alternative to the full gesture system,
177
+ * focused only on swipe detection.
178
+ *
179
+ * @param config - Configuration object containing swipe settings
180
+ * @returns Function that enhances a component with swipe capabilities
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * // Add swipe gesture recognition to a component
185
+ * const component = pipe(
186
+ * createBase,
187
+ * withElement(...),
188
+ * withSwipeGesture({
189
+ * onSwipeLeft: () => showNextPage(),
190
+ * onSwipeRight: () => showPreviousPage()
191
+ * })
192
+ * )(config);
193
+ * ```
194
+ */
195
+ export const withSwipeGesture =
196
+ (config: SwipeGestureConfig = {}) =>
197
+ <C extends ElementComponent>(component: C): C & SwipeGestureComponent => {
198
+ if (!component.element) {
199
+ console.warn("Cannot add swipe gesture recognition: missing element");
200
+ return component as C & SwipeGestureComponent;
201
+ }
202
+
203
+ // Default configuration
204
+ const {
205
+ swipeThreshold = 30,
206
+ swipeTimeThreshold = 300,
207
+ preventDefault = true,
208
+ onSwipe,
209
+ onSwipeLeft,
210
+ onSwipeRight,
211
+ onSwipeUp,
212
+ onSwipeDown,
213
+ enabled = true,
214
+ } = config;
215
+
216
+ // Event handlers storage by direction
217
+ const handlers = {
218
+ swipe: new Set<(event: SwipeEvent) => void>(),
219
+ swipeleft: new Set<(event: SwipeEvent) => void>(),
220
+ swiperight: new Set<(event: SwipeEvent) => void>(),
221
+ swipeup: new Set<(event: SwipeEvent) => void>(),
222
+ swipedown: new Set<(event: SwipeEvent) => void>(),
223
+ };
224
+
225
+ // Add initial handlers if provided
226
+ if (onSwipe) handlers.swipe.add(onSwipe as (event: SwipeEvent) => void);
227
+ if (onSwipeLeft)
228
+ handlers.swipeleft.add(onSwipeLeft as (event: SwipeEvent) => void);
229
+ if (onSwipeRight)
230
+ handlers.swiperight.add(onSwipeRight as (event: SwipeEvent) => void);
231
+ if (onSwipeUp)
232
+ handlers.swipeup.add(onSwipeUp as (event: SwipeEvent) => void);
233
+ if (onSwipeDown)
234
+ handlers.swipedown.add(onSwipeDown as (event: SwipeEvent) => void);
235
+
236
+ // Gesture state for tracking
237
+ let startX = 0;
238
+ let startY = 0;
239
+ let active = false;
240
+ let startTime = 0;
241
+ let isEnabled = enabled;
242
+
243
+ /**
244
+ * Dispatch a swipe event to all registered handlers
245
+ */
246
+ const dispatchSwipe = (
247
+ e: MouseEvent | TouchEvent,
248
+ endX: number,
249
+ endY: number
250
+ ): void => {
251
+ const deltaX = endX - startX;
252
+ const deltaY = endY - startY;
253
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
254
+ const endTime = Date.now();
255
+ const duration = endTime - startTime;
256
+ const velocity = distance / duration;
257
+ const direction = getSwipeDirection(deltaX, deltaY);
258
+
259
+ // Create the swipe event
260
+ const swipeEvent: SwipeEvent = {
261
+ type: "swipe",
262
+ originalEvent: e,
263
+ target: e.target!,
264
+ startTime,
265
+ endTime,
266
+ duration,
267
+ defaultPrevented: false,
268
+ preventDefault: () => {
269
+ swipeEvent.defaultPrevented = true;
270
+ if (e.cancelable) {
271
+ e.preventDefault();
272
+ }
273
+ },
274
+ stopPropagation: () => {
275
+ e.stopPropagation();
276
+ },
277
+ direction,
278
+ deltaX,
279
+ deltaY,
280
+ distance,
281
+ velocity,
282
+ startX,
283
+ startY,
284
+ endX,
285
+ endY,
286
+ };
287
+
288
+ // First trigger generic swipe handlers
289
+ handlers.swipe.forEach((handler) => {
290
+ try {
291
+ handler(swipeEvent);
292
+ } catch (error) {
293
+ console.error("Error in swipe handler:", error);
294
+ }
295
+ });
296
+
297
+ // Then trigger direction-specific handlers
298
+ const directionKey = `swipe${direction}` as keyof typeof handlers;
299
+ handlers[directionKey].forEach((handler) => {
300
+ try {
301
+ handler({ ...swipeEvent, type: directionKey } as SwipeEvent);
302
+ } catch (error) {
303
+ console.error(`Error in ${directionKey} handler:`, error);
304
+ }
305
+ });
306
+
307
+ // Forward to component's event system if available
308
+ if (hasEmit(component)) {
309
+ component.emit("swipe", swipeEvent);
310
+ component.emit(directionKey, swipeEvent);
311
+ }
312
+
313
+ // Apply preventDefault if configured
314
+ if (preventDefault && !swipeEvent.defaultPrevented) {
315
+ swipeEvent.preventDefault();
316
+ }
317
+ };
318
+
319
+ /**
320
+ * Handle touch/mouse start
321
+ */
322
+ const handleStart = (e: MouseEvent | TouchEvent): void => {
323
+ if (!isEnabled) return;
324
+
325
+ const touch = "touches" in e ? e.touches[0] : e;
326
+
327
+ startX = touch.clientX;
328
+ startY = touch.clientY;
329
+ startTime = Date.now();
330
+ active = true;
331
+ };
332
+
333
+ /**
334
+ * Handle touch/mouse end
335
+ */
336
+ const handleEnd = (e: MouseEvent | TouchEvent): void => {
337
+ if (!active || !isEnabled) return;
338
+
339
+ const touch =
340
+ "changedTouches" in e && e.changedTouches.length > 0
341
+ ? e.changedTouches[0]
342
+ : (e as MouseEvent);
343
+
344
+ const endX = touch.clientX;
345
+ const endY = touch.clientY;
346
+
347
+ // Calculate swipe properties
348
+ const deltaX = endX - startX;
349
+ const deltaY = endY - startY;
350
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
351
+ const duration = Date.now() - startTime;
352
+
353
+ // Check if it's a swipe
354
+ if (distance >= swipeThreshold && duration <= swipeTimeThreshold) {
355
+ dispatchSwipe(e, endX, endY);
356
+ }
357
+
358
+ active = false;
359
+ };
360
+
361
+ /**
362
+ * Handle touch/mouse cancel
363
+ */
364
+ const handleCancel = (): void => {
365
+ active = false;
366
+ };
367
+
368
+ // Event listeners dictionary
369
+ const eventListeners: Record<string, EventListener> = {
370
+ mousedown: handleStart as EventListener,
371
+ mouseup: handleEnd as EventListener,
372
+ mouseleave: handleCancel as EventListener,
373
+ touchstart: handleStart as EventListener,
374
+ touchend: handleEnd as EventListener,
375
+ touchcancel: handleCancel as EventListener,
376
+ };
377
+
378
+ /**
379
+ * Add event listeners to element
380
+ */
381
+ const setupEventListeners = (): void => {
382
+ Object.entries(eventListeners).forEach(([event, listener]) => {
383
+ component.element.addEventListener(event, listener, {
384
+ passive: !preventDefault,
385
+ });
386
+ });
387
+ };
388
+
389
+ /**
390
+ * Remove event listeners from element
391
+ */
392
+ const removeEventListeners = (): void => {
393
+ Object.entries(eventListeners).forEach(([event, listener]) => {
394
+ component.element.removeEventListener(event, listener);
395
+ });
396
+ };
397
+
398
+ // Setup listeners if initially enabled
399
+ if (isEnabled) {
400
+ setupEventListeners();
401
+ }
402
+
403
+ // Handle lifecycle integration
404
+ if (hasLifecycle(component)) {
405
+ const originalDestroy = component.lifecycle.destroy;
406
+
407
+ component.lifecycle.destroy = () => {
408
+ // Clean up event listeners
409
+ removeEventListeners();
410
+
411
+ // Clear handlers
412
+ Object.values(handlers).forEach((handlerSet) => handlerSet.clear());
413
+
414
+ // Call original destroy method
415
+ originalDestroy.call(component.lifecycle);
416
+ };
417
+ }
418
+
419
+ // Create enhanced component
420
+ return {
421
+ ...component,
422
+
423
+ // Add handler methods
424
+ onSwipe(handler: (event: SwipeEvent) => void) {
425
+ handlers.swipe.add(handler);
426
+ return this;
427
+ },
428
+
429
+ onSwipeLeft(handler: (event: SwipeEvent) => void) {
430
+ handlers.swipeleft.add(handler);
431
+ return this;
432
+ },
433
+
434
+ onSwipeRight(handler: (event: SwipeEvent) => void) {
435
+ handlers.swiperight.add(handler);
436
+ return this;
437
+ },
438
+
439
+ onSwipeUp(handler: (event: SwipeEvent) => void) {
440
+ handlers.swipeup.add(handler);
441
+ return this;
442
+ },
443
+
444
+ onSwipeDown(handler: (event: SwipeEvent) => void) {
445
+ handlers.swipedown.add(handler);
446
+ return this;
447
+ },
448
+
449
+ // Remove handler methods
450
+ offSwipe(handler: (event: SwipeEvent) => void) {
451
+ handlers.swipe.delete(handler);
452
+ return this;
453
+ },
454
+
455
+ offSwipeLeft(handler: (event: SwipeEvent) => void) {
456
+ handlers.swipeleft.delete(handler);
457
+ return this;
458
+ },
459
+
460
+ offSwipeRight(handler: (event: SwipeEvent) => void) {
461
+ handlers.swiperight.delete(handler);
462
+ return this;
463
+ },
464
+
465
+ offSwipeUp(handler: (event: SwipeEvent) => void) {
466
+ handlers.swipeup.delete(handler);
467
+ return this;
468
+ },
469
+
470
+ offSwipeDown(handler: (event: SwipeEvent) => void) {
471
+ handlers.swipedown.delete(handler);
472
+ return this;
473
+ },
474
+
475
+ // Enable/disable methods
476
+ enableSwipe() {
477
+ if (!isEnabled) {
478
+ isEnabled = true;
479
+ setupEventListeners();
480
+ }
481
+ return this;
482
+ },
483
+
484
+ disableSwipe() {
485
+ if (isEnabled) {
486
+ isEnabled = false;
487
+ removeEventListeners();
488
+ }
489
+ return this;
490
+ },
491
+ };
492
+ };