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,383 @@
1
+ // src/core/compose/features/gestures/longpress.ts
2
+ /**
3
+ * @module core/compose/features/gestures
4
+ * @description Adds long press gesture recognition to components
5
+ */
6
+
7
+ import type { BaseComponent, ElementComponent } from "mtrl";
8
+ import { LongPressEvent, GestureHandler } from "../../../gestures";
9
+ import { hasLifecycle, hasEmit } from "mtrl";
10
+
11
+ /**
12
+ * Configuration for long press gesture feature
13
+ */
14
+ export interface LongPressGestureConfig {
15
+ /**
16
+ * Time (in ms) to recognize a long press
17
+ * @default 500
18
+ */
19
+ longPressTime?: number;
20
+
21
+ /**
22
+ * Distance threshold (in pixels) for movement that cancels long press
23
+ * @default 10
24
+ */
25
+ moveThreshold?: number;
26
+
27
+ /**
28
+ * Whether to prevent default behaviors on touch events
29
+ * @default true
30
+ */
31
+ preventDefault?: boolean;
32
+
33
+ /**
34
+ * Handler for long press gesture
35
+ */
36
+ onLongPress?: GestureHandler;
37
+
38
+ /**
39
+ * Whether to enable long press recognition immediately
40
+ * @default true
41
+ */
42
+ enabled?: boolean;
43
+
44
+ [key: string]: any;
45
+ }
46
+
47
+ /**
48
+ * Component with long press gesture recognition capabilities
49
+ */
50
+ export interface LongPressGestureComponent extends BaseComponent {
51
+ /**
52
+ * Add a long press event handler
53
+ * @param handler - Event handler function
54
+ * @returns Component for chaining
55
+ */
56
+ onLongPress: (
57
+ handler: (event: LongPressEvent) => void
58
+ ) => LongPressGestureComponent;
59
+
60
+ /**
61
+ * Remove a long press event handler
62
+ * @param handler - Event handler function
63
+ * @returns Component for chaining
64
+ */
65
+ offLongPress: (
66
+ handler: (event: LongPressEvent) => void
67
+ ) => LongPressGestureComponent;
68
+
69
+ /**
70
+ * Enable long press recognition
71
+ * @returns Component for chaining
72
+ */
73
+ enableLongPress: () => LongPressGestureComponent;
74
+
75
+ /**
76
+ * Disable long press recognition
77
+ * @returns Component for chaining
78
+ */
79
+ disableLongPress: () => LongPressGestureComponent;
80
+ }
81
+
82
+ /**
83
+ * Adds long press gesture recognition to a component.
84
+ * This is a lightweight alternative to the full gesture system,
85
+ * focused only on long press detection.
86
+ *
87
+ * @param config - Configuration object containing long press settings
88
+ * @returns Function that enhances a component with long press capabilities
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * // Add long press gesture recognition to a component
93
+ * const component = pipe(
94
+ * createBase,
95
+ * withElement(...),
96
+ * withLongPressGesture({
97
+ * longPressTime: 800,
98
+ * onLongPress: (e) => showContextMenu(e.x, e.y)
99
+ * })
100
+ * )(config);
101
+ * ```
102
+ */
103
+ export const withLongPressGesture =
104
+ (config: LongPressGestureConfig = {}) =>
105
+ <C extends ElementComponent>(component: C): C & LongPressGestureComponent => {
106
+ if (!component.element) {
107
+ console.warn(
108
+ "Cannot add long press gesture recognition: missing element"
109
+ );
110
+ return component as C & LongPressGestureComponent;
111
+ }
112
+
113
+ // Default configuration
114
+ const {
115
+ longPressTime = 500,
116
+ moveThreshold = 10,
117
+ preventDefault = true,
118
+ onLongPress,
119
+ enabled = true,
120
+ } = config;
121
+
122
+ // Event handlers storage
123
+ const handlers: Set<(event: LongPressEvent) => void> = new Set();
124
+
125
+ // If initial handler provided, add it
126
+ if (onLongPress) {
127
+ handlers.add(onLongPress as (event: LongPressEvent) => void);
128
+ }
129
+
130
+ // Gesture state for tracking
131
+ let startX = 0;
132
+ let startY = 0;
133
+ let currentX = 0;
134
+ let currentY = 0;
135
+ let active = false;
136
+ let startTime = 0;
137
+ let longPressTimer: number | null = null;
138
+ let isEnabled = enabled;
139
+
140
+ /**
141
+ * Dispatch a long press event to all handlers
142
+ */
143
+ const dispatchLongPress = (e: MouseEvent | TouchEvent): void => {
144
+ // Create the long press event
145
+ const longPressEvent: LongPressEvent = {
146
+ type: "longpress",
147
+ originalEvent: e,
148
+ target: e.target!,
149
+ startTime,
150
+ endTime: Date.now(),
151
+ duration: Date.now() - startTime,
152
+ defaultPrevented: false,
153
+ preventDefault: () => {
154
+ longPressEvent.defaultPrevented = true;
155
+ if (e.cancelable) {
156
+ e.preventDefault();
157
+ }
158
+ },
159
+ stopPropagation: () => {
160
+ e.stopPropagation();
161
+ },
162
+ x: currentX,
163
+ y: currentY,
164
+ };
165
+
166
+ // Call each handler
167
+ handlers.forEach((handler) => {
168
+ try {
169
+ handler(longPressEvent);
170
+ } catch (error) {
171
+ console.error("Error in long press handler:", error);
172
+ }
173
+ });
174
+
175
+ // Forward to component's event system if available
176
+ if (hasEmit(component)) {
177
+ component.emit("longpress", longPressEvent);
178
+ }
179
+
180
+ // Apply preventDefault if configured
181
+ if (preventDefault && !longPressEvent.defaultPrevented) {
182
+ longPressEvent.preventDefault();
183
+ }
184
+ };
185
+
186
+ /**
187
+ * Handle touch/mouse start
188
+ */
189
+ const handleStart = (e: MouseEvent | TouchEvent): void => {
190
+ if (!isEnabled) return;
191
+
192
+ const touch = "touches" in e ? e.touches[0] : e;
193
+
194
+ startX = currentX = touch.clientX;
195
+ startY = currentY = touch.clientY;
196
+ startTime = Date.now();
197
+ active = true;
198
+
199
+ // Cancel any existing timer
200
+ if (longPressTimer !== null) {
201
+ window.clearTimeout(longPressTimer);
202
+ }
203
+
204
+ // Set up long press timer
205
+ longPressTimer = window.setTimeout(() => {
206
+ if (active) {
207
+ // Check if movement was within threshold
208
+ const deltaX = currentX - startX;
209
+ const deltaY = currentY - startY;
210
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
211
+
212
+ if (distance < moveThreshold) {
213
+ dispatchLongPress(e);
214
+ }
215
+ }
216
+
217
+ longPressTimer = null;
218
+ }, longPressTime);
219
+ };
220
+
221
+ /**
222
+ * Handle touch/mouse move
223
+ */
224
+ const handleMove = (e: MouseEvent | TouchEvent): void => {
225
+ if (!active || !isEnabled) return;
226
+
227
+ const touch = "touches" in e ? e.touches[0] : e;
228
+
229
+ currentX = touch.clientX;
230
+ currentY = touch.clientY;
231
+
232
+ // Check if movement exceeds threshold
233
+ const deltaX = currentX - startX;
234
+ const deltaY = currentY - startY;
235
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
236
+
237
+ if (distance > moveThreshold) {
238
+ // Cancel long press if moved too much
239
+ if (longPressTimer !== null) {
240
+ window.clearTimeout(longPressTimer);
241
+ longPressTimer = null;
242
+ }
243
+ }
244
+ };
245
+
246
+ /**
247
+ * Handle touch/mouse end
248
+ */
249
+ const handleEnd = (): void => {
250
+ if (longPressTimer !== null) {
251
+ window.clearTimeout(longPressTimer);
252
+ longPressTimer = null;
253
+ }
254
+
255
+ active = false;
256
+ };
257
+
258
+ /**
259
+ * Handle touch/mouse cancel
260
+ */
261
+ const handleCancel = (): void => {
262
+ if (longPressTimer !== null) {
263
+ window.clearTimeout(longPressTimer);
264
+ longPressTimer = null;
265
+ }
266
+
267
+ active = false;
268
+ };
269
+
270
+ // Event listeners dictionary
271
+ const eventListeners: Record<string, EventListener> = {
272
+ mousedown: handleStart as EventListener,
273
+ mousemove: handleMove as EventListener,
274
+ mouseup: handleEnd as EventListener,
275
+ mouseleave: handleCancel as EventListener,
276
+ touchstart: handleStart as EventListener,
277
+ touchmove: handleMove as EventListener,
278
+ touchend: handleEnd as EventListener,
279
+ touchcancel: handleCancel as EventListener,
280
+ };
281
+
282
+ /**
283
+ * Add event listeners to element
284
+ */
285
+ const setupEventListeners = (): void => {
286
+ Object.entries(eventListeners).forEach(([event, listener]) => {
287
+ component.element.addEventListener(event, listener, {
288
+ passive: !preventDefault,
289
+ });
290
+ });
291
+ };
292
+
293
+ /**
294
+ * Remove event listeners from element
295
+ */
296
+ const removeEventListeners = (): void => {
297
+ Object.entries(eventListeners).forEach(([event, listener]) => {
298
+ component.element.removeEventListener(event, listener);
299
+ });
300
+ };
301
+
302
+ // Setup listeners if initially enabled
303
+ if (isEnabled) {
304
+ setupEventListeners();
305
+ }
306
+
307
+ // Handle lifecycle integration
308
+ if (hasLifecycle(component)) {
309
+ const originalDestroy = component.lifecycle.destroy;
310
+
311
+ component.lifecycle.destroy = () => {
312
+ // Clean up event listeners
313
+ removeEventListeners();
314
+
315
+ // Clear any timers
316
+ if (longPressTimer !== null) {
317
+ window.clearTimeout(longPressTimer);
318
+ longPressTimer = null;
319
+ }
320
+
321
+ // Clear handlers
322
+ handlers.clear();
323
+
324
+ // Call original destroy method
325
+ originalDestroy.call(component.lifecycle);
326
+ };
327
+ }
328
+
329
+ // Create enhanced component
330
+ return {
331
+ ...component,
332
+
333
+ /**
334
+ * Add a long press event handler
335
+ * @param handler - Event handler function
336
+ * @returns Component for chaining
337
+ */
338
+ onLongPress(handler: (event: LongPressEvent) => void) {
339
+ handlers.add(handler);
340
+ return this;
341
+ },
342
+
343
+ /**
344
+ * Remove a long press event handler
345
+ * @param handler - Event handler function
346
+ * @returns Component for chaining
347
+ */
348
+ offLongPress(handler: (event: LongPressEvent) => void) {
349
+ handlers.delete(handler);
350
+ return this;
351
+ },
352
+
353
+ /**
354
+ * Enable long press recognition
355
+ * @returns Component for chaining
356
+ */
357
+ enableLongPress() {
358
+ if (!isEnabled) {
359
+ isEnabled = true;
360
+ setupEventListeners();
361
+ }
362
+ return this;
363
+ },
364
+
365
+ /**
366
+ * Disable long press recognition
367
+ * @returns Component for chaining
368
+ */
369
+ disableLongPress() {
370
+ if (isEnabled) {
371
+ isEnabled = false;
372
+ removeEventListeners();
373
+
374
+ // Clear any timers
375
+ if (longPressTimer !== null) {
376
+ window.clearTimeout(longPressTimer);
377
+ longPressTimer = null;
378
+ }
379
+ }
380
+ return this;
381
+ },
382
+ };
383
+ };