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.
- package/AI.md +28 -230
- package/CLAUDE.md +882 -0
- package/build.js +253 -24
- package/package.json +14 -4
- package/scripts/debug/vlist-selection.ts +121 -0
- package/src/components/index.ts +5 -41
- package/src/components/{list → vlist}/config.ts +66 -95
- package/src/components/vlist/constants.ts +23 -0
- package/src/components/vlist/features/api.ts +626 -0
- package/src/components/vlist/features/index.ts +10 -0
- package/src/components/vlist/features/selection.ts +436 -0
- package/src/components/vlist/features/viewport.ts +59 -0
- package/src/components/vlist/index.ts +17 -0
- package/src/components/{list → vlist}/types.ts +242 -32
- package/src/components/vlist/vlist.ts +92 -0
- package/src/core/compose/features/gestures/index.ts +227 -0
- package/src/core/compose/features/gestures/longpress.ts +383 -0
- package/src/core/compose/features/gestures/pan.ts +424 -0
- package/src/core/compose/features/gestures/pinch.ts +475 -0
- package/src/core/compose/features/gestures/rotate.ts +485 -0
- package/src/core/compose/features/gestures/swipe.ts +492 -0
- package/src/core/compose/features/gestures/tap.ts +334 -0
- package/src/core/compose/features/index.ts +2 -38
- package/src/core/compose/index.ts +13 -29
- package/src/core/gestures/index.ts +31 -0
- package/src/core/gestures/longpress.ts +68 -0
- package/src/core/gestures/manager.ts +418 -0
- package/src/core/gestures/pan.ts +48 -0
- package/src/core/gestures/pinch.ts +58 -0
- package/src/core/gestures/rotate.ts +58 -0
- package/src/core/gestures/swipe.ts +66 -0
- package/src/core/gestures/tap.ts +45 -0
- package/src/core/gestures/types.ts +387 -0
- package/src/core/gestures/utils.ts +128 -0
- package/src/core/index.ts +27 -151
- package/src/core/layout/schema.ts +153 -72
- package/src/core/layout/types.ts +5 -2
- package/src/core/viewport/constants.ts +145 -0
- package/src/core/viewport/features/base.ts +73 -0
- package/src/core/viewport/features/collection.ts +1182 -0
- package/src/core/viewport/features/events.ts +130 -0
- package/src/core/viewport/features/index.ts +20 -0
- package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +31 -34
- package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
- package/src/core/viewport/features/momentum.ts +269 -0
- package/src/core/viewport/features/placeholders.ts +335 -0
- package/src/core/viewport/features/rendering.ts +962 -0
- package/src/core/viewport/features/scrollbar.ts +434 -0
- package/src/core/viewport/features/scrolling.ts +634 -0
- package/src/core/viewport/features/utils.ts +94 -0
- package/src/core/viewport/features/virtual.ts +525 -0
- package/src/core/viewport/index.ts +31 -0
- package/src/core/viewport/types.ts +133 -0
- package/src/core/viewport/utils/speed-tracker.ts +79 -0
- package/src/core/viewport/viewport.ts +265 -0
- package/src/index.ts +0 -7
- package/src/styles/components/_vlist.scss +352 -0
- package/src/styles/index.scss +1 -1
- package/test/components/vlist-selection.test.ts +240 -0
- package/test/components/vlist.test.ts +63 -0
- package/test/core/collection/adapter.test.ts +161 -0
- package/bun.lock +0 -792
- package/src/components/list/api.ts +0 -314
- package/src/components/list/constants.ts +0 -56
- package/src/components/list/features/api.ts +0 -428
- package/src/components/list/features/index.ts +0 -31
- package/src/components/list/features/list-manager.ts +0 -502
- package/src/components/list/index.ts +0 -39
- package/src/components/list/list.ts +0 -234
- package/src/core/collection/base-collection.ts +0 -100
- package/src/core/collection/collection-composer.ts +0 -178
- package/src/core/collection/collection.ts +0 -745
- package/src/core/collection/constants.ts +0 -172
- package/src/core/collection/events.ts +0 -428
- package/src/core/collection/features/api/loading.ts +0 -279
- package/src/core/collection/features/operations/data-operations.ts +0 -147
- package/src/core/collection/index.ts +0 -104
- package/src/core/collection/state.ts +0 -497
- package/src/core/collection/types.ts +0 -404
- package/src/core/compose/features/collection.ts +0 -119
- package/src/core/compose/features/selection.ts +0 -213
- package/src/core/compose/features/styling.ts +0 -108
- package/src/core/list-manager/api.ts +0 -599
- package/src/core/list-manager/config.ts +0 -593
- package/src/core/list-manager/constants.ts +0 -268
- package/src/core/list-manager/features/api.ts +0 -58
- package/src/core/list-manager/features/collection/collection.ts +0 -705
- package/src/core/list-manager/features/collection/index.ts +0 -17
- package/src/core/list-manager/features/viewport/constants.ts +0 -42
- package/src/core/list-manager/features/viewport/index.ts +0 -16
- package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
- package/src/core/list-manager/features/viewport/rendering.ts +0 -575
- package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
- package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
- package/src/core/list-manager/features/viewport/template.ts +0 -220
- package/src/core/list-manager/features/viewport/viewport.ts +0 -654
- package/src/core/list-manager/features/viewport/virtual.ts +0 -309
- package/src/core/list-manager/index.ts +0 -279
- package/src/core/list-manager/list-manager.ts +0 -206
- package/src/core/list-manager/types.ts +0 -439
- package/src/core/list-manager/utils/calculations.ts +0 -290
- package/src/core/list-manager/utils/range-calculator.ts +0 -349
- package/src/core/list-manager/utils/speed-tracker.ts +0 -273
- package/src/styles/components/_list.scss +0 -244
- package/src/types/mtrl.d.ts +0 -6
- package/test/components/list.test.ts +0 -256
- package/test/core/collection/failed-ranges.test.ts +0 -270
- package/test/core/compose/features.test.ts +0 -183
- package/test/core/list-manager/features/collection.test.ts +0 -704
- package/test/core/list-manager/features/viewport.test.ts +0 -698
- package/test/core/list-manager/list-manager.test.ts +0 -593
- package/test/core/list-manager/utils/calculations.test.ts +0 -433
- package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
- package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
- package/tsconfig.build.json +0 -23
- /package/src/components/{list → vlist}/features.ts +0 -0
- /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
|
+
};
|