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,334 @@
1
+ // src/core/compose/features/gestures/tap.ts
2
+ /**
3
+ * @module core/compose/features/gestures
4
+ * @description Adds tap gesture recognition to components
5
+ */
6
+
7
+ import type { BaseComponent, ElementComponent } from "mtrl";
8
+ import { TapEvent, GestureHandler } from "../../../gestures";
9
+ import { hasLifecycle, hasEmit } from "mtrl";
10
+
11
+ /**
12
+ * Configuration for tap gesture feature
13
+ */
14
+ export interface TapGestureConfig {
15
+ /**
16
+ * Distance threshold (in pixels) for tap recognition
17
+ * @default 10
18
+ */
19
+ tapDistanceThreshold?: number;
20
+
21
+ /**
22
+ * Whether to prevent default behaviors on touch events
23
+ * @default true
24
+ */
25
+ preventDefault?: boolean;
26
+
27
+ /**
28
+ * Handler for tap gesture
29
+ */
30
+ onTap?: GestureHandler;
31
+
32
+ /**
33
+ * Whether to enable tap recognition immediately
34
+ * @default true
35
+ */
36
+ enabled?: boolean;
37
+
38
+ [key: string]: any;
39
+ }
40
+
41
+ /**
42
+ * Component with tap gesture recognition capabilities
43
+ */
44
+ export interface TapGestureComponent extends BaseComponent {
45
+ /**
46
+ * Add a tap event handler
47
+ * @param handler - Event handler function
48
+ * @returns Component for chaining
49
+ */
50
+ onTap: (handler: (event: TapEvent) => void) => TapGestureComponent;
51
+
52
+ /**
53
+ * Remove a tap event handler
54
+ * @param handler - Event handler function
55
+ * @returns Component for chaining
56
+ */
57
+ offTap: (handler: (event: TapEvent) => void) => TapGestureComponent;
58
+
59
+ /**
60
+ * Enable tap recognition
61
+ * @returns Component for chaining
62
+ */
63
+ enableTap: () => TapGestureComponent;
64
+
65
+ /**
66
+ * Disable tap recognition
67
+ * @returns Component for chaining
68
+ */
69
+ disableTap: () => TapGestureComponent;
70
+ }
71
+
72
+ /**
73
+ * Adds tap gesture recognition to a component.
74
+ * This is a lightweight alternative to the full gesture system,
75
+ * focused only on tap detection.
76
+ *
77
+ * @param config - Configuration object containing tap settings
78
+ * @returns Function that enhances a component with tap capabilities
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * // Add tap gesture recognition to a component
83
+ * const component = pipe(
84
+ * createBase,
85
+ * withElement(...),
86
+ * withTapGesture({
87
+ * onTap: (e) => console.log('Tapped at', e.x, e.y)
88
+ * })
89
+ * )(config);
90
+ * ```
91
+ */
92
+ export const withTapGesture =
93
+ (config: TapGestureConfig = {}) =>
94
+ <C extends ElementComponent>(component: C): C & TapGestureComponent => {
95
+ if (!component.element) {
96
+ console.warn("Cannot add tap gesture recognition: missing element");
97
+ return component as C & TapGestureComponent;
98
+ }
99
+
100
+ // Default configuration
101
+ const {
102
+ tapDistanceThreshold = 10,
103
+ preventDefault = true,
104
+ onTap,
105
+ enabled = true,
106
+ } = config;
107
+
108
+ // Event handlers storage
109
+ const handlers: Set<(event: TapEvent) => void> = new Set();
110
+
111
+ // If initial handler provided, add it
112
+ if (onTap) {
113
+ handlers.add(onTap as (event: TapEvent) => void);
114
+ }
115
+
116
+ // Gesture state for tracking
117
+ let startX = 0;
118
+ let startY = 0;
119
+ let active = false;
120
+ let startTime = 0;
121
+ let lastTapTime = 0;
122
+ let tapCount = 0;
123
+ let isEnabled = enabled;
124
+
125
+ /**
126
+ * Dispatch a tap event to all handlers
127
+ */
128
+ const dispatchTap = (
129
+ e: MouseEvent | TouchEvent,
130
+ x: number,
131
+ y: number
132
+ ): void => {
133
+ const now = Date.now();
134
+ const isDoubleTap = now - lastTapTime < 300;
135
+
136
+ if (isDoubleTap) {
137
+ tapCount++;
138
+ } else {
139
+ tapCount = 1;
140
+ }
141
+
142
+ lastTapTime = now;
143
+
144
+ // Create the tap event
145
+ const tapEvent: TapEvent = {
146
+ type: "tap",
147
+ originalEvent: e,
148
+ target: e.target!,
149
+ startTime,
150
+ endTime: now,
151
+ duration: now - startTime,
152
+ defaultPrevented: false,
153
+ preventDefault: () => {
154
+ tapEvent.defaultPrevented = true;
155
+ if (e.cancelable) {
156
+ e.preventDefault();
157
+ }
158
+ },
159
+ stopPropagation: () => {
160
+ e.stopPropagation();
161
+ },
162
+ count: tapCount,
163
+ x,
164
+ y,
165
+ };
166
+
167
+ // Call each handler
168
+ handlers.forEach((handler) => {
169
+ try {
170
+ handler(tapEvent);
171
+ } catch (error) {
172
+ console.error("Error in tap handler:", error);
173
+ }
174
+ });
175
+
176
+ // Forward to component's event system if available
177
+ if (hasEmit(component)) {
178
+ component.emit("tap", tapEvent);
179
+ }
180
+
181
+ // Apply preventDefault if configured
182
+ if (preventDefault && !tapEvent.defaultPrevented) {
183
+ tapEvent.preventDefault();
184
+ }
185
+ };
186
+
187
+ /**
188
+ * Handle touch/mouse start
189
+ */
190
+ const handleStart = (e: MouseEvent | TouchEvent): void => {
191
+ if (!isEnabled) return;
192
+
193
+ const touch = "touches" in e ? e.touches[0] : e;
194
+
195
+ startX = touch.clientX;
196
+ startY = touch.clientY;
197
+ startTime = Date.now();
198
+ active = true;
199
+ };
200
+
201
+ /**
202
+ * Handle touch/mouse end
203
+ */
204
+ const handleEnd = (e: MouseEvent | TouchEvent): void => {
205
+ if (!active || !isEnabled) return;
206
+
207
+ const touch =
208
+ "changedTouches" in e && e.changedTouches.length > 0
209
+ ? e.changedTouches[0]
210
+ : (e as MouseEvent);
211
+
212
+ const endX = touch.clientX;
213
+ const endY = touch.clientY;
214
+
215
+ // Calculate distance moved
216
+ const deltaX = endX - startX;
217
+ const deltaY = endY - startY;
218
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
219
+
220
+ // Check if movement is within threshold
221
+ if (distance < tapDistanceThreshold) {
222
+ dispatchTap(e, endX, endY);
223
+ }
224
+
225
+ active = false;
226
+ };
227
+
228
+ /**
229
+ * Handle touch/mouse cancel
230
+ */
231
+ const handleCancel = (): void => {
232
+ active = false;
233
+ };
234
+
235
+ // Event listeners dictionary
236
+ const eventListeners: Record<string, EventListener> = {
237
+ mousedown: handleStart as EventListener,
238
+ mouseup: handleEnd as EventListener,
239
+ mouseleave: handleCancel as EventListener,
240
+ touchstart: handleStart as EventListener,
241
+ touchend: handleEnd as EventListener,
242
+ touchcancel: handleCancel as EventListener,
243
+ };
244
+
245
+ /**
246
+ * Add event listeners to element
247
+ */
248
+ const setupEventListeners = (): void => {
249
+ Object.entries(eventListeners).forEach(([event, listener]) => {
250
+ component.element.addEventListener(event, listener, {
251
+ passive: !preventDefault,
252
+ });
253
+ });
254
+ };
255
+
256
+ /**
257
+ * Remove event listeners from element
258
+ */
259
+ const removeEventListeners = (): void => {
260
+ Object.entries(eventListeners).forEach(([event, listener]) => {
261
+ component.element.removeEventListener(event, listener);
262
+ });
263
+ };
264
+
265
+ // Setup listeners if initially enabled
266
+ if (isEnabled) {
267
+ setupEventListeners();
268
+ }
269
+
270
+ // Handle lifecycle integration
271
+ if (hasLifecycle(component)) {
272
+ const originalDestroy = component.lifecycle.destroy;
273
+
274
+ component.lifecycle.destroy = () => {
275
+ // Clean up event listeners
276
+ removeEventListeners();
277
+
278
+ // Clear handlers
279
+ handlers.clear();
280
+
281
+ // Call original destroy method
282
+ originalDestroy.call(component.lifecycle);
283
+ };
284
+ }
285
+
286
+ // Create enhanced component
287
+ return {
288
+ ...component,
289
+
290
+ /**
291
+ * Add a tap event handler
292
+ * @param handler - Event handler function
293
+ * @returns Component for chaining
294
+ */
295
+ onTap(handler: (event: TapEvent) => void) {
296
+ handlers.add(handler);
297
+ return this;
298
+ },
299
+
300
+ /**
301
+ * Remove a tap event handler
302
+ * @param handler - Event handler function
303
+ * @returns Component for chaining
304
+ */
305
+ offTap(handler: (event: TapEvent) => void) {
306
+ handlers.delete(handler);
307
+ return this;
308
+ },
309
+
310
+ /**
311
+ * Enable tap recognition
312
+ * @returns Component for chaining
313
+ */
314
+ enableTap() {
315
+ if (!isEnabled) {
316
+ isEnabled = true;
317
+ setupEventListeners();
318
+ }
319
+ return this;
320
+ },
321
+
322
+ /**
323
+ * Disable tap recognition
324
+ * @returns Component for chaining
325
+ */
326
+ disableTap() {
327
+ if (isEnabled) {
328
+ isEnabled = false;
329
+ removeEventListeners();
330
+ }
331
+ return this;
332
+ },
333
+ };
334
+ };
@@ -1,39 +1,3 @@
1
- /**
2
- * @module core/compose/features
3
- * @description Addons-specific compose features for creating and combining components
4
- */
1
+ export { withGestures } from './gestures';
5
2
 
6
- // Collection feature
7
- export {
8
- withCollection,
9
- type CollectionConfig,
10
- type CollectionComponent,
11
- } from "./collection";
12
-
13
- // Styling feature
14
- export {
15
- withStyling,
16
- type StylingConfig,
17
- type StylingComponent,
18
- } from "./styling";
19
-
20
- // Selection feature
21
- export {
22
- withSelection,
23
- type SelectionConfig,
24
- type SelectionComponent,
25
- type SelectableItem,
26
- } from "./selection";
27
-
28
- // Performance feature
29
- export {
30
- withPerformance,
31
- type PerformanceConfig,
32
- type PerformanceComponent,
33
- type PerformanceMetrics,
34
- } from "./performance";
35
-
36
- // Future features will be exported here
37
- // export { withVirtualScroll } from './virtual-scroll';
38
- // export { withDataGrid } from './data-grid';
39
- // export { withInfiniteScroll } from './infinite-scroll';
3
+ export type { GesturesComponent, GesturesFeatureConfig } from './gestures';
@@ -1,31 +1,15 @@
1
- /**
2
- * @module core/compose
3
- * @description Core composition utilities and addons-specific features
4
- */
5
-
6
- // Re-export mtrl compose system
7
1
  export {
8
- pipe,
9
- createBase,
10
- withElement,
11
- withEvents,
12
- withLifecycle,
13
- } from "mtrl/src/core/compose";
2
+ withGestures
3
+ } from './features/gestures';
14
4
 
15
- // Export addons-specific features
16
- export {
17
- withCollection,
18
- withStyling,
19
- withSelection,
20
- withPerformance,
21
- type CollectionConfig,
22
- type CollectionComponent,
23
- type StylingConfig,
24
- type StylingComponent,
25
- type SelectionConfig,
26
- type SelectionComponent,
27
- type SelectableItem,
28
- type PerformanceConfig,
29
- type PerformanceComponent,
30
- type PerformanceMetrics,
31
- } from "./features";
5
+ // Gesture features
6
+ export { withTapGesture } from './features/gestures/tap';
7
+ export { withSwipeGesture } from './features/gestures/swipe';
8
+ export { withLongPressGesture } from './features/gestures/longpress';
9
+ export { withPanGesture } from './features/gestures/pan';
10
+
11
+
12
+ export type {
13
+ GesturesComponent,
14
+ GesturesFeatureConfig
15
+ } from './features';
@@ -0,0 +1,31 @@
1
+ // src/core/gestures/index.ts
2
+ /**
3
+ * @module core/gestures
4
+ * @description Modular gesture recognition system for touch and mouse interactions
5
+ */
6
+
7
+ // Export core gesture utilities and types
8
+ export { createGestureManager, GESTURE_TYPES, SWIPE_DIRECTIONS } from './manager';
9
+ export { detectTap } from './tap';
10
+ export { detectSwipe } from './swipe';
11
+ export { detectLongPress } from './longpress';
12
+ export { detectPinch } from './pinch';
13
+ export { detectRotate } from './rotate';
14
+ export { detectPan } from './pan';
15
+
16
+ // Export types
17
+ export type {
18
+ GestureManager,
19
+ GestureConfig,
20
+ GestureEvent,
21
+ TapEvent,
22
+ SwipeEvent,
23
+ LongPressEvent,
24
+ PinchEvent,
25
+ RotateEvent,
26
+ PanEvent,
27
+ AnyGestureEvent,
28
+ GestureHandler,
29
+ GestureState,
30
+ GestureDetectionContext
31
+ } from './types';
@@ -0,0 +1,68 @@
1
+ // src/core/gestures/longpress.ts
2
+ /**
3
+ * @module core/gestures
4
+ * @description Long press gesture detection
5
+ */
6
+
7
+ import { LongPressEvent, GestureDetectionContext } from './types';
8
+ import { createGestureEvent } from './utils';
9
+
10
+ /**
11
+ * Detect long press gesture
12
+ *
13
+ * This is slightly different from other gesture detectors as it sets up a timer
14
+ * and returns a callback function that will be triggered when the timer completes.
15
+ *
16
+ * @param context - Gesture detection context
17
+ * @param callback - Function to call when long press is detected
18
+ * @returns Cleanup function to cancel the long press detection
19
+ */
20
+ export function detectLongPress(
21
+ context: GestureDetectionContext,
22
+ callback: (event: LongPressEvent) => void
23
+ ): () => void {
24
+ const { state, options, originalEvent } = context;
25
+
26
+ // Set a timer for long press detection
27
+ const timer = window.setTimeout(() => {
28
+ // Only trigger if gesture is still active
29
+ if (state.active) {
30
+ // Check if movement is within threshold
31
+ const diffX = Math.abs(state.currentX - state.startX);
32
+ const diffY = Math.abs(state.currentY - state.startY);
33
+
34
+ if (diffX < options.tapDistanceThreshold && diffY < options.tapDistanceThreshold) {
35
+ // Create long press event
36
+ const longPressEvent: LongPressEvent = {
37
+ ...createGestureEvent('longpress', originalEvent, state),
38
+ type: 'longpress',
39
+ x: state.currentX,
40
+ y: state.currentY
41
+ };
42
+
43
+ // Trigger callback
44
+ callback(longPressEvent);
45
+ }
46
+ }
47
+ }, options.longPressTime);
48
+
49
+ // Return cleanup function
50
+ return () => {
51
+ clearTimeout(timer);
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Check if movement would cancel a long press
57
+ *
58
+ * @param context - Gesture detection context
59
+ * @returns Whether the long press should be canceled
60
+ */
61
+ export function shouldCancelLongPress(context: GestureDetectionContext): boolean {
62
+ const { state, options } = context;
63
+
64
+ const diffX = Math.abs(state.currentX - state.startX);
65
+ const diffY = Math.abs(state.currentY - state.startY);
66
+
67
+ return diffX > options.tapDistanceThreshold || diffY > options.tapDistanceThreshold;
68
+ }