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,424 @@
1
+ // src/core/compose/features/gestures/pan.ts
2
+ /**
3
+ * @module core/compose/features/gestures
4
+ * @description Adds pan gesture recognition to components
5
+ */
6
+
7
+ import type { BaseComponent, ElementComponent } from "mtrl";
8
+ import { PanEvent, GestureHandler } from "../../../gestures";
9
+ import { hasLifecycle, hasEmit } from "mtrl";
10
+
11
+ /**
12
+ * Configuration for pan gesture feature
13
+ */
14
+ export interface PanGestureConfig {
15
+ /**
16
+ * Whether to prevent default behaviors on touch events
17
+ * @default true
18
+ */
19
+ preventDefault?: boolean;
20
+
21
+ /**
22
+ * Handler for pan start (first movement)
23
+ */
24
+ onPanStart?: GestureHandler;
25
+
26
+ /**
27
+ * Handler for pan move (continuous updates during pan)
28
+ */
29
+ onPan?: GestureHandler;
30
+
31
+ /**
32
+ * Handler for pan end (touch/mouse release)
33
+ */
34
+ onPanEnd?: GestureHandler;
35
+
36
+ /**
37
+ * Whether to enable pan recognition immediately
38
+ * @default true
39
+ */
40
+ enabled?: boolean;
41
+
42
+ [key: string]: any;
43
+ }
44
+
45
+ /**
46
+ * Extend the PanEvent interface to support our custom event types
47
+ */
48
+ interface ExtendedPanEvent extends Omit<PanEvent, "type"> {
49
+ type: "pan" | "panstart" | "panend";
50
+ }
51
+
52
+ /**
53
+ * Component with pan gesture recognition capabilities
54
+ */
55
+ export interface PanGestureComponent extends BaseComponent {
56
+ /**
57
+ * Add a handler for pan start
58
+ * @param handler - Event handler function
59
+ * @returns Component for chaining
60
+ */
61
+ onPanStart: (handler: (event: PanEvent) => void) => PanGestureComponent;
62
+
63
+ /**
64
+ * Add a handler for pan move (continuous updates)
65
+ * @param handler - Event handler function
66
+ * @returns Component for chaining
67
+ */
68
+ onPan: (handler: (event: PanEvent) => void) => PanGestureComponent;
69
+
70
+ /**
71
+ * Add a handler for pan end
72
+ * @param handler - Event handler function
73
+ * @returns Component for chaining
74
+ */
75
+ onPanEnd: (handler: (event: PanEvent) => void) => PanGestureComponent;
76
+
77
+ /**
78
+ * Remove a pan start handler
79
+ * @param handler - Event handler function
80
+ * @returns Component for chaining
81
+ */
82
+ offPanStart: (handler: (event: PanEvent) => void) => PanGestureComponent;
83
+
84
+ /**
85
+ * Remove a pan move handler
86
+ * @param handler - Event handler function
87
+ * @returns Component for chaining
88
+ */
89
+ offPan: (handler: (event: PanEvent) => void) => PanGestureComponent;
90
+
91
+ /**
92
+ * Remove a pan end handler
93
+ * @param handler - Event handler function
94
+ * @returns Component for chaining
95
+ */
96
+ offPanEnd: (handler: (event: PanEvent) => void) => PanGestureComponent;
97
+
98
+ /**
99
+ * Enable pan recognition
100
+ * @returns Component for chaining
101
+ */
102
+ enablePan: () => PanGestureComponent;
103
+
104
+ /**
105
+ * Disable pan recognition
106
+ * @returns Component for chaining
107
+ */
108
+ disablePan: () => PanGestureComponent;
109
+ }
110
+
111
+ /**
112
+ * Adds pan gesture recognition to a component.
113
+ * This is a lightweight alternative to the full gesture system,
114
+ * focused only on pan detection.
115
+ *
116
+ * @param config - Configuration object containing pan settings
117
+ * @returns Function that enhances a component with pan capabilities
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * // Add pan gesture recognition to a component
122
+ * const component = pipe(
123
+ * createBase,
124
+ * withElement(...),
125
+ * withPanGesture({
126
+ * onPanStart: (e) => startDrag(e),
127
+ * onPan: (e) => updateDragPosition(e),
128
+ * onPanEnd: (e) => endDrag(e)
129
+ * })
130
+ * )(config);
131
+ * ```
132
+ */
133
+ export const withPanGesture =
134
+ (config: PanGestureConfig = {}) =>
135
+ <C extends ElementComponent>(component: C): C & PanGestureComponent => {
136
+ if (!component.element) {
137
+ console.warn("Cannot add pan gesture recognition: missing element");
138
+ return component as C & PanGestureComponent;
139
+ }
140
+
141
+ // Default configuration
142
+ const {
143
+ preventDefault = true,
144
+ onPanStart,
145
+ onPan,
146
+ onPanEnd,
147
+ enabled = true,
148
+ } = config;
149
+
150
+ // Event handlers storage by pan phase
151
+ const handlers = {
152
+ panstart: new Set<(event: PanEvent) => void>(),
153
+ pan: new Set<(event: PanEvent) => void>(),
154
+ panend: new Set<(event: PanEvent) => void>(),
155
+ };
156
+
157
+ // Add initial handlers if provided
158
+ if (onPanStart)
159
+ handlers.panstart.add(onPanStart as (event: PanEvent) => void);
160
+ if (onPan) handlers.pan.add(onPan as (event: PanEvent) => void);
161
+ if (onPanEnd) handlers.panend.add(onPanEnd as (event: PanEvent) => void);
162
+
163
+ // Gesture state for tracking
164
+ let startX = 0;
165
+ let startY = 0;
166
+ let lastX = 0;
167
+ let lastY = 0;
168
+ let currentX = 0;
169
+ let currentY = 0;
170
+ let active = false;
171
+ let isPanning = false;
172
+ let startTime = 0;
173
+ let isEnabled = enabled;
174
+
175
+ /**
176
+ * Create a pan event with the current state
177
+ */
178
+ const createPanEvent = (
179
+ e: MouseEvent | TouchEvent,
180
+ type: "panstart" | "pan" | "panend"
181
+ ): ExtendedPanEvent => {
182
+ const event: ExtendedPanEvent = {
183
+ type,
184
+ originalEvent: e,
185
+ target: e.target!,
186
+ startTime,
187
+ endTime: Date.now(),
188
+ duration: Date.now() - startTime,
189
+ defaultPrevented: false,
190
+ preventDefault: () => {
191
+ event.defaultPrevented = true;
192
+ if (e.cancelable) {
193
+ e.preventDefault();
194
+ }
195
+ },
196
+ stopPropagation: () => {
197
+ e.stopPropagation();
198
+ },
199
+ deltaX: currentX - startX,
200
+ deltaY: currentY - startY,
201
+ startX,
202
+ startY,
203
+ currentX,
204
+ currentY,
205
+ };
206
+ return event;
207
+ };
208
+
209
+ /**
210
+ * Dispatch a pan event to all registered handlers
211
+ */
212
+ const dispatchPan = (
213
+ e: MouseEvent | TouchEvent,
214
+ type: "panstart" | "pan" | "panend"
215
+ ): void => {
216
+ const extendedPanEvent = createPanEvent(e, type);
217
+
218
+ // Call each handler for this phase
219
+ handlers[type].forEach((handler) => {
220
+ try {
221
+ // Type assertion for the handler call - we're deliberately passing our extended event
222
+ handler(extendedPanEvent as unknown as PanEvent);
223
+ } catch (error) {
224
+ console.error(`Error in ${type} handler:`, error);
225
+ }
226
+ });
227
+
228
+ // Forward to component's event system if available
229
+ if (hasEmit(component)) {
230
+ component.emit(type, extendedPanEvent);
231
+ }
232
+
233
+ // Apply preventDefault if configured
234
+ if (preventDefault && !extendedPanEvent.defaultPrevented) {
235
+ extendedPanEvent.preventDefault();
236
+ }
237
+ };
238
+
239
+ /**
240
+ * Handle touch/mouse start
241
+ */
242
+ const handleStart = (e: MouseEvent | TouchEvent): void => {
243
+ if (!isEnabled) return;
244
+
245
+ const touch = "touches" in e ? e.touches[0] : e;
246
+
247
+ startX = lastX = currentX = touch.clientX;
248
+ startY = lastY = currentY = touch.clientY;
249
+ startTime = Date.now();
250
+ active = true;
251
+ isPanning = false;
252
+ };
253
+
254
+ /**
255
+ * Handle touch/mouse move
256
+ */
257
+ const handleMove = (e: MouseEvent | TouchEvent): void => {
258
+ if (!active || !isEnabled) return;
259
+
260
+ const touch = "touches" in e ? e.touches[0] : e;
261
+
262
+ // Update position
263
+ lastX = currentX;
264
+ lastY = currentY;
265
+ currentX = touch.clientX;
266
+ currentY = touch.clientY;
267
+
268
+ // Calculate movement delta
269
+ const moveDeltaX = currentX - lastX;
270
+ const moveDeltaY = currentY - lastY;
271
+ const moveDelta = Math.sqrt(
272
+ moveDeltaX * moveDeltaX + moveDeltaY * moveDeltaY
273
+ );
274
+
275
+ // Detect significant movement
276
+ if (moveDelta > 0) {
277
+ // If this is the first significant movement, trigger panstart
278
+ if (!isPanning) {
279
+ isPanning = true;
280
+ dispatchPan(e, "panstart");
281
+ }
282
+
283
+ // Then trigger the continuous pan event
284
+ dispatchPan(e, "pan");
285
+ }
286
+ };
287
+
288
+ /**
289
+ * Handle touch/mouse end
290
+ */
291
+ const handleEnd = (e: MouseEvent | TouchEvent): void => {
292
+ if (!active || !isEnabled) return;
293
+
294
+ // If we were panning, trigger the panend event
295
+ if (isPanning) {
296
+ dispatchPan(e, "panend");
297
+ }
298
+
299
+ active = false;
300
+ isPanning = false;
301
+ };
302
+
303
+ /**
304
+ * Handle touch/mouse cancel
305
+ */
306
+ const handleCancel = (e: MouseEvent | TouchEvent): void => {
307
+ if (!active || !isEnabled) return;
308
+
309
+ // If we were panning, trigger the panend event
310
+ if (isPanning) {
311
+ dispatchPan(e, "panend");
312
+ }
313
+
314
+ active = false;
315
+ isPanning = false;
316
+ };
317
+
318
+ // Event listeners dictionary
319
+ const eventListeners: Record<string, EventListener> = {
320
+ mousedown: handleStart as EventListener,
321
+ mousemove: handleMove as EventListener,
322
+ mouseup: handleEnd as EventListener,
323
+ mouseleave: handleCancel as EventListener,
324
+ touchstart: handleStart as EventListener,
325
+ touchmove: handleMove as EventListener,
326
+ touchend: handleEnd as EventListener,
327
+ touchcancel: handleCancel as EventListener,
328
+ };
329
+
330
+ /**
331
+ * Add event listeners to element
332
+ */
333
+ const setupEventListeners = (): void => {
334
+ Object.entries(eventListeners).forEach(([event, listener]) => {
335
+ component.element.addEventListener(event, listener, {
336
+ passive: !preventDefault,
337
+ });
338
+ });
339
+ };
340
+
341
+ /**
342
+ * Remove event listeners from element
343
+ */
344
+ const removeEventListeners = (): void => {
345
+ Object.entries(eventListeners).forEach(([event, listener]) => {
346
+ component.element.removeEventListener(event, listener);
347
+ });
348
+ };
349
+
350
+ // Setup listeners if initially enabled
351
+ if (isEnabled) {
352
+ setupEventListeners();
353
+ }
354
+
355
+ // Handle lifecycle integration
356
+ if (hasLifecycle(component)) {
357
+ const originalDestroy = component.lifecycle.destroy;
358
+
359
+ component.lifecycle.destroy = () => {
360
+ // Clean up event listeners
361
+ removeEventListeners();
362
+
363
+ // Clear handlers
364
+ Object.values(handlers).forEach((handlerSet) => handlerSet.clear());
365
+
366
+ // Call original destroy method
367
+ originalDestroy.call(component.lifecycle);
368
+ };
369
+ }
370
+
371
+ // Create enhanced component
372
+ return {
373
+ ...component,
374
+
375
+ // Add handler methods
376
+ onPanStart(handler: (event: PanEvent) => void) {
377
+ handlers.panstart.add(handler);
378
+ return this;
379
+ },
380
+
381
+ onPan(handler: (event: PanEvent) => void) {
382
+ handlers.pan.add(handler);
383
+ return this;
384
+ },
385
+
386
+ onPanEnd(handler: (event: PanEvent) => void) {
387
+ handlers.panend.add(handler);
388
+ return this;
389
+ },
390
+
391
+ // Remove handler methods
392
+ offPanStart(handler: (event: PanEvent) => void) {
393
+ handlers.panstart.delete(handler);
394
+ return this;
395
+ },
396
+
397
+ offPan(handler: (event: PanEvent) => void) {
398
+ handlers.pan.delete(handler);
399
+ return this;
400
+ },
401
+
402
+ offPanEnd(handler: (event: PanEvent) => void) {
403
+ handlers.panend.delete(handler);
404
+ return this;
405
+ },
406
+
407
+ // Enable/disable methods
408
+ enablePan() {
409
+ if (!isEnabled) {
410
+ isEnabled = true;
411
+ setupEventListeners();
412
+ }
413
+ return this;
414
+ },
415
+
416
+ disablePan() {
417
+ if (isEnabled) {
418
+ isEnabled = false;
419
+ removeEventListeners();
420
+ }
421
+ return this;
422
+ },
423
+ };
424
+ };