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,335 @@
1
+ // src/core/viewport/features/placeholders.ts
2
+
3
+ /**
4
+ * Placeholder Feature - Smart placeholder generation
5
+ * Analyzes first loaded data to generate realistic masked placeholders
6
+ * Shows placeholders while data is loading
7
+ */
8
+
9
+ import type { ViewportContext } from "../types";
10
+ import type { CollectionComponent } from "./collection";
11
+ import { VIEWPORT_CONSTANTS } from "../constants";
12
+
13
+ /**
14
+ * Configuration for placeholder feature
15
+ */
16
+ export interface PlaceholderConfig {
17
+ enabled?: boolean;
18
+ analyzeFirstLoad?: boolean;
19
+ maskCharacter?: string;
20
+ randomLengthVariance?: boolean;
21
+ }
22
+
23
+ /**
24
+ * Field structure for placeholder generation
25
+ */
26
+ interface FieldStructure {
27
+ minLength: number;
28
+ maxLength: number;
29
+ avgLength: number;
30
+ }
31
+
32
+ export interface PlaceholderComponent {
33
+ placeholders: {
34
+ analyzeDataStructure: (items: any[]) => void;
35
+ hasAnalyzedStructure: () => boolean;
36
+ generatePlaceholderItem: (index: number) => any;
37
+ generatePlaceholderItems: (range: { start: number; end: number }) => any[];
38
+ showPlaceholders: (range: { start: number; end: number }) => void;
39
+ isPlaceholder: (item: any) => boolean;
40
+ replacePlaceholders: (items: any[], offset: number) => void;
41
+ clear: () => void;
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Adds placeholder functionality to viewport component
47
+ */
48
+ export function withPlaceholders(config: PlaceholderConfig = {}) {
49
+ return <T extends ViewportContext & CollectionComponent>(
50
+ component: T
51
+ ): T & PlaceholderComponent => {
52
+ const {
53
+ enabled = true,
54
+ analyzeFirstLoad = true,
55
+ maskCharacter = VIEWPORT_CONSTANTS.PLACEHOLDER.MASK_CHARACTER,
56
+ randomLengthVariance = VIEWPORT_CONSTANTS.PLACEHOLDER
57
+ .RANDOM_LENGTH_VARIANCE,
58
+ } = config;
59
+
60
+ // State
61
+ let fieldStructures: Map<string, FieldStructure> | null = null;
62
+ let hasAnalyzed = false;
63
+ let placeholderIdCounter = 0;
64
+
65
+ /**
66
+ * Analyze data structure from first loaded items
67
+ */
68
+ const analyzeDataStructure = (items: any[]): void => {
69
+ if (!enabled || hasAnalyzed || !items.length) {
70
+ return;
71
+ }
72
+
73
+ // console.log(
74
+ // `🔍 [PLACEHOLDERS] Analyzing data structure from ${items.length} items`
75
+ // );
76
+
77
+ const structures = new Map<string, FieldStructure>();
78
+ const sampleSize = Math.min(
79
+ items.length,
80
+ VIEWPORT_CONSTANTS.PLACEHOLDER.MAX_SAMPLE_SIZE
81
+ );
82
+
83
+ // Analyze each field across all sample items
84
+ const fieldStats = new Map<string, number[]>();
85
+
86
+ for (let i = 0; i < sampleSize; i++) {
87
+ const item = items[i];
88
+ if (!item || typeof item !== "object") continue;
89
+
90
+ Object.keys(item).forEach((field) => {
91
+ // Skip internal fields
92
+ if (
93
+ field.startsWith("_") ||
94
+ field === VIEWPORT_CONSTANTS.PLACEHOLDER.PLACEHOLDER_FLAG
95
+ ) {
96
+ return;
97
+ }
98
+
99
+ const value = String(item[field] || "");
100
+ const length = value.length;
101
+
102
+ if (!fieldStats.has(field)) {
103
+ fieldStats.set(field, []);
104
+ }
105
+ fieldStats.get(field)!.push(length);
106
+ });
107
+ }
108
+
109
+ // Calculate statistics for each field
110
+ fieldStats.forEach((lengths, field) => {
111
+ if (lengths.length === 0) return;
112
+
113
+ const minLength = Math.min(...lengths);
114
+ const maxLength = Math.max(...lengths);
115
+ const avgLength = Math.round(
116
+ lengths.reduce((sum, len) => sum + len, 0) / lengths.length
117
+ );
118
+
119
+ structures.set(field, {
120
+ minLength,
121
+ maxLength,
122
+ avgLength,
123
+ });
124
+ });
125
+
126
+ fieldStructures = structures;
127
+ hasAnalyzed = true;
128
+
129
+ // console.log(
130
+ // `✅ [PLACEHOLDERS] Structure analyzed:`,
131
+ // Object.fromEntries(structures)
132
+ // );
133
+
134
+ // Emit event
135
+ component.emit?.("viewport:placeholders-structure-analyzed", {
136
+ structure: Object.fromEntries(structures),
137
+ });
138
+ };
139
+
140
+ /**
141
+ * Generate a single placeholder item
142
+ */
143
+ const generatePlaceholderItem = (index: number): any => {
144
+ const placeholder: Record<string, any> = {
145
+ id: `placeholder-${placeholderIdCounter++}`,
146
+ [VIEWPORT_CONSTANTS.PLACEHOLDER.PLACEHOLDER_FLAG]: true,
147
+ _index: index,
148
+ };
149
+
150
+ if (!fieldStructures || fieldStructures.size === 0) {
151
+ // No structure analyzed yet - return basic placeholder
152
+ placeholder.label = maskCharacter.repeat(10);
153
+ return placeholder;
154
+ }
155
+
156
+ // Generate fields based on analyzed structure
157
+ fieldStructures.forEach((structure, field) => {
158
+ let length: number;
159
+
160
+ if (
161
+ randomLengthVariance &&
162
+ structure.minLength !== structure.maxLength
163
+ ) {
164
+ // Random length within range
165
+ length = Math.floor(
166
+ Math.random() * (structure.maxLength - structure.minLength + 1) +
167
+ structure.minLength
168
+ );
169
+ } else {
170
+ // Use average length
171
+ length = structure.avgLength;
172
+ }
173
+
174
+ // Apply some variation to make it look more natural
175
+ if (randomLengthVariance && Math.random() < 0.3) {
176
+ length = Math.max(1, length + Math.floor(Math.random() * 3) - 1);
177
+ }
178
+
179
+ placeholder[field] = maskCharacter.repeat(length);
180
+ });
181
+
182
+ return placeholder;
183
+ };
184
+
185
+ /**
186
+ * Generate multiple placeholder items
187
+ */
188
+ const generatePlaceholderItems = (range: {
189
+ start: number;
190
+ end: number;
191
+ }): any[] => {
192
+ const items: any[] = [];
193
+ for (let i = range.start; i <= range.end; i++) {
194
+ items.push(generatePlaceholderItem(i));
195
+ }
196
+ return items;
197
+ };
198
+
199
+ /**
200
+ * Show placeholders for a range
201
+ */
202
+ const showPlaceholders = (range: { start: number; end: number }): void => {
203
+ if (!enabled) return;
204
+
205
+ const placeholders = generatePlaceholderItems(range);
206
+
207
+ // Update items array
208
+ if (component.items) {
209
+ for (let i = 0; i < placeholders.length; i++) {
210
+ const index = range.start + i;
211
+ if (!component.items[index]) {
212
+ component.items[index] = placeholders[i];
213
+ }
214
+ }
215
+ }
216
+
217
+ console.log(
218
+ `🔄 [PLACEHOLDERS] Showing ${placeholders.length} placeholders for range ${range.start}-${range.end}`
219
+ );
220
+
221
+ // Emit event
222
+ component.emit?.("viewport:placeholders-shown", {
223
+ range,
224
+ count: placeholders.length,
225
+ });
226
+ };
227
+
228
+ /**
229
+ * Check if an item is a placeholder
230
+ */
231
+ const isPlaceholder = (item: any): boolean => {
232
+ return (
233
+ item &&
234
+ typeof item === "object" &&
235
+ item[VIEWPORT_CONSTANTS.PLACEHOLDER.PLACEHOLDER_FLAG] === true
236
+ );
237
+ };
238
+
239
+ /**
240
+ * Replace placeholders with real data
241
+ */
242
+ const replacePlaceholders = (items: any[], offset: number): void => {
243
+ if (!component.items) return;
244
+
245
+ let replacedCount = 0;
246
+
247
+ for (let i = 0; i < items.length; i++) {
248
+ const index = offset + i;
249
+ const currentItem = component.items[index];
250
+
251
+ if (isPlaceholder(currentItem)) {
252
+ component.items[index] = items[i];
253
+ replacedCount++;
254
+ }
255
+ }
256
+
257
+ if (replacedCount > 0) {
258
+ console.log(
259
+ `✨ [PLACEHOLDERS] Replaced ${replacedCount} placeholders at offset ${offset}`
260
+ );
261
+
262
+ // Emit event
263
+ component.emit?.("viewport:placeholders-replaced", {
264
+ offset,
265
+ count: replacedCount,
266
+ });
267
+ }
268
+ };
269
+
270
+ /**
271
+ * Clear all placeholder state
272
+ */
273
+ const clear = (): void => {
274
+ fieldStructures = null;
275
+ hasAnalyzed = false;
276
+ placeholderIdCounter = 0;
277
+ };
278
+
279
+ // Initialize function
280
+ const initialize = () => {
281
+ if (!enabled) return;
282
+
283
+ // Listen for first data load to analyze structure
284
+ if (analyzeFirstLoad) {
285
+ const handleRangeLoaded = (data: any) => {
286
+ if (!hasAnalyzed && data.items && data.items.length > 0) {
287
+ analyzeDataStructure(data.items);
288
+ }
289
+
290
+ // Replace any placeholders with real data
291
+ replacePlaceholders(data.items, data.offset);
292
+ };
293
+
294
+ component.on?.("viewport:range-loaded", handleRangeLoaded);
295
+ }
296
+
297
+ // Show initial placeholders if configured
298
+ const totalItems = component.collection?.getTotalItems() || 0;
299
+ if (totalItems > 0) {
300
+ const initialRange = {
301
+ start: 0,
302
+ end: Math.min(
303
+ VIEWPORT_CONSTANTS.PLACEHOLDER.MAX_SAMPLE_SIZE - 1,
304
+ totalItems - 1
305
+ ),
306
+ };
307
+ showPlaceholders(initialRange);
308
+ }
309
+ };
310
+
311
+ // Cleanup function
312
+ const destroy = () => {
313
+ clear();
314
+ };
315
+
316
+ // Store functions for viewport to call
317
+ (component as any)._placeholdersInitialize = initialize;
318
+ (component as any)._placeholdersDestroy = destroy;
319
+
320
+ // Return enhanced component
321
+ return {
322
+ ...component,
323
+ placeholders: {
324
+ analyzeDataStructure,
325
+ hasAnalyzedStructure: () => hasAnalyzed,
326
+ generatePlaceholderItem,
327
+ generatePlaceholderItems,
328
+ showPlaceholders,
329
+ isPlaceholder,
330
+ replacePlaceholders,
331
+ clear,
332
+ },
333
+ };
334
+ };
335
+ }