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
@@ -1,497 +0,0 @@
1
- /**
2
- * State management for mtrl-addons collection (Pure Data Layer)
3
- *
4
- * Handles reactive data state with zero UI concerns
5
- */
6
-
7
- import type { CollectionItem } from "./types";
8
- import {
9
- COLLECTION_STATE,
10
- DATA_PAGINATION,
11
- DATA_CACHE,
12
- DATA_LOGGING,
13
- } from "./constants";
14
-
15
- /**
16
- * Pure data state interface (NO UI STATE)
17
- */
18
- export interface CollectionDataState<
19
- T extends CollectionItem = CollectionItem
20
- > {
21
- // Core data
22
- items: T[];
23
- filteredItems: T[];
24
- totalCount: number;
25
-
26
- // Data loading state
27
- loading: boolean;
28
- error: Error | null;
29
- lastUpdate: number;
30
-
31
- // Pagination data
32
- currentPage: number;
33
- pageSize: number;
34
- hasMore: boolean;
35
- cursor: string | null;
36
-
37
- // Data operations state
38
- query: ((item: T) => boolean) | null;
39
- sort: ((a: T, b: T) => number) | null;
40
- searchQuery: string | null;
41
- searchFields: string[];
42
-
43
- // Cache state
44
- cacheHits: number;
45
- cacheMisses: number;
46
- cacheSize: number;
47
- lastCacheCleanup: number;
48
-
49
- // Background processing state
50
- activeWorkerTasks: number;
51
- prefetchQueue: number[];
52
- syncInProgress: boolean;
53
- lastSync: number;
54
-
55
- // Data persistence state
56
- isDirty: boolean;
57
- lastSave: number;
58
- persistenceError: Error | null;
59
-
60
- // Validation state
61
- validationErrors: string[];
62
- validationInProgress: boolean;
63
-
64
- // Transformation state
65
- transformInProgress: boolean;
66
- transformError: Error | null;
67
-
68
- // Data version for optimistic updates
69
- version: number;
70
-
71
- // NO UI state: scrollTop, containerHeight, visibleRange, renderRange, etc.
72
- }
73
-
74
- /**
75
- * State change listener type
76
- */
77
- export type StateChangeListener<T extends CollectionItem = CollectionItem> = (
78
- state: CollectionDataState<T>
79
- ) => void;
80
-
81
- /**
82
- * State store interface for collection data
83
- */
84
- export interface StateStore<T extends CollectionItem = CollectionItem> {
85
- // State access
86
- get(): CollectionDataState<T>;
87
- set(newState: Partial<CollectionDataState<T>>): void;
88
- update(
89
- updater: (state: CollectionDataState<T>) => Partial<CollectionDataState<T>>
90
- ): void;
91
-
92
- // State subscription
93
- subscribe(listener: StateChangeListener<T>): () => void;
94
-
95
- // State utilities
96
- reset(): void;
97
- snapshot(): CollectionDataState<T>;
98
- restore(snapshot: CollectionDataState<T>): void;
99
-
100
- // State queries
101
- isLoading(): boolean;
102
- hasError(): boolean;
103
- isEmpty(): boolean;
104
- isDirty(): boolean;
105
- getVersion(): number;
106
-
107
- // State lifecycle
108
- destroy(): void;
109
- }
110
-
111
- /**
112
- * Creates initial data state
113
- */
114
- export function createInitialDataState<
115
- T extends CollectionItem = CollectionItem
116
- >(
117
- options: {
118
- items?: T[];
119
- pageSize?: number;
120
- initialCapacity?: number;
121
- } = {}
122
- ): CollectionDataState<T> {
123
- const now = Date.now();
124
-
125
- return {
126
- // Core data
127
- items: options.items || [],
128
- filteredItems: options.items || [],
129
- totalCount: options.items?.length || COLLECTION_STATE.INITIAL_SIZE,
130
-
131
- // Data loading state
132
- loading: COLLECTION_STATE.INITIAL_LOADING,
133
- error: COLLECTION_STATE.INITIAL_ERROR,
134
- lastUpdate: now,
135
-
136
- // Pagination data
137
- currentPage: COLLECTION_STATE.INITIAL_PAGE,
138
- pageSize: options.pageSize || DATA_PAGINATION.DEFAULT_PAGE_SIZE,
139
- hasMore: COLLECTION_STATE.INITIAL_HAS_MORE,
140
- cursor: null,
141
-
142
- // Data operations state
143
- query: null,
144
- sort: null,
145
- searchQuery: null,
146
- searchFields: [],
147
-
148
- // Cache state
149
- cacheHits: 0,
150
- cacheMisses: 0,
151
- cacheSize: 0,
152
- lastCacheCleanup: now,
153
-
154
- // Background processing state
155
- activeWorkerTasks: 0,
156
- prefetchQueue: [],
157
- syncInProgress: false,
158
- lastSync: 0,
159
-
160
- // Data persistence state
161
- isDirty: false,
162
- lastSave: 0,
163
- persistenceError: null,
164
-
165
- // Validation state
166
- validationErrors: [],
167
- validationInProgress: false,
168
-
169
- // Transformation state
170
- transformInProgress: false,
171
- transformError: null,
172
-
173
- // Data version
174
- version: 1,
175
- };
176
- }
177
-
178
- /**
179
- * Creates a reactive data state store
180
- */
181
- export function createCollectionState<
182
- T extends CollectionItem = CollectionItem
183
- >(initialState?: Partial<CollectionDataState<T>>): StateStore<T> {
184
- let state: CollectionDataState<T> = {
185
- ...createInitialDataState<T>(),
186
- ...initialState,
187
- };
188
-
189
- const listeners = new Set<StateChangeListener<T>>();
190
- let isDestroyed = false;
191
-
192
- /**
193
- * Get current state (immutable copy)
194
- */
195
- const get = (): CollectionDataState<T> => {
196
- if (isDestroyed) {
197
- throw new Error("Cannot access destroyed state store");
198
- }
199
- return { ...state };
200
- };
201
-
202
- /**
203
- * Set partial state with validation
204
- */
205
- const set = (newState: Partial<CollectionDataState<T>>): void => {
206
- if (isDestroyed) {
207
- console.warn(
208
- `${DATA_LOGGING.PREFIX} Cannot set state on destroyed store`
209
- );
210
- return;
211
- }
212
-
213
- // Validate state changes
214
- if (newState.items && !Array.isArray(newState.items)) {
215
- throw new Error("Items must be an array");
216
- }
217
-
218
- if (newState.currentPage && newState.currentPage < 1) {
219
- throw new Error("Current page must be >= 1");
220
- }
221
-
222
- if (newState.pageSize && newState.pageSize < 1) {
223
- throw new Error("Page size must be >= 1");
224
- }
225
-
226
- // Apply state changes
227
- const previousState = { ...state };
228
- state = {
229
- ...state,
230
- ...newState,
231
- lastUpdate: Date.now(),
232
- version: state.version + 1,
233
- };
234
-
235
- // Auto-update filtered items if items changed
236
- if (newState.items && newState.items !== previousState.items) {
237
- state.filteredItems = applyFiltersAndSort(state.items, state);
238
- }
239
-
240
- // Mark as dirty if data changed
241
- if (newState.items || newState.filteredItems) {
242
- state.isDirty = true;
243
- }
244
-
245
- // Notify listeners
246
- notifyListeners();
247
-
248
- // Debug logging
249
- if (DATA_LOGGING.ENABLE_DEBUG) {
250
- console.log(`${DATA_LOGGING.PREFIX} State updated:`, {
251
- changes: Object.keys(newState),
252
- version: state.version,
253
- listenerCount: listeners.size,
254
- });
255
- }
256
- };
257
-
258
- /**
259
- * Update state using updater function
260
- */
261
- const update = (
262
- updater: (state: CollectionDataState<T>) => Partial<CollectionDataState<T>>
263
- ): void => {
264
- if (isDestroyed) {
265
- console.warn(
266
- `${DATA_LOGGING.PREFIX} Cannot update state on destroyed store`
267
- );
268
- return;
269
- }
270
-
271
- try {
272
- const changes = updater({ ...state });
273
- set(changes);
274
- } catch (error) {
275
- console.error(`${DATA_LOGGING.PREFIX} Error in state updater:`, error);
276
- set({ error: error as Error });
277
- }
278
- };
279
-
280
- /**
281
- * Subscribe to state changes
282
- */
283
- const subscribe = (listener: StateChangeListener<T>): (() => void) => {
284
- if (isDestroyed) {
285
- console.warn(
286
- `${DATA_LOGGING.PREFIX} Cannot subscribe to destroyed state store`
287
- );
288
- return () => {};
289
- }
290
-
291
- if (typeof listener !== "function") {
292
- throw new Error("State listener must be a function");
293
- }
294
-
295
- listeners.add(listener);
296
-
297
- // Return unsubscribe function
298
- return () => {
299
- listeners.delete(listener);
300
- };
301
- };
302
-
303
- /**
304
- * Notify all listeners of state changes
305
- */
306
- const notifyListeners = (): void => {
307
- const currentState = { ...state };
308
-
309
- listeners.forEach((listener) => {
310
- try {
311
- listener(currentState);
312
- } catch (error) {
313
- console.error(`${DATA_LOGGING.PREFIX} Error in state listener:`, error);
314
- // Remove problematic listener
315
- listeners.delete(listener);
316
- }
317
- });
318
- };
319
-
320
- /**
321
- * Apply filters and sorting to items
322
- */
323
- const applyFiltersAndSort = (
324
- items: T[],
325
- currentState: CollectionDataState<T>
326
- ): T[] => {
327
- let result = [...items];
328
-
329
- // Apply query filter
330
- if (currentState.query) {
331
- result = result.filter(currentState.query);
332
- }
333
-
334
- // Apply search filter
335
- if (currentState.searchQuery && currentState.searchFields.length > 0) {
336
- const searchQuery = currentState.searchQuery.toLowerCase();
337
- result = result.filter((item) => {
338
- return currentState.searchFields.some((field) => {
339
- const fieldValue = (item as any)[field];
340
- return (
341
- fieldValue &&
342
- fieldValue.toString().toLowerCase().includes(searchQuery)
343
- );
344
- });
345
- });
346
- }
347
-
348
- // Apply sorting
349
- if (currentState.sort) {
350
- result.sort(currentState.sort);
351
- }
352
-
353
- return result;
354
- };
355
-
356
- /**
357
- * Reset state to initial values
358
- */
359
- const reset = (): void => {
360
- const initialState = createInitialDataState<T>();
361
- state = { ...initialState };
362
- notifyListeners();
363
- };
364
-
365
- /**
366
- * Create state snapshot
367
- */
368
- const snapshot = (): CollectionDataState<T> => {
369
- return JSON.parse(JSON.stringify(state));
370
- };
371
-
372
- /**
373
- * Restore from snapshot
374
- */
375
- const restore = (snapshot: CollectionDataState<T>): void => {
376
- state = { ...snapshot };
377
- notifyListeners();
378
- };
379
-
380
- /**
381
- * State query utilities
382
- */
383
- const isLoading = (): boolean => state.loading;
384
- const hasError = (): boolean => state.error !== null;
385
- const isEmpty = (): boolean => state.items.length === 0;
386
- const isDirty = (): boolean => state.isDirty;
387
- const getVersion = (): number => state.version;
388
-
389
- /**
390
- * Destroy state store
391
- */
392
- const destroy = (): void => {
393
- listeners.clear();
394
- isDestroyed = true;
395
- };
396
-
397
- return {
398
- get,
399
- set,
400
- update,
401
- subscribe,
402
- reset,
403
- snapshot,
404
- restore,
405
- isLoading,
406
- hasError,
407
- isEmpty,
408
- isDirty,
409
- getVersion,
410
- destroy,
411
- };
412
- }
413
-
414
- /**
415
- * State utilities for debugging and testing
416
- */
417
- export const stateUtils = {
418
- /**
419
- * Create state diff between two states
420
- */
421
- createDiff: <T extends CollectionItem>(
422
- oldState: CollectionDataState<T>,
423
- newState: CollectionDataState<T>
424
- ): Record<string, { old: any; new: any }> => {
425
- const diff: Record<string, { old: any; new: any }> = {};
426
-
427
- Object.keys(newState).forEach((key) => {
428
- const oldValue = (oldState as any)[key];
429
- const newValue = (newState as any)[key];
430
-
431
- if (oldValue !== newValue) {
432
- diff[key] = { old: oldValue, new: newValue };
433
- }
434
- });
435
-
436
- return diff;
437
- },
438
-
439
- /**
440
- * Validate state integrity
441
- */
442
- validateState: <T extends CollectionItem>(
443
- state: CollectionDataState<T>
444
- ): string[] => {
445
- const errors: string[] = [];
446
-
447
- if (!Array.isArray(state.items)) {
448
- errors.push("Items must be an array");
449
- }
450
-
451
- if (!Array.isArray(state.filteredItems)) {
452
- errors.push("Filtered items must be an array");
453
- }
454
-
455
- if (state.currentPage < 1) {
456
- errors.push("Current page must be >= 1");
457
- }
458
-
459
- if (state.pageSize < 1) {
460
- errors.push("Page size must be >= 1");
461
- }
462
-
463
- if (state.totalCount < 0) {
464
- errors.push("Total count must be >= 0");
465
- }
466
-
467
- if (state.version < 1) {
468
- errors.push("Version must be >= 1");
469
- }
470
-
471
- return errors;
472
- },
473
-
474
- /**
475
- * Get state summary for debugging
476
- */
477
- getSummary: <T extends CollectionItem>(
478
- state: CollectionDataState<T>
479
- ): object => ({
480
- itemCount: state.items.length,
481
- filteredCount: state.filteredItems.length,
482
- totalCount: state.totalCount,
483
- currentPage: state.currentPage,
484
- pageSize: state.pageSize,
485
- hasMore: state.hasMore,
486
- loading: state.loading,
487
- hasError: !!state.error,
488
- hasQuery: !!state.query,
489
- hasSort: !!state.sort,
490
- hasSearch: !!state.searchQuery,
491
- version: state.version,
492
- isDirty: state.isDirty,
493
- cacheHits: state.cacheHits,
494
- cacheMisses: state.cacheMisses,
495
- activeWorkerTasks: state.activeWorkerTasks,
496
- }),
497
- };