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,444 @@
1
+ // src/components/vlist/features/selection.ts
2
+
3
+ import type { VListConfig, VListComponent, VListItem } from "../types";
4
+ import { VLIST_CLASSES } from "../constants";
5
+ import { PREFIX, addClass, removeClass } from "mtrl";
6
+
7
+ /**
8
+ * Selection state interface
9
+ */
10
+ interface SelectionState {
11
+ selectedIds: Set<string | number>;
12
+ mode: "none" | "single" | "multiple";
13
+ lastSelectedIndex?: number;
14
+ }
15
+
16
+ /**
17
+ * Adds selection management capabilities to VList component
18
+ * Works with viewport's virtual scrolling architecture
19
+ *
20
+ * @param config - VList configuration with selection options
21
+ * @returns Function that enhances a component with selection management
22
+ */
23
+ export const withSelection = <T extends VListItem = VListItem>(
24
+ config: VListConfig<T>
25
+ ) => {
26
+ return (component: VListComponent<T>): VListComponent<T> => {
27
+ // Skip if selection is not enabled
28
+ if (!config.selection?.enabled || config.selection?.mode === "none") {
29
+ // console.log("🎯 [Selection] Skipped - not enabled or mode is none");
30
+ return component;
31
+ }
32
+
33
+ // console.log("🎯 [Selection] Initializing selection feature", {
34
+ // enabled: config.selection?.enabled,
35
+ // mode: config.selection?.mode,
36
+ // });
37
+
38
+ // Initialize selection state
39
+ const state: SelectionState = {
40
+ selectedIds: new Set(),
41
+ mode: config.selection?.mode || "single",
42
+ lastSelectedIndex: undefined,
43
+ };
44
+
45
+ // Get configuration options
46
+ const requireModifiers = config.selection?.requireModifiers ?? false;
47
+
48
+ // Add BEM modifier class to container element
49
+ const addContainerModifier = () => {
50
+ if (component.element) {
51
+ // Add selection mode modifier class following BEM convention
52
+ addClass(component.element, `vlist--selection`);
53
+ // Also add specific mode modifier
54
+ addClass(component.element, `vlist--selection-${state.mode}`);
55
+ }
56
+ };
57
+
58
+ // Defer initialization of pre-selected items until after setup
59
+ const initializePreselectedItems = () => {
60
+ if (config.selection?.selectedIndices && component.getItems) {
61
+ config.selection.selectedIndices.forEach((index) => {
62
+ const items = component.getItems();
63
+ const item = items?.[index];
64
+ if (item && (item as any).id !== undefined) {
65
+ state.selectedIds.add((item as any).id);
66
+ }
67
+ });
68
+ }
69
+ };
70
+
71
+ /**
72
+ * Apply selection class to an element
73
+ */
74
+ const applySelectionClass = (element: HTMLElement, isSelected: boolean) => {
75
+ if (isSelected) {
76
+ addClass(element, VLIST_CLASSES.SELECTED);
77
+ } else {
78
+ removeClass(element, VLIST_CLASSES.SELECTED);
79
+ }
80
+ };
81
+
82
+ /**
83
+ * Get item ID from element or item data
84
+ */
85
+ const getItemId = (item: any): string | number | undefined => {
86
+ if (item?.id !== undefined) return item.id;
87
+ if (typeof item === "string" || typeof item === "number") return item;
88
+ return undefined;
89
+ };
90
+
91
+ /**
92
+ * Handle item click for selection
93
+ */
94
+ const handleItemClick = (e: MouseEvent) => {
95
+ // console.log("🎯 [Selection] Click detected on:", e.target);
96
+
97
+ // Find the clicked viewport item element (wrapper)
98
+ const viewportItem = (e.target as HTMLElement).closest(
99
+ `.${PREFIX}-viewport-item[data-index]`
100
+ ) as HTMLElement;
101
+ if (!viewportItem) {
102
+ console.log("🎯 [Selection] No viewport item found for click");
103
+ return;
104
+ }
105
+
106
+ const index = parseInt(viewportItem.dataset.index || "-1");
107
+ // console.log(`🎯 [Selection] Clicked item index: ${index}`);
108
+ if (index < 0) return;
109
+
110
+ // Get the item data
111
+ const enhancedComponent = component as any;
112
+ const items = enhancedComponent.getItems?.();
113
+ const item = items?.[index];
114
+ if (!item) return;
115
+
116
+ const itemId = getItemId(item);
117
+ if (itemId === undefined) return;
118
+
119
+ // Handle selection based on mode
120
+ const wasSelected = state.selectedIds.has(itemId);
121
+
122
+ // console.log("🎯 [Selection] Click detected:", {
123
+ // index,
124
+ // itemId,
125
+ // wasSelected,
126
+ // mode: state.mode,
127
+ // shiftKey: e.shiftKey,
128
+ // ctrlKey: e.ctrlKey,
129
+ // metaKey: e.metaKey,
130
+ // lastSelectedIndex: state.lastSelectedIndex,
131
+ // });
132
+
133
+ if (state.mode === "single") {
134
+ // Clear previous selection
135
+ state.selectedIds.clear();
136
+
137
+ // Toggle selection
138
+ if (!wasSelected) {
139
+ state.selectedIds.add(itemId);
140
+ state.lastSelectedIndex = index;
141
+ } else {
142
+ state.lastSelectedIndex = undefined;
143
+ }
144
+ } else if (state.mode === "multiple") {
145
+ // Handle multi-select with keyboard modifiers
146
+ if (e.shiftKey && state.lastSelectedIndex !== undefined) {
147
+ // Range selection
148
+ const start = Math.min(state.lastSelectedIndex, index);
149
+ const end = Math.max(state.lastSelectedIndex, index);
150
+
151
+ if (!e.ctrlKey && !e.metaKey) {
152
+ // Clear existing selection if not holding ctrl/cmd
153
+ state.selectedIds.clear();
154
+ }
155
+
156
+ for (let i = start; i <= end; i++) {
157
+ const rangeItem = items?.[i];
158
+ const rangeItemId = getItemId(rangeItem);
159
+ if (rangeItemId !== undefined) {
160
+ state.selectedIds.add(rangeItemId);
161
+ }
162
+ }
163
+ } else if (e.ctrlKey || e.metaKey) {
164
+ // Toggle individual selection with Ctrl/Cmd
165
+ if (wasSelected) {
166
+ state.selectedIds.delete(itemId);
167
+ } else {
168
+ state.selectedIds.add(itemId);
169
+ }
170
+ state.lastSelectedIndex = index;
171
+ } else {
172
+ // Single click without modifiers
173
+ // console.log("🎯 [Selection] Single click without modifiers:", {
174
+ // requireModifiers,
175
+ // wasSelected,
176
+ // willToggle: !requireModifiers,
177
+ // });
178
+
179
+ if (requireModifiers) {
180
+ // If modifiers are required, single click selects only this item
181
+ state.selectedIds.clear();
182
+ state.selectedIds.add(itemId);
183
+ } else {
184
+ // If modifiers are NOT required, single click toggles selection
185
+ if (wasSelected) {
186
+ state.selectedIds.delete(itemId);
187
+ } else {
188
+ state.selectedIds.add(itemId);
189
+ }
190
+ }
191
+ state.lastSelectedIndex = index;
192
+ }
193
+ }
194
+
195
+ // Update visible elements
196
+ updateVisibleElements();
197
+
198
+ // Emit selection change event
199
+ const selectedItems =
200
+ items?.filter((item: any) => {
201
+ const id = getItemId(item);
202
+ return id !== undefined && state.selectedIds.has(id);
203
+ }) || [];
204
+
205
+ const selectedIndices =
206
+ items?.reduce((acc: number[], item: any, idx: number) => {
207
+ const id = getItemId(item);
208
+ if (id !== undefined && state.selectedIds.has(id)) {
209
+ acc.push(idx);
210
+ }
211
+ return acc;
212
+ }, [] as number[]) || [];
213
+
214
+ component.emit?.("selection:change", {
215
+ selectedItems,
216
+ selectedIndices,
217
+ });
218
+
219
+ // Call the selection change callback if provided
220
+ if (config.selection?.onSelectionChange) {
221
+ config.selection.onSelectionChange(selectedItems, selectedIndices);
222
+ }
223
+
224
+ // Emit individual item selection event
225
+ component.emit?.("item:selection:change", {
226
+ item,
227
+ index,
228
+ isSelected: state.selectedIds.has(itemId),
229
+ });
230
+ };
231
+
232
+ /**
233
+ * Update selection state for visible elements
234
+ */
235
+ const updateVisibleElements = () => {
236
+ const container = component.element?.querySelector(
237
+ `.${PREFIX}-viewport-items`
238
+ );
239
+ if (!container) {
240
+ // console.warn("🎯 [Selection] No viewport items container found");
241
+ return;
242
+ }
243
+
244
+ const viewportItems = container.querySelectorAll(
245
+ `.${PREFIX}-viewport-item[data-index]`
246
+ );
247
+ // console.log(
248
+ // `🎯 [Selection] Updating ${viewportItems.length} visible elements`
249
+ // );
250
+
251
+ const enhancedComponent = component as any;
252
+ const items = enhancedComponent.getItems?.();
253
+
254
+ viewportItems.forEach((viewportItem) => {
255
+ const index = parseInt(
256
+ (viewportItem as HTMLElement).dataset.index || "-1"
257
+ );
258
+ if (index < 0) return;
259
+
260
+ const item = items?.[index];
261
+ if (!item) return;
262
+
263
+ const itemId = getItemId(item);
264
+ if (itemId === undefined) return;
265
+
266
+ const isSelected = state.selectedIds.has(itemId);
267
+
268
+ // Apply selection class to the viewport item itself
269
+ // The new layout system doesn't have a separate inner item
270
+ applySelectionClass(viewportItem as HTMLElement, isSelected);
271
+ });
272
+ };
273
+
274
+ // Setup listeners after component is fully initialized
275
+ // Since selection is applied last, we can just use a timeout
276
+ setTimeout(() => {
277
+ // console.log("🎯 [Selection] Setting up listeners after initialization");
278
+
279
+ // Add BEM modifier classes to the container
280
+ addContainerModifier();
281
+
282
+ setupSelectionListeners();
283
+ }, 0);
284
+
285
+ function setupSelectionListeners() {
286
+ // Wait for viewport to be ready
287
+ setTimeout(() => {
288
+ // Initialize pre-selected items now that component is ready
289
+ initializePreselectedItems();
290
+
291
+ // Listen for render complete to update selection state
292
+ // Using type assertion since viewport:rendered is not in ListEvents type
293
+ (component as any).on?.("viewport:rendered", () => {
294
+ updateVisibleElements();
295
+ });
296
+
297
+ // Add click listener to the viewport element
298
+ if (component.element) {
299
+ // Use capture phase to ensure we get the event
300
+ component.element.addEventListener("click", handleItemClick, true);
301
+ // console.log(
302
+ // "🎯 [Selection] Click handler attached to element (capture phase)"
303
+ // );
304
+
305
+ // Test if handler works
306
+ // setTimeout(() => {
307
+ // const testItem = component.element?.querySelector(
308
+ // `.${PREFIX}-viewport-item`
309
+ // );
310
+ // // console.log("🎯 [Selection] Test item found:", !!testItem);
311
+ // }, 500);
312
+ }
313
+ }, 100);
314
+ }
315
+
316
+ // Clean up on destroy
317
+ const originalDestroy = component.destroy;
318
+ component.destroy = () => {
319
+ if (component.element) {
320
+ component.element.removeEventListener("click", handleItemClick, true);
321
+ }
322
+ originalDestroy?.();
323
+ };
324
+
325
+ // Create the enhanced component
326
+ const enhancedComponent = {
327
+ ...component,
328
+
329
+ // Selection API methods
330
+ selectItems(indices: number[]) {
331
+ // Use type assertion to access getItems which is added by API feature
332
+ const getItemsFn = (this as any).getItems;
333
+ if (!getItemsFn) {
334
+ console.warn("🎯 [Selection] getItems not available yet");
335
+ return;
336
+ }
337
+ const items = getItemsFn();
338
+
339
+ if (state.mode === "single" && indices.length > 1) {
340
+ // In single mode, only select the first item
341
+ indices = [indices[0]];
342
+ }
343
+
344
+ indices.forEach((index) => {
345
+ const item = items?.[index];
346
+ const itemId = getItemId(item);
347
+ if (itemId !== undefined) {
348
+ state.selectedIds.add(itemId);
349
+ }
350
+ });
351
+
352
+ // Update lastSelectedIndex for shift+click range selection
353
+ if (indices.length > 0) {
354
+ state.lastSelectedIndex = indices[indices.length - 1];
355
+ }
356
+
357
+ updateVisibleElements();
358
+ (this as any).emit?.("selection:change", {
359
+ selectedItems: (this as any).getSelectedItems(),
360
+ selectedIndices: (this as any).getSelectedIndices(),
361
+ });
362
+ },
363
+
364
+ deselectItems(indices: number[]) {
365
+ // Use type assertion to access getItems which is added by API feature
366
+ const getItemsFn = (this as any).getItems;
367
+ if (!getItemsFn) {
368
+ console.warn("🎯 [Selection] getItems not available yet");
369
+ return;
370
+ }
371
+ const items = getItemsFn();
372
+
373
+ indices.forEach((index) => {
374
+ const item = items?.[index];
375
+ const itemId = getItemId(item);
376
+ if (itemId !== undefined) {
377
+ state.selectedIds.delete(itemId);
378
+ }
379
+ });
380
+
381
+ updateVisibleElements();
382
+ (this as any).emit?.("selection:change", {
383
+ selectedItems: (this as any).getSelectedItems(),
384
+ selectedIndices: (this as any).getSelectedIndices(),
385
+ });
386
+ },
387
+
388
+ clearSelection() {
389
+ state.selectedIds.clear();
390
+ state.lastSelectedIndex = undefined;
391
+ updateVisibleElements();
392
+ (this as any).emit?.("selection:change", {
393
+ selectedItems: [],
394
+ selectedIndices: [],
395
+ });
396
+ },
397
+
398
+ getSelectedItems(): T[] {
399
+ // Use type assertion to access getItems which is added by API feature
400
+ const getItemsFn = (this as any).getItems;
401
+ if (!getItemsFn) {
402
+ return [];
403
+ }
404
+ const items = getItemsFn() || [];
405
+ return items.filter((item: any) => {
406
+ const id = getItemId(item);
407
+ return id !== undefined && state.selectedIds.has(id);
408
+ });
409
+ },
410
+
411
+ getSelectedIndices(): number[] {
412
+ // Use type assertion to access getItems which is added by API feature
413
+ const getItemsFn = (this as any).getItems;
414
+ if (!getItemsFn) {
415
+ return [];
416
+ }
417
+ const items = getItemsFn() || [];
418
+ return items.reduce((acc: number[], item: any, index: number) => {
419
+ const id = getItemId(item);
420
+ if (id !== undefined && state.selectedIds.has(id)) {
421
+ acc.push(index);
422
+ }
423
+ return acc;
424
+ }, [] as number[]);
425
+ },
426
+
427
+ isSelected(index: number): boolean {
428
+ // Use type assertion to access getItems which is added by API feature
429
+ const getItemsFn = (this as any).getItems;
430
+ if (!getItemsFn) {
431
+ return false;
432
+ }
433
+ const items = getItemsFn();
434
+ const item = items?.[index];
435
+ const itemId = getItemId(item);
436
+ return itemId !== undefined && state.selectedIds.has(itemId);
437
+ },
438
+ };
439
+
440
+ return enhancedComponent;
441
+ };
442
+ };
443
+
444
+ export default withSelection;
@@ -0,0 +1,65 @@
1
+ // src/components/vlist/features/viewport.ts
2
+
3
+ /**
4
+ * Viewport feature for VList
5
+ * Integrates the core viewport functionality with VList component
6
+ */
7
+
8
+ import type { VListConfig, VListItem } from "../types";
9
+ import { createViewport } from "../../../core/viewport";
10
+
11
+ /**
12
+ * Adds viewport functionality to VList
13
+ */
14
+ export const withViewport = <T extends VListItem = VListItem>(
15
+ config: VListConfig<T>
16
+ ) => {
17
+ return (component: any) => {
18
+ // console.log("📋 [VList] Applying viewport feature", {
19
+ // hasElement: !!component.element,
20
+ // hasItems: !!config.items,
21
+ // itemCount: config.items?.length || 0,
22
+ // });
23
+
24
+ // Set initial items if provided
25
+ if (config.items) {
26
+ component.items = config.items;
27
+ component.totalItems = config.items.length;
28
+ }
29
+
30
+ // Set template if provided
31
+ if (config.template) {
32
+ component.template = config.template;
33
+ }
34
+
35
+ // Ensure the element has both vlist and viewport classes
36
+ const viewportConfig = {
37
+ ...config,
38
+ className: "mtrl-viewport", // This will be added by viewport base feature
39
+ };
40
+
41
+ // Pass VList config directly to viewport
42
+ const viewportEnhanced = createViewport(viewportConfig as any)(component);
43
+
44
+ // Handle parent element if provided
45
+ if (config.parent || config.container) {
46
+ const container = config.parent || config.container;
47
+ const element =
48
+ typeof container === "string"
49
+ ? document.querySelector(container)
50
+ : container;
51
+
52
+ if (element && viewportEnhanced.element) {
53
+ element.appendChild(viewportEnhanced.element);
54
+
55
+ // Ensure viewport is initialized after DOM attachment
56
+ if (viewportEnhanced.viewport && viewportEnhanced.viewport.initialize) {
57
+ // console.log("📋 [VList] Initializing viewport after DOM attachment");
58
+ viewportEnhanced.viewport.initialize();
59
+ }
60
+ }
61
+ }
62
+
63
+ return viewportEnhanced;
64
+ };
65
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * VList Component - Virtual List with direct viewport integration
3
+ *
4
+ * A high-performance virtual list component that uses the viewport
5
+ * feature directly without the list-manager layer.
6
+ */
7
+
8
+ export { createVList } from "./vlist";
9
+ export type {
10
+ VListConfig,
11
+ VListComponent,
12
+ VListItem,
13
+ VListAPI,
14
+ VListState,
15
+ VListEvents,
16
+ } from "./types";