mtrl-addons 0.1.2 → 0.2.1

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