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,433 +0,0 @@
1
- // test/core/list-manager/utils/calculations.test.ts
2
-
3
- import { describe, it, expect } from "bun:test";
4
- import {
5
- calculateTotalVirtualSize,
6
- calculateContainerPosition,
7
- calculateScrollPositionForIndex,
8
- calculateScrollPositionForPage,
9
- calculateScrollbarMetrics,
10
- calculateInitialRangeSize,
11
- calculateMissingRanges,
12
- calculateBufferRanges,
13
- clamp,
14
- applyBoundaryResistance,
15
- } from "../../../../src/core/list-manager/utils/calculations";
16
- import type { ItemRange } from "../../../../src/core/list-manager/types";
17
-
18
- describe("Calculations Utility", () => {
19
- describe("clamp", () => {
20
- it("should clamp value within range", () => {
21
- expect(clamp(5, 0, 10)).toBe(5);
22
- expect(clamp(-5, 0, 10)).toBe(0);
23
- expect(clamp(15, 0, 10)).toBe(10);
24
- });
25
-
26
- it("should handle edge cases", () => {
27
- expect(clamp(0, 0, 0)).toBe(0);
28
- expect(clamp(5, 5, 5)).toBe(5);
29
- expect(clamp(3, 5, 2)).toBe(3); // Invalid range
30
- });
31
- });
32
-
33
- // Removed calculateVisibleRange tests - using unified index-based approach
34
-
35
- describe("calculateTotalVirtualSize", () => {
36
- it("should calculate total size for uniform items", () => {
37
- const size = calculateTotalVirtualSize({
38
- totalItems: 100,
39
- estimatedItemSize: 50,
40
- orientation: "vertical",
41
- measuredSizes: new Map(),
42
- });
43
-
44
- expect(size).toBe(5000); // 100 * 50
45
- });
46
-
47
- it("should calculate size with some measured items", () => {
48
- const measuredSizes = new Map<number, number>();
49
- measuredSizes.set(0, 60);
50
- measuredSizes.set(1, 70);
51
- measuredSizes.set(2, 40);
52
-
53
- const size = calculateTotalVirtualSize({
54
- totalItems: 10,
55
- estimatedItemSize: 50,
56
- orientation: "vertical",
57
- measuredSizes,
58
- });
59
-
60
- // First 3 items: 60 + 70 + 40 = 170
61
- // Remaining 7 items: 7 * 50 = 350
62
- // Total: 520
63
- expect(size).toBe(520);
64
- });
65
-
66
- it("should handle empty list", () => {
67
- const size = calculateTotalVirtualSize({
68
- totalItems: 0,
69
- estimatedItemSize: 50,
70
- orientation: "vertical",
71
- measuredSizes: new Map(),
72
- });
73
-
74
- expect(size).toBe(0);
75
- });
76
- });
77
-
78
- describe("calculateContainerPosition", () => {
79
- it("should calculate position for vertical orientation", () => {
80
- const position = calculateContainerPosition({
81
- index: 10,
82
- estimatedItemSize: 50,
83
- orientation: "vertical",
84
- measuredSizes: new Map(),
85
- totalItems: 100,
86
- });
87
-
88
- expect(position).toBe(500); // 10 * 50
89
- });
90
-
91
- it("should calculate position with measured sizes", () => {
92
- const measuredSizes = new Map<number, number>();
93
- measuredSizes.set(0, 60);
94
- measuredSizes.set(1, 70);
95
- measuredSizes.set(2, 40);
96
-
97
- const position = calculateContainerPosition({
98
- index: 3,
99
- estimatedItemSize: 50,
100
- orientation: "horizontal",
101
- measuredSizes,
102
- totalItems: 100,
103
- });
104
-
105
- expect(position).toBe(170); // 60 + 70 + 40
106
- });
107
-
108
- it("should handle index 0", () => {
109
- const position = calculateContainerPosition({
110
- index: 0,
111
- estimatedItemSize: 50,
112
- orientation: "vertical",
113
- measuredSizes: new Map(),
114
- totalItems: 100,
115
- });
116
-
117
- expect(position).toBe(0);
118
- });
119
- });
120
-
121
- describe("calculateScrollPositionForIndex", () => {
122
- it("should calculate scroll position for 'start' alignment", () => {
123
- const position = calculateScrollPositionForIndex({
124
- index: 10,
125
- alignment: "start",
126
- containerSize: 300,
127
- estimatedItemSize: 50,
128
- orientation: "vertical",
129
- measuredSizes: new Map(),
130
- totalItems: 100,
131
- });
132
-
133
- expect(position).toBe(500); // 10 * 50
134
- });
135
-
136
- it("should calculate scroll position for 'center' alignment", () => {
137
- const position = calculateScrollPositionForIndex({
138
- index: 10,
139
- alignment: "center",
140
- containerSize: 300,
141
- estimatedItemSize: 50,
142
- orientation: "vertical",
143
- measuredSizes: new Map(),
144
- totalItems: 100,
145
- });
146
-
147
- // Item at 500, center in 300px container = 500 - 150 + 25 = 375
148
- expect(position).toBe(375);
149
- });
150
-
151
- it("should calculate scroll position for 'end' alignment", () => {
152
- const position = calculateScrollPositionForIndex({
153
- index: 10,
154
- alignment: "end",
155
- containerSize: 300,
156
- estimatedItemSize: 50,
157
- orientation: "vertical",
158
- measuredSizes: new Map(),
159
- totalItems: 100,
160
- });
161
-
162
- // Item at 500, end in 300px container = 500 - 300 + 50 = 250
163
- expect(position).toBe(250);
164
- });
165
-
166
- it("should not scroll past beginning", () => {
167
- const position = calculateScrollPositionForIndex({
168
- index: 0,
169
- alignment: "center",
170
- containerSize: 300,
171
- estimatedItemSize: 50,
172
- orientation: "vertical",
173
- measuredSizes: new Map(),
174
- totalItems: 100,
175
- });
176
-
177
- expect(position).toBeGreaterThanOrEqual(0);
178
- });
179
- });
180
-
181
- describe("calculateScrollPositionForPage", () => {
182
- it("should calculate position for page", () => {
183
- const position = calculateScrollPositionForPage({
184
- page: 3,
185
- pageSize: 10,
186
- alignment: "start",
187
- containerSize: 300,
188
- estimatedItemSize: 50,
189
- orientation: "vertical",
190
- measuredSizes: new Map(),
191
- totalItems: 100,
192
- });
193
-
194
- // Page 3, item 20 (0-indexed), position 1000
195
- expect(position).toBe(1000);
196
- });
197
-
198
- it("should handle page 1 (first page)", () => {
199
- const position = calculateScrollPositionForPage({
200
- page: 1,
201
- pageSize: 10,
202
- alignment: "start",
203
- containerSize: 300,
204
- estimatedItemSize: 50,
205
- orientation: "vertical",
206
- measuredSizes: new Map(),
207
- totalItems: 100,
208
- });
209
-
210
- expect(position).toBe(0);
211
- });
212
- });
213
-
214
- describe("calculateScrollbarMetrics", () => {
215
- it("should calculate scrollbar metrics", () => {
216
- const metrics = calculateScrollbarMetrics({
217
- containerSize: 300,
218
- totalVirtualSize: 1500,
219
- scrollPosition: 300,
220
- orientation: "vertical",
221
- });
222
-
223
- expect(metrics.trackSize).toBe(300);
224
- expect(metrics.thumbSize).toBeGreaterThan(0);
225
- expect(metrics.thumbPosition).toBeGreaterThan(0);
226
- expect(metrics.scrollRatio).toBe(0.2); // 300/1500
227
- });
228
-
229
- it("should handle case where content fits in container", () => {
230
- const metrics = calculateScrollbarMetrics({
231
- containerSize: 500,
232
- totalVirtualSize: 300,
233
- scrollPosition: 0,
234
- orientation: "vertical",
235
- });
236
-
237
- expect(metrics.thumbSize).toBe(500); // Full size
238
- expect(metrics.thumbPosition).toBe(0);
239
- expect(metrics.scrollRatio).toBe(0);
240
- });
241
-
242
- it("should handle horizontal scrollbar", () => {
243
- const metrics = calculateScrollbarMetrics({
244
- containerSize: 400,
245
- totalVirtualSize: 1200,
246
- scrollPosition: 200,
247
- orientation: "horizontal",
248
- });
249
-
250
- expect(metrics.trackSize).toBe(400);
251
- expect(metrics.scrollRatio).toBeCloseTo(0.167, 2); // 200/1200
252
- });
253
- });
254
-
255
- // Removed calculateViewportInfo tests - viewport.ts now uses virtualManager directly
256
-
257
- describe("calculateInitialRangeSize", () => {
258
- it("should calculate initial range size", () => {
259
- const size = calculateInitialRangeSize({
260
- containerSize: 300,
261
- estimatedItemSize: 50,
262
- viewportMultiplier: 2,
263
- minItems: 5,
264
- maxItems: 50,
265
- });
266
-
267
- // Container fits 6 items (300/50), * 2 = 12 items
268
- expect(size).toBe(12);
269
- });
270
-
271
- it("should respect min items constraint", () => {
272
- const size = calculateInitialRangeSize({
273
- containerSize: 100,
274
- estimatedItemSize: 50,
275
- viewportMultiplier: 1,
276
- minItems: 10,
277
- maxItems: 50,
278
- });
279
-
280
- expect(size).toBe(10); // Min constraint
281
- });
282
-
283
- it("should respect max items constraint", () => {
284
- const size = calculateInitialRangeSize({
285
- containerSize: 1000,
286
- estimatedItemSize: 10,
287
- viewportMultiplier: 2,
288
- minItems: 5,
289
- maxItems: 50,
290
- });
291
-
292
- expect(size).toBe(50); // Max constraint
293
- });
294
- });
295
-
296
- describe("calculateMissingRanges", () => {
297
- it("should find missing ranges", () => {
298
- const loadedRanges = new Set([0, 1, 2, 5, 6, 7]);
299
- const requiredRange: ItemRange = { start: 0, end: 10 };
300
-
301
- const missing = calculateMissingRanges(requiredRange, loadedRanges);
302
-
303
- expect(missing).toEqual([
304
- { start: 3, end: 4 },
305
- { start: 8, end: 10 },
306
- ]);
307
- });
308
-
309
- it("should handle no missing ranges", () => {
310
- const loadedRanges = new Set([0, 1, 2, 3, 4, 5]);
311
- const requiredRange: ItemRange = { start: 0, end: 5 };
312
-
313
- const missing = calculateMissingRanges(requiredRange, loadedRanges);
314
-
315
- expect(missing).toEqual([]);
316
- });
317
-
318
- it("should handle completely missing range", () => {
319
- const loadedRanges = new Set<number>();
320
- const requiredRange: ItemRange = { start: 10, end: 15 };
321
-
322
- const missing = calculateMissingRanges(requiredRange, loadedRanges);
323
-
324
- expect(missing).toEqual([{ start: 10, end: 15 }]);
325
- });
326
- });
327
-
328
- describe("calculateBufferRanges", () => {
329
- it("should calculate buffer ranges", () => {
330
- const visibleRange: ItemRange = { start: 10, end: 20 };
331
- const bufferSize = 5;
332
- const totalItems = 100;
333
-
334
- const bufferRanges = calculateBufferRanges(
335
- visibleRange,
336
- bufferSize,
337
- totalItems
338
- );
339
-
340
- expect(bufferRanges.before).toEqual({ start: 5, end: 9 });
341
- expect(bufferRanges.after).toEqual({ start: 21, end: 25 });
342
- });
343
-
344
- it("should handle buffer at beginning", () => {
345
- const visibleRange: ItemRange = { start: 0, end: 10 };
346
- const bufferSize = 5;
347
- const totalItems = 100;
348
-
349
- const bufferRanges = calculateBufferRanges(
350
- visibleRange,
351
- bufferSize,
352
- totalItems
353
- );
354
-
355
- expect(bufferRanges.before).toEqual({ start: 0, end: -1 }); // Empty range
356
- expect(bufferRanges.after).toEqual({ start: 11, end: 15 });
357
- });
358
-
359
- it("should handle buffer at end", () => {
360
- const visibleRange: ItemRange = { start: 90, end: 99 };
361
- const bufferSize = 5;
362
- const totalItems = 100;
363
-
364
- const bufferRanges = calculateBufferRanges(
365
- visibleRange,
366
- bufferSize,
367
- totalItems
368
- );
369
-
370
- expect(bufferRanges.before).toEqual({ start: 85, end: 89 });
371
- expect(bufferRanges.after).toEqual({ start: 100, end: 99 }); // Empty range
372
- });
373
- });
374
-
375
- describe("applyBoundaryResistance", () => {
376
- it("should apply resistance at beginning", () => {
377
- const result = applyBoundaryResistance({
378
- requestedPosition: -100,
379
- currentPosition: 0,
380
- maxPosition: 1000,
381
- resistance: 0.5,
382
- });
383
-
384
- expect(result).toBeGreaterThan(-100);
385
- expect(result).toBeLessThan(0);
386
- });
387
-
388
- it("should apply resistance at end", () => {
389
- const result = applyBoundaryResistance({
390
- requestedPosition: 1100,
391
- currentPosition: 1000,
392
- maxPosition: 1000,
393
- resistance: 0.5,
394
- });
395
-
396
- expect(result).toBeLessThan(1100);
397
- expect(result).toBeGreaterThan(1000);
398
- });
399
-
400
- it("should not apply resistance within bounds", () => {
401
- const result = applyBoundaryResistance({
402
- requestedPosition: 500,
403
- currentPosition: 400,
404
- maxPosition: 1000,
405
- resistance: 0.5,
406
- });
407
-
408
- expect(result).toBe(500);
409
- });
410
-
411
- it("should handle zero resistance", () => {
412
- const result = applyBoundaryResistance({
413
- requestedPosition: -100,
414
- currentPosition: 0,
415
- maxPosition: 1000,
416
- resistance: 0,
417
- });
418
-
419
- expect(result).toBe(0); // Clamped to boundary
420
- });
421
-
422
- it("should handle full resistance", () => {
423
- const result = applyBoundaryResistance({
424
- requestedPosition: -100,
425
- currentPosition: 0,
426
- maxPosition: 1000,
427
- resistance: 1,
428
- });
429
-
430
- expect(result).toBe(0); // No movement beyond boundary
431
- });
432
- });
433
- });