mtrl-addons 0.2.2 → 0.2.3

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 (128) hide show
  1. package/{src/components/index.ts → dist/components/index.d.ts} +0 -2
  2. package/dist/components/vlist/config.d.ts +86 -0
  3. package/{src/components/vlist/constants.ts → dist/components/vlist/constants.d.ts} +10 -11
  4. package/dist/components/vlist/features/api.d.ts +7 -0
  5. package/{src/components/vlist/features/index.ts → dist/components/vlist/features/index.d.ts} +0 -2
  6. package/dist/components/vlist/features/selection.d.ts +6 -0
  7. package/dist/components/vlist/features/viewport.d.ts +9 -0
  8. package/dist/components/vlist/features.d.ts +31 -0
  9. package/{src/components/vlist/index.ts → dist/components/vlist/index.d.ts} +1 -10
  10. package/dist/components/vlist/types.d.ts +596 -0
  11. package/dist/components/vlist/vlist.d.ts +29 -0
  12. package/dist/core/compose/features/gestures/index.d.ts +86 -0
  13. package/dist/core/compose/features/gestures/longpress.d.ts +85 -0
  14. package/dist/core/compose/features/gestures/pan.d.ts +108 -0
  15. package/dist/core/compose/features/gestures/pinch.d.ts +111 -0
  16. package/dist/core/compose/features/gestures/rotate.d.ts +111 -0
  17. package/dist/core/compose/features/gestures/swipe.d.ts +149 -0
  18. package/dist/core/compose/features/gestures/tap.d.ts +79 -0
  19. package/{src/core/compose/features/index.ts → dist/core/compose/features/index.d.ts} +1 -2
  20. package/{src/core/compose/index.ts → dist/core/compose/index.d.ts} +2 -11
  21. package/{src/core/gestures/index.ts → dist/core/gestures/index.d.ts} +1 -20
  22. package/dist/core/gestures/longpress.d.ts +23 -0
  23. package/dist/core/gestures/manager.d.ts +14 -0
  24. package/dist/core/gestures/pan.d.ts +12 -0
  25. package/dist/core/gestures/pinch.d.ts +14 -0
  26. package/dist/core/gestures/rotate.d.ts +14 -0
  27. package/dist/core/gestures/swipe.d.ts +20 -0
  28. package/dist/core/gestures/tap.d.ts +12 -0
  29. package/dist/core/gestures/types.d.ts +320 -0
  30. package/dist/core/gestures/utils.d.ts +57 -0
  31. package/dist/core/index.d.ts +13 -0
  32. package/dist/core/layout/config.d.ts +33 -0
  33. package/dist/core/layout/index.d.ts +51 -0
  34. package/dist/core/layout/jsx.d.ts +65 -0
  35. package/dist/core/layout/schema.d.ts +112 -0
  36. package/dist/core/layout/types.d.ts +69 -0
  37. package/dist/core/viewport/constants.d.ts +105 -0
  38. package/dist/core/viewport/features/base.d.ts +14 -0
  39. package/dist/core/viewport/features/collection.d.ts +41 -0
  40. package/dist/core/viewport/features/events.d.ts +13 -0
  41. package/{src/core/viewport/features/index.ts → dist/core/viewport/features/index.d.ts} +0 -7
  42. package/dist/core/viewport/features/item-size.d.ts +30 -0
  43. package/dist/core/viewport/features/loading.d.ts +34 -0
  44. package/dist/core/viewport/features/momentum.d.ts +17 -0
  45. package/dist/core/viewport/features/performance.d.ts +53 -0
  46. package/dist/core/viewport/features/placeholders.d.ts +38 -0
  47. package/dist/core/viewport/features/rendering.d.ts +16 -0
  48. package/dist/core/viewport/features/scrollbar.d.ts +26 -0
  49. package/dist/core/viewport/features/scrolling.d.ts +16 -0
  50. package/dist/core/viewport/features/utils.d.ts +43 -0
  51. package/dist/core/viewport/features/virtual.d.ts +18 -0
  52. package/{src/core/viewport/index.ts → dist/core/viewport/index.d.ts} +1 -17
  53. package/dist/core/viewport/types.d.ts +96 -0
  54. package/dist/core/viewport/utils/speed-tracker.d.ts +22 -0
  55. package/dist/core/viewport/viewport.d.ts +11 -0
  56. package/{src/index.ts → dist/index.d.ts} +0 -4
  57. package/dist/index.js +5143 -0
  58. package/dist/index.mjs +5111 -0
  59. package/dist/styles.css +254 -0
  60. package/dist/styles.css.map +1 -0
  61. package/package.json +5 -1
  62. package/.cursorrules +0 -117
  63. package/AI.md +0 -39
  64. package/CLAUDE.md +0 -882
  65. package/build.js +0 -377
  66. package/scripts/analyze-orphaned-functions.ts +0 -387
  67. package/scripts/debug/vlist-selection.ts +0 -121
  68. package/src/components/vlist/config.ts +0 -323
  69. package/src/components/vlist/features/api.ts +0 -626
  70. package/src/components/vlist/features/selection.ts +0 -436
  71. package/src/components/vlist/features/viewport.ts +0 -59
  72. package/src/components/vlist/features.ts +0 -112
  73. package/src/components/vlist/types.ts +0 -723
  74. package/src/components/vlist/vlist.ts +0 -92
  75. package/src/core/compose/features/gestures/index.ts +0 -227
  76. package/src/core/compose/features/gestures/longpress.ts +0 -383
  77. package/src/core/compose/features/gestures/pan.ts +0 -424
  78. package/src/core/compose/features/gestures/pinch.ts +0 -475
  79. package/src/core/compose/features/gestures/rotate.ts +0 -485
  80. package/src/core/compose/features/gestures/swipe.ts +0 -492
  81. package/src/core/compose/features/gestures/tap.ts +0 -334
  82. package/src/core/gestures/longpress.ts +0 -68
  83. package/src/core/gestures/manager.ts +0 -418
  84. package/src/core/gestures/pan.ts +0 -48
  85. package/src/core/gestures/pinch.ts +0 -58
  86. package/src/core/gestures/rotate.ts +0 -58
  87. package/src/core/gestures/swipe.ts +0 -66
  88. package/src/core/gestures/tap.ts +0 -45
  89. package/src/core/gestures/types.ts +0 -387
  90. package/src/core/gestures/utils.ts +0 -128
  91. package/src/core/index.ts +0 -43
  92. package/src/core/layout/config.ts +0 -102
  93. package/src/core/layout/index.ts +0 -168
  94. package/src/core/layout/jsx.ts +0 -174
  95. package/src/core/layout/schema.ts +0 -1044
  96. package/src/core/layout/types.ts +0 -95
  97. package/src/core/viewport/constants.ts +0 -145
  98. package/src/core/viewport/features/base.ts +0 -73
  99. package/src/core/viewport/features/collection.ts +0 -1182
  100. package/src/core/viewport/features/events.ts +0 -130
  101. package/src/core/viewport/features/item-size.ts +0 -271
  102. package/src/core/viewport/features/loading.ts +0 -263
  103. package/src/core/viewport/features/momentum.ts +0 -269
  104. package/src/core/viewport/features/performance.ts +0 -161
  105. package/src/core/viewport/features/placeholders.ts +0 -335
  106. package/src/core/viewport/features/rendering.ts +0 -962
  107. package/src/core/viewport/features/scrollbar.ts +0 -434
  108. package/src/core/viewport/features/scrolling.ts +0 -634
  109. package/src/core/viewport/features/utils.ts +0 -94
  110. package/src/core/viewport/features/virtual.ts +0 -525
  111. package/src/core/viewport/types.ts +0 -133
  112. package/src/core/viewport/utils/speed-tracker.ts +0 -79
  113. package/src/core/viewport/viewport.ts +0 -265
  114. package/test/benchmarks/layout/advanced.test.ts +0 -656
  115. package/test/benchmarks/layout/comparison.test.ts +0 -519
  116. package/test/benchmarks/layout/performance-comparison.test.ts +0 -274
  117. package/test/benchmarks/layout/real-components.test.ts +0 -733
  118. package/test/benchmarks/layout/simple.test.ts +0 -321
  119. package/test/benchmarks/layout/stress.test.ts +0 -990
  120. package/test/collection/basic.test.ts +0 -304
  121. package/test/components/vlist-selection.test.ts +0 -240
  122. package/test/components/vlist.test.ts +0 -63
  123. package/test/core/collection/adapter.test.ts +0 -161
  124. package/test/core/collection/collection.test.ts +0 -394
  125. package/test/core/layout/layout.test.ts +0 -201
  126. package/test/utils/dom-helpers.ts +0 -275
  127. package/test/utils/performance-helpers.ts +0 -392
  128. package/tsconfig.json +0 -20
@@ -1,626 +0,0 @@
1
- /**
2
- * API feature for VList
3
- * Provides a clean public API for the VList component
4
- */
5
-
6
- import type { VListConfig, VListItem, RemoveItemOptions } from "../types";
7
-
8
- // Re-export for convenience
9
- export type { RemoveItemOptions } from "../types";
10
-
11
- export const withAPI = <T extends VListItem = VListItem>(
12
- config: VListConfig<T>,
13
- ) => {
14
- return (component: any) => {
15
- // Initialize viewport on creation
16
- setTimeout(() => {
17
- if (component.viewport && component.viewport.initialize) {
18
- component.viewport.initialize();
19
- }
20
- }, 0);
21
-
22
- // Track loading state
23
- let isLoading = false;
24
- let selectedIds = new Set<string | number>();
25
-
26
- // Track pending removals to filter out items from stale server responses
27
- // This prevents race conditions where server returns old data before updates are committed
28
- const pendingRemovals = new Set<string | number>();
29
-
30
- // Listen for collection events
31
- component.on?.("collection:range-loaded", () => {
32
- isLoading = false;
33
- });
34
- component.on?.("collection:range-failed", () => {
35
- isLoading = false;
36
- });
37
-
38
- return {
39
- ...component,
40
-
41
- // Data operations
42
- setItems(items: T[]) {
43
- if (component.viewport?.collection) {
44
- component.viewport.collection.setItems(items);
45
- } else {
46
- component.items = items;
47
- component.totalItems = items.length;
48
- }
49
- component.emit?.("items:set", { items, total: items.length });
50
- },
51
-
52
- getItems(): T[] {
53
- // Collection is added directly to component, not to viewport.collection
54
- if ((component as any).collection?.getItems) {
55
- return (component as any).collection.getItems();
56
- }
57
- return component.items || [];
58
- },
59
-
60
- getVisibleItems(): T[] {
61
- const range = component.viewport?.getVisibleRange() || {
62
- start: 0,
63
- end: 0,
64
- };
65
- const items = this.getItems();
66
- return items.slice(range.start, range.end);
67
- },
68
-
69
- getItemCount(): number {
70
- if (component.viewport?.collection) {
71
- return component.viewport.collection.getTotalItems();
72
- }
73
- return component.totalItems || component.items?.length || 0;
74
- },
75
-
76
- /**
77
- * Get item at a specific index
78
- * @param index - The index of the item to retrieve
79
- * @returns The item at the index, or undefined if not found
80
- */
81
- getItem(index: number): T | undefined {
82
- if ((component as any).collection?.getItem) {
83
- return (component as any).collection.getItem(index);
84
- }
85
- const items = this.getItems();
86
- return items[index];
87
- },
88
-
89
- /**
90
- * Update item at a specific index
91
- * @param index - The index of the item to update
92
- * @param item - The new item data (full replacement)
93
- */
94
- updateItem(index: number, item: T): void {
95
- const items = this.getItems();
96
-
97
- if (index < 0 || index >= items.length) {
98
- console.warn(`[VList] updateItem: index ${index} out of bounds`);
99
- return;
100
- }
101
-
102
- const previousItem = items[index];
103
-
104
- // Update in collection items array
105
- if ((component as any).collection?.items) {
106
- (component as any).collection.items[index] = item;
107
- }
108
-
109
- // Emit event for rendering feature to handle DOM update
110
- component.emit?.("item:update-request", {
111
- index,
112
- item,
113
- previousItem,
114
- });
115
- },
116
-
117
- /**
118
- * Update item by ID
119
- * Finds the item in the collection by its ID and updates it with new data
120
- * Re-renders the item if currently visible in the viewport
121
- * @param id - The item ID to find
122
- * @param data - Partial data to merge with existing item, or full item replacement
123
- * @param options - Update options
124
- * @returns true if item was found and updated, false otherwise
125
- */
126
- updateItemById(
127
- id: string | number,
128
- data: Partial<T>,
129
- options: {
130
- /** If true, replace the entire item instead of merging (default: false) */
131
- replace?: boolean;
132
- /** If true, re-render even if not visible (default: false) */
133
- forceRender?: boolean;
134
- } = {},
135
- ): boolean {
136
- const { replace = false } = options;
137
- const items = this.getItems();
138
-
139
- // Find the item by ID (handle sparse arrays from pagination)
140
- // Check all possible ID fields since items may have different ID structures
141
- const index = items.findIndex((item: any) => {
142
- if (!item) return false;
143
- // Check all possible ID fields - user_id is important for profile/contact items
144
- const idsToCheck = [item.id, item._id, item.user_id].filter(
145
- (v) => v !== undefined && v !== null,
146
- );
147
- return idsToCheck.some(
148
- (itemId) => itemId === id || String(itemId) === String(id),
149
- );
150
- });
151
-
152
- if (index === -1) {
153
- console.warn(`[VList] updateItemById: item with id ${id} not found`);
154
- return false;
155
- }
156
-
157
- const previousItem = items[index];
158
-
159
- // Create updated item - either replace entirely or merge
160
- let updatedItem: T;
161
- if (replace) {
162
- updatedItem = { ...data, id: previousItem.id ?? id } as T;
163
- } else {
164
- // Only merge properties that have actual values (not undefined)
165
- // This prevents partial updates from overwriting existing data
166
- updatedItem = { ...previousItem } as T;
167
- for (const [key, value] of Object.entries(data)) {
168
- if (value !== undefined) {
169
- (updatedItem as any)[key] = value;
170
- }
171
- }
172
- }
173
-
174
- // Update in collection items array
175
- if ((component as any).collection?.items) {
176
- (component as any).collection.items[index] = updatedItem;
177
- }
178
-
179
- // Emit event for rendering feature to handle DOM update
180
- component.emit?.("item:update-request", {
181
- index,
182
- item: updatedItem,
183
- previousItem,
184
- });
185
-
186
- return true;
187
- },
188
-
189
- /**
190
- * Remove item at a specific index
191
- * Removes the item from the collection, updates totalItems, and triggers re-render
192
- * @param index - The index of the item to remove
193
- * @returns true if item was found and removed, false otherwise
194
- */
195
- removeItem(index: number): boolean {
196
- const items = this.getItems();
197
- const collection = (component as any).collection;
198
-
199
- if (index < 0 || index >= items.length) {
200
- console.warn(`[VList] removeItem: index ${index} out of bounds`);
201
- return false;
202
- }
203
-
204
- const removedItem = items[index];
205
-
206
- // Remove from collection items array (component.collection.items is the actual array)
207
- if ((component as any).collection?.items) {
208
- (component as any).collection.items.splice(index, 1);
209
- }
210
-
211
- // IMPORTANT: Emit item:remove-request FIRST so rendering feature rebuilds collectionItems
212
- // BEFORE setTotalItems triggers a render via viewport:total-items-changed
213
- component.emit?.("item:remove-request", {
214
- index,
215
- item: removedItem,
216
- });
217
-
218
- // Update totalItems - this will trigger viewport:total-items-changed which calls renderItems
219
- // By now, collectionItems has been rebuilt by the item:remove-request handler
220
- if (collection?.getTotalItems && collection?.setTotalItems) {
221
- const currentTotal = collection.getTotalItems();
222
- collection.setTotalItems(Math.max(0, currentTotal - 1));
223
- } else if (component.viewport?.collection?.setTotalItems) {
224
- // Fallback to viewport.collection if available
225
- const currentTotal = component.viewport.collection.getTotalItems();
226
- component.viewport.collection.setTotalItems(
227
- Math.max(0, currentTotal - 1),
228
- );
229
- } else {
230
- // Last resort: update component.totalItems directly
231
- if (component.totalItems !== undefined) {
232
- component.totalItems = Math.max(0, component.totalItems - 1);
233
- }
234
- }
235
-
236
- return true;
237
- },
238
-
239
- /**
240
- * Remove item by ID
241
- * Finds the item in the collection by its ID and removes it
242
- * Updates totalItems and triggers re-render of visible items
243
- * Optionally tracks as pending removal to filter from future fetches
244
- * @param id - The item ID to find and remove
245
- * @param options - Remove options (trackPending, pendingTimeout)
246
- * @returns true if item was found and removed, false otherwise
247
- */
248
- removeItemById(
249
- id: string | number,
250
- options: RemoveItemOptions = {},
251
- ): boolean {
252
- const { trackPending = true, pendingTimeout = 5000 } = options;
253
-
254
- if (id === undefined || id === null) {
255
- console.warn(`[VList] removeItemById: invalid id`);
256
- return false;
257
- }
258
-
259
- // Add to pending removals to filter from future server responses
260
- if (trackPending) {
261
- pendingRemovals.add(id);
262
-
263
- // Clear from pending removals after timeout (server should have updated by then)
264
- setTimeout(() => {
265
- pendingRemovals.delete(id);
266
- }, pendingTimeout);
267
- }
268
-
269
- const items = this.getItems();
270
-
271
- // Find the item by ID (handle sparse arrays from pagination)
272
- // Check all possible ID fields since items may have different ID structures
273
- const index = items.findIndex((item: any) => {
274
- if (!item) return false;
275
- // Check all possible ID fields
276
- const idsToCheck = [item.id, item._id, item.user_id].filter(
277
- (v) => v !== undefined && v !== null,
278
- );
279
- return idsToCheck.some(
280
- (itemId) => itemId === id || String(itemId) === String(id),
281
- );
282
- });
283
-
284
- if (index === -1) {
285
- return false;
286
- }
287
-
288
- return this.removeItem(index);
289
- },
290
-
291
- /**
292
- * Check if an item ID is pending removal
293
- * @param id - The item ID to check
294
- * @returns true if the item is pending removal
295
- */
296
- isPendingRemoval(id: string | number): boolean {
297
- return pendingRemovals.has(id);
298
- },
299
-
300
- /**
301
- * Get all pending removal IDs
302
- * @returns Set of pending removal IDs
303
- */
304
- getPendingRemovals(): Set<string | number> {
305
- return new Set(pendingRemovals);
306
- },
307
-
308
- /**
309
- * Clear a specific pending removal
310
- * @param id - The item ID to clear from pending removals
311
- */
312
- clearPendingRemoval(id: string | number): void {
313
- pendingRemovals.delete(id);
314
- },
315
-
316
- /**
317
- * Clear all pending removals
318
- */
319
- clearAllPendingRemovals(): void {
320
- pendingRemovals.clear();
321
- },
322
-
323
- /**
324
- * Filter items array to exclude pending removals
325
- * Utility method for use in collection adapters
326
- * @param items - Array of items to filter
327
- * @returns Filtered array without pending removal items
328
- */
329
- filterPendingRemovals<I extends { id?: any; _id?: any }>(
330
- items: I[],
331
- ): I[] {
332
- if (pendingRemovals.size === 0) return items;
333
-
334
- return items.filter((item) => {
335
- const id = item._id || item.id;
336
- return !pendingRemovals.has(id);
337
- });
338
- },
339
-
340
- // Loading operations
341
- async loadRange(
342
- page: number,
343
- limit: number,
344
- strategy: string = "page",
345
- alignment?: string,
346
- ) {
347
- isLoading = true;
348
-
349
- if (component.viewport?.collection) {
350
- const offset = strategy === "page" ? (page - 1) * limit : page;
351
- try {
352
- await component.viewport.collection.loadRange(offset, limit);
353
- component.emit?.("load", { page, limit, strategy });
354
-
355
- // Scroll to the loaded range if alignment is specified
356
- if (alignment) {
357
- const index = offset;
358
- this.scrollToIndex(index, alignment);
359
- }
360
- } catch (error) {
361
- component.emit?.("error", { error });
362
- throw error;
363
- } finally {
364
- isLoading = false;
365
- }
366
- }
367
- },
368
-
369
- // Data loading
370
- loadNext: async function () {
371
- console.log(`[VList] loadNext()`);
372
-
373
- if (component.viewport?.collection) {
374
- const totalItems = component.viewport.collection.getTotalItems();
375
- const loadedRanges = component.viewport.collection.getLoadedRanges();
376
-
377
- // Find the next unloaded range
378
- let nextOffset = 0;
379
- for (const rangeId of loadedRanges) {
380
- const rangeEnd = (rangeId + 1) * (config.pagination?.limit || 20);
381
- if (rangeEnd > nextOffset) {
382
- nextOffset = rangeEnd;
383
- }
384
- }
385
-
386
- if (nextOffset < totalItems) {
387
- await component.viewport.collection.loadRange(
388
- nextOffset,
389
- config.pagination?.limit || 20,
390
- );
391
- }
392
- }
393
-
394
- return Promise.resolve();
395
- },
396
-
397
- isLoading(): boolean {
398
- return isLoading;
399
- },
400
-
401
- hasNext(): boolean {
402
- if (component.viewport?.collection) {
403
- const total = component.viewport.collection.getTotalItems();
404
- const items = component.viewport.collection.getItems();
405
- return items.length < total;
406
- }
407
- return false;
408
- },
409
-
410
- // Note: Selection operations are handled by the withSelection feature
411
- // These are kept for backward compatibility but delegate to selection feature if available
412
- getSelectedIds(): (string | number)[] {
413
- if (typeof component.getSelectedIndices === "function") {
414
- // Use selection feature if available
415
- const items = this.getItems();
416
- const indices = component.getSelectedIndices();
417
- return indices.map((idx: number) => (items[idx] as any)?.id || idx);
418
- }
419
- // Fallback to local tracking
420
- return Array.from(selectedIds);
421
- },
422
-
423
- selectItem(id: string | number) {
424
- if (typeof component.selectItems === "function") {
425
- // Use selection feature if available
426
- const items = this.getItems();
427
- const index = items.findIndex((item: any) => item.id === id);
428
- if (index >= 0) {
429
- component.selectItems([index]);
430
- }
431
- } else {
432
- // Fallback to local tracking
433
- selectedIds.add(id);
434
- component.emit?.("selection:change", {
435
- selected: this.getSelectedIds(),
436
- });
437
- }
438
- },
439
-
440
- deselectItem(id: string | number) {
441
- if (typeof component.deselectItems === "function") {
442
- // Use selection feature if available
443
- const items = this.getItems();
444
- const index = items.findIndex((item: any) => item.id === id);
445
- if (index >= 0) {
446
- component.deselectItems([index]);
447
- }
448
- } else {
449
- // Fallback to local tracking
450
- selectedIds.delete(id);
451
- component.emit?.("selection:change", {
452
- selected: this.getSelectedIds(),
453
- });
454
- }
455
- },
456
-
457
- clearSelection() {
458
- if (typeof component.clearSelection === "function") {
459
- // Use selection feature if available
460
- component.clearSelection();
461
- } else {
462
- // Fallback to local tracking
463
- selectedIds.clear();
464
- component.emit?.("selection:change", { selected: [] });
465
- }
466
- },
467
-
468
- // Scrolling methods
469
- scrollToIndex: (
470
- index: number,
471
- alignment: "start" | "center" | "end" = "start",
472
- animate?: boolean,
473
- ) => {
474
- if (component.viewport) {
475
- component.viewport.scrollToIndex(index, alignment);
476
- }
477
- return Promise.resolve();
478
- },
479
-
480
- /**
481
- * Scroll to index and select item after data loads
482
- * Useful for runtime scroll+select when VList is already created
483
- */
484
- scrollToIndexAndSelect: async function (
485
- index: number,
486
- selectId: string | number,
487
- alignment: "start" | "center" | "end" = "start",
488
- ) {
489
- if (component.viewport) {
490
- component.viewport.scrollToIndex(index, alignment);
491
- }
492
-
493
- // Listen for range load to complete, then select
494
- const onRangeLoaded = () => {
495
- component.off?.("viewport:range-loaded", onRangeLoaded);
496
- requestAnimationFrame(() => {
497
- if (component.selectById) {
498
- component.selectById(selectId);
499
- }
500
- });
501
- };
502
-
503
- component.on?.("viewport:range-loaded", onRangeLoaded);
504
-
505
- // Fallback timeout in case event doesn't fire (data already loaded)
506
- setTimeout(() => {
507
- component.off?.("viewport:range-loaded", onRangeLoaded);
508
- if (component.selectById) {
509
- component.selectById(selectId);
510
- }
511
- }, 300);
512
-
513
- return Promise.resolve();
514
- },
515
-
516
- scrollToItem: async function (
517
- itemId: string,
518
- alignment: "start" | "center" | "end" = "start",
519
- animate?: boolean,
520
- ) {
521
- const items = this.getItems();
522
- const index = items.findIndex(
523
- (item: any) => String(item.id) === String(itemId),
524
- );
525
-
526
- if (index >= 0) {
527
- return this.scrollToIndex(index, alignment, animate);
528
- }
529
-
530
- console.warn(`Item ${itemId} not found`);
531
- return Promise.resolve();
532
- },
533
-
534
- scrollToTop: function () {
535
- return this.scrollToIndex(0, "start");
536
- },
537
-
538
- scrollToBottom: function () {
539
- const totalItems = this.getItemCount();
540
- if (totalItems > 0) {
541
- return this.scrollToIndex(totalItems - 1, "end");
542
- }
543
- return Promise.resolve();
544
- },
545
-
546
- scrollToPage: async function (
547
- pageNum: number,
548
- alignment: "start" | "center" | "end" = "start",
549
- animate?: boolean,
550
- ) {
551
- // console.log(`[VList] scrollToPage(${pageNum})`);
552
-
553
- // Get limit from config (rangeSize) or default
554
- const limit = config.pagination?.limit || 20;
555
-
556
- // Check if we're in cursor mode
557
- if (config.pagination?.strategy === "cursor") {
558
- // In cursor mode, we need to handle sequential loading
559
- const collection = (component.viewport as any)?.collection;
560
- if (collection) {
561
- const loadedRanges = collection.getLoadedRanges();
562
- const highestLoadedPage = loadedRanges.size;
563
-
564
- if (pageNum > highestLoadedPage + 1) {
565
- console.warn(
566
- `[VList] Cannot jump to page ${pageNum} in cursor mode. ` +
567
- `Loading pages sequentially from ${
568
- highestLoadedPage + 1
569
- } to ${pageNum}`,
570
- );
571
- }
572
- }
573
- }
574
-
575
- // Use viewport's scrollToPage if available
576
- if ((component.viewport as any)?.scrollToPage) {
577
- (component.viewport as any).scrollToPage(pageNum, limit, alignment);
578
- } else {
579
- // Fallback to scrollToIndex
580
- const targetIndex = (pageNum - 1) * limit;
581
- await this.scrollToIndex(targetIndex, alignment);
582
- }
583
- },
584
-
585
- getScrollPosition: () => {
586
- return component.viewport?.getScrollPosition() || 0;
587
- },
588
-
589
- getCurrentCursor: () => {
590
- const collection = (component.viewport as any)?.collection;
591
- return collection?.getCurrentCursor?.() || null;
592
- },
593
-
594
- setScrollAnimation(enabled: boolean) {
595
- // Store animation preference (would be used by viewport scrolling feature)
596
- component.scrollAnimation = enabled;
597
- },
598
-
599
- // State
600
- getState() {
601
- return {
602
- items: this.getItems(),
603
- totalItems: component.viewport?.collection
604
- ? component.viewport.collection.getTotalItems()
605
- : component.totalItems || 0,
606
- visibleRange: component.viewport?.getVisibleRange() || {
607
- start: 0,
608
- end: 0,
609
- },
610
- scrollPosition: component.viewport?.getScrollPosition() || 0,
611
- selectedIds: this.getSelectedIds(),
612
- isLoading: this.isLoading(),
613
- };
614
- },
615
-
616
- // Lifecycle
617
- destroy() {
618
- if (component.viewport?.destroy) {
619
- component.viewport.destroy();
620
- }
621
- selectedIds.clear();
622
- component.emit?.("destroyed");
623
- },
624
- };
625
- };
626
- };