mtrl-addons 0.2.1 → 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 -9
  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 +6 -1
  62. package/src/styles/components/_vlist.scss +234 -213
  63. package/.cursorrules +0 -117
  64. package/AI.md +0 -241
  65. package/build.js +0 -201
  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 -322
  70. package/src/components/vlist/features/selection.ts +0 -444
  71. package/src/components/vlist/features/viewport.ts +0 -65
  72. package/src/components/vlist/features.ts +0 -112
  73. package/src/components/vlist/types.ts +0 -591
  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 -1001
  96. package/src/core/layout/types.ts +0 -95
  97. package/src/core/viewport/constants.ts +0 -140
  98. package/src/core/viewport/features/base.ts +0 -73
  99. package/src/core/viewport/features/collection.ts +0 -882
  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 -260
  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 -568
  107. package/src/core/viewport/features/scrollbar.ts +0 -434
  108. package/src/core/viewport/features/scrolling.ts +0 -618
  109. package/src/core/viewport/features/utils.ts +0 -88
  110. package/src/core/viewport/features/virtual.ts +0 -384
  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 -246
  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,568 +0,0 @@
1
- /**
2
- * Rendering Feature - Item rendering and positioning for viewport
3
- * Handles DOM element creation, positioning, recycling, and updates
4
- */
5
-
6
- import { addClass, removeClass, hasClass } from "mtrl";
7
- import type { ViewportContext, ViewportComponent } from "../types";
8
- import { VIEWPORT_CONSTANTS } from "../constants";
9
- import {
10
- isPlaceholder,
11
- getViewportState,
12
- wrapInitialize,
13
- wrapDestroy,
14
- } from "./utils";
15
- import { createLayout } from "../../layout";
16
-
17
- export interface RenderingConfig {
18
- template?: (
19
- item: any,
20
- index: number
21
- ) => string | HTMLElement | any[] | Record<string, any>;
22
- overscan?: number;
23
- measureItems?: boolean;
24
- enableRecycling?: boolean;
25
- maxPoolSize?: number;
26
- }
27
-
28
- interface ViewportState {
29
- scrollPosition: number;
30
- totalItems: number;
31
- itemSize: number;
32
- containerSize: number;
33
- virtualTotalSize: number;
34
- visibleRange: { start: number; end: number };
35
- itemsContainer: HTMLElement | null;
36
- }
37
-
38
- /**
39
- * Rendering feature for viewport
40
- */
41
- export const withRendering = (config: RenderingConfig = {}) => {
42
- return <T extends ViewportContext & ViewportComponent>(component: T): T => {
43
- const {
44
- template,
45
- overscan = 5,
46
- measureItems = false,
47
- enableRecycling = true,
48
- maxPoolSize = VIEWPORT_CONSTANTS.RENDERING.DEFAULT_MAX_POOL_SIZE,
49
- } = config;
50
-
51
- // State
52
- const renderedElements = new Map<number, HTMLElement>();
53
- const collectionItems: Record<number, any> = {};
54
- const elementPool: HTMLElement[] = [];
55
- const poolStats = { created: 0, recycled: 0, poolSize: 0 };
56
-
57
- let viewportState: ViewportState | null = null;
58
- let currentVisibleRange = { start: 0, end: 0 };
59
- let lastRenderTime = 0;
60
-
61
- // Element pool management
62
- const getPooledElement = (): HTMLElement => {
63
- if (enableRecycling && elementPool.length > 0) {
64
- poolStats.recycled++;
65
- return elementPool.pop()!;
66
- }
67
- const element = document.createElement("div");
68
- element.className = "mtrl-viewport-item";
69
- poolStats.created++;
70
- return element;
71
- };
72
-
73
- const releaseElement = (element: HTMLElement): void => {
74
- if (!enableRecycling) {
75
- element.remove();
76
- return;
77
- }
78
- // Clean and clone for reuse
79
- element.className = "mtrl-viewport-item";
80
- element.removeAttribute("data-index");
81
- element.style.cssText = "";
82
- element.innerHTML = "";
83
- const cleanElement = element.cloneNode(false) as HTMLElement;
84
-
85
- if (elementPool.length < maxPoolSize) {
86
- elementPool.push(cleanElement);
87
- poolStats.poolSize = elementPool.length;
88
- }
89
- element.remove();
90
- };
91
-
92
- // Initialize
93
- wrapInitialize(component, () => {
94
- viewportState = getViewportState(component) as ViewportState;
95
-
96
- // Listen for collection data loaded
97
- component.on?.("collection:range-loaded", (data: any) => {
98
- if (!data.items?.length) return;
99
-
100
- // Analyze data structure on first load
101
- const placeholders = (component as any).placeholders;
102
- if (placeholders && !placeholders.hasAnalyzedStructure()) {
103
- placeholders.analyzeDataStructure(data.items);
104
- }
105
-
106
- // Update collection items and replace placeholders
107
- data.items.forEach((item: any, i: number) => {
108
- const index = data.offset + i;
109
- const oldItem = collectionItems[index];
110
- collectionItems[index] = item;
111
-
112
- // Replace placeholder in DOM if needed
113
- if (
114
- oldItem &&
115
- isPlaceholder(oldItem) &&
116
- renderedElements.has(index)
117
- ) {
118
- const element = renderedElements.get(index);
119
- if (element) {
120
- const newElement = renderItem(item, index);
121
- if (newElement) {
122
- // Remove placeholder classes
123
- removeClass(newElement, VIEWPORT_CONSTANTS.PLACEHOLDER.CLASS);
124
-
125
- // Add replaced class for fade-in animation
126
- addClass(newElement, "viewport-item--replaced");
127
-
128
- // Copy position and replace
129
- Object.assign(newElement.style, {
130
- position: element.style.position,
131
- transform: element.style.transform,
132
- width: element.style.width,
133
- });
134
- element.parentNode?.replaceChild(newElement, element);
135
- renderedElements.set(index, newElement);
136
- releaseElement(element);
137
-
138
- // Remove the replaced class after animation completes
139
- setTimeout(() => {
140
- removeClass(newElement, "viewport-item--replaced");
141
- }, 300);
142
- }
143
- }
144
- }
145
- });
146
-
147
- // Check if we need to render
148
- const { visibleRange } = viewportState || {};
149
- if (visibleRange) {
150
- const renderStart = Math.max(0, visibleRange.start - overscan);
151
- const renderEnd = Math.min(
152
- viewportState?.totalItems ?? 0 - 1,
153
- visibleRange.end + overscan
154
- );
155
- const loadedStart = data.offset;
156
- const loadedEnd = data.offset + data.items.length - 1;
157
-
158
- // Check for placeholders in range
159
- const hasPlaceholdersInRange = Array.from(
160
- { length: Math.min(loadedEnd, renderEnd) - loadedStart + 1 },
161
- (_, i) => collectionItems[loadedStart + i]
162
- ).some((item) => item && isPlaceholder(item));
163
-
164
- const needsRender =
165
- (loadedStart <= renderEnd &&
166
- loadedEnd >= renderStart &&
167
- renderedElements.size < renderEnd - renderStart + 1) ||
168
- hasPlaceholdersInRange;
169
-
170
- if (needsRender) renderItems();
171
- }
172
- });
173
-
174
- // Listen for events
175
- component.on?.("viewport:range-changed", renderItems);
176
- component.on?.("viewport:scroll", updateItemPositions);
177
- });
178
-
179
- // Template helpers
180
- const getDefaultTemplate = () => (item: any, index: number) => {
181
- // Use layout system for default template
182
- return [
183
- {
184
- tag: "div",
185
- class: "viewport-item",
186
- text:
187
- typeof item === "object"
188
- ? item.name || item.label || item.text || `Item ${index}`
189
- : String(item),
190
- },
191
- ];
192
- };
193
-
194
- // Process layout schema with item data substitution
195
- const processLayoutSchema = (
196
- schema: any,
197
- item: any,
198
- index: number
199
- ): any => {
200
- if (typeof schema === "string") {
201
- // Handle variable substitution like {{name}}, {{index}}
202
- return schema.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
203
- if (key === "index") return String(index);
204
- if (key === "item") return String(item);
205
-
206
- // Handle nested properties like {{user.name}}
207
- const value = key.split(".").reduce((obj: any, prop: string) => {
208
- return obj?.[prop.trim()];
209
- }, item);
210
-
211
- return value !== undefined ? String(value) : match;
212
- });
213
- }
214
-
215
- if (Array.isArray(schema)) {
216
- return schema.map((child) => processLayoutSchema(child, item, index));
217
- }
218
-
219
- if (typeof schema === "object" && schema !== null) {
220
- const processed: any = {};
221
- for (const [key, value] of Object.entries(schema)) {
222
- processed[key] = processLayoutSchema(value, item, index);
223
- }
224
- return processed;
225
- }
226
-
227
- return schema;
228
- };
229
-
230
- // Position calculation
231
- const calculateItemPosition = (
232
- index: number,
233
- scrollPosition: number,
234
- totalItems: number,
235
- itemSize: number,
236
- virtualTotalSize: number,
237
- containerSize: number
238
- ): number => {
239
- const actualTotalSize = totalItems * itemSize;
240
- const isCompressed =
241
- actualTotalSize > virtualTotalSize && virtualTotalSize > 0;
242
-
243
- if (!isCompressed || totalItems === 0) {
244
- return index * itemSize - scrollPosition;
245
- }
246
-
247
- // Compressed space handling
248
- const maxScrollPosition = virtualTotalSize - containerSize;
249
- const distanceFromBottom = maxScrollPosition - scrollPosition;
250
- const nearBottomThreshold = containerSize;
251
-
252
- if (
253
- distanceFromBottom <= nearBottomThreshold &&
254
- distanceFromBottom >= -1
255
- ) {
256
- // Near bottom interpolation
257
- const itemsAtBottom = Math.floor(containerSize / itemSize);
258
- const firstVisibleAtBottom = Math.max(0, totalItems - itemsAtBottom);
259
- const scrollRatio = scrollPosition / virtualTotalSize;
260
- const exactScrollIndex = scrollRatio * totalItems;
261
- const interpolation = Math.max(
262
- 0,
263
- Math.min(1, 1 - distanceFromBottom / nearBottomThreshold)
264
- );
265
-
266
- const bottomPosition = (index - firstVisibleAtBottom) * itemSize;
267
- const normalPosition = (index - exactScrollIndex) * itemSize;
268
- return (
269
- normalPosition + (bottomPosition - normalPosition) * interpolation
270
- );
271
- }
272
-
273
- // Normal compressed scrolling
274
- const scrollRatio = scrollPosition / virtualTotalSize;
275
- return (index - scrollRatio * totalItems) * itemSize;
276
- };
277
-
278
- // Render single item
279
- const renderItem = (item: any, index: number): HTMLElement | null => {
280
- const itemTemplate = template || getDefaultTemplate();
281
-
282
- try {
283
- const result = itemTemplate(item, index);
284
- let element: HTMLElement;
285
-
286
- // Check if result is a layout schema (array or object)
287
- if (
288
- Array.isArray(result) ||
289
- (typeof result === "object" &&
290
- result !== null &&
291
- !(result instanceof HTMLElement))
292
- ) {
293
- // Process schema to substitute variables
294
- const processedSchema = processLayoutSchema(result, item, index);
295
-
296
- // Use layout system to create element
297
- const layoutResult = createLayout(processedSchema);
298
- element = layoutResult.element;
299
-
300
- // If the layout created a wrapper, use it directly
301
- if (element && element.nodeType === 1) {
302
- // Element is already created by layout system
303
- } else {
304
- // Fallback if layout didn't create a proper element
305
- element = getPooledElement();
306
- if (layoutResult.element) {
307
- element.appendChild(layoutResult.element);
308
- }
309
- }
310
- } else if (typeof result === "string") {
311
- element = getPooledElement();
312
- element.innerHTML = result;
313
- if (element.children.length === 1) {
314
- addClass(element.firstElementChild as HTMLElement, "viewport-item");
315
- }
316
- } else if (result instanceof HTMLElement) {
317
- element = getPooledElement();
318
- element.appendChild(result);
319
- } else {
320
- console.warn(`[Rendering] Invalid template result for item ${index}`);
321
- return null;
322
- }
323
-
324
- // Add classes
325
- if (!hasClass(element, "viewport-item"))
326
- addClass(element, "viewport-item");
327
- if (isPlaceholder(item)) {
328
- addClass(element, VIEWPORT_CONSTANTS.PLACEHOLDER.CLASS);
329
- }
330
-
331
- element.dataset.index = String(index);
332
- return element;
333
- } catch (error) {
334
- console.error(`[Rendering] Error rendering item ${index}:`, error);
335
- return null;
336
- }
337
- };
338
-
339
- // Update positions
340
- const updateItemPositions = (): void => {
341
- if (!viewportState || renderedElements.size === 0) return;
342
-
343
- const {
344
- scrollPosition,
345
- itemSize: itemSize,
346
- totalItems,
347
- virtualTotalSize,
348
- containerSize,
349
- } = viewportState;
350
- const actualTotalSize = totalItems * itemSize;
351
- const isCompressed =
352
- actualTotalSize > virtualTotalSize && virtualTotalSize > 0;
353
-
354
- const sortedIndices = Array.from(renderedElements.keys()).sort(
355
- (a, b) => a - b
356
- );
357
- if (!sortedIndices.length) return;
358
-
359
- const firstIndex = sortedIndices[0];
360
- let currentPosition = 0;
361
-
362
- if (isCompressed) {
363
- const maxScroll = virtualTotalSize - containerSize;
364
- const distanceFromBottom = maxScroll - scrollPosition;
365
-
366
- if (distanceFromBottom <= containerSize && distanceFromBottom >= -1) {
367
- // Near bottom interpolation
368
- const itemsAtBottom = Math.floor(containerSize / itemSize);
369
- const firstVisibleAtBottom = Math.max(0, totalItems - itemsAtBottom);
370
- const scrollRatio = scrollPosition / virtualTotalSize;
371
- const exactScrollIndex = scrollRatio * totalItems;
372
- const interpolation = Math.max(
373
- 0,
374
- Math.min(1, 1 - distanceFromBottom / containerSize)
375
- );
376
-
377
- const bottomPos = (firstIndex - firstVisibleAtBottom) * itemSize;
378
- const normalPos = (firstIndex - exactScrollIndex) * itemSize;
379
- currentPosition = normalPos + (bottomPos - normalPos) * interpolation;
380
- } else {
381
- const scrollRatio = scrollPosition / virtualTotalSize;
382
- currentPosition = (firstIndex - scrollRatio * totalItems) * itemSize;
383
- }
384
- } else {
385
- currentPosition = firstIndex * itemSize - scrollPosition;
386
- }
387
-
388
- // Position each item
389
- sortedIndices.forEach((index) => {
390
- const element = renderedElements.get(index);
391
- if (element) {
392
- element.style.transform = `translateY(${Math.round(
393
- currentPosition
394
- )}px)`;
395
-
396
- // Strategic log for last items
397
- // if (index >= totalItems - 5) {
398
- // console.log(
399
- // `[Rendering] Last item positioned: index=${index}, position=${Math.round(
400
- // currentPosition
401
- // )}px, itemSize=${itemSize}px, totalItems=${totalItems}`
402
- // );
403
- // }
404
-
405
- currentPosition += itemSize;
406
- }
407
- });
408
- };
409
-
410
- // Main render function
411
- const renderItems = () => {
412
- if (!viewportState?.itemsContainer) return;
413
-
414
- const {
415
- visibleRange,
416
- itemsContainer,
417
- totalItems,
418
- itemSize,
419
- scrollPosition,
420
- containerSize,
421
- virtualTotalSize,
422
- } = viewportState;
423
-
424
- // Validate range
425
- if (
426
- !visibleRange ||
427
- visibleRange.start < 0 ||
428
- visibleRange.start >= totalItems ||
429
- visibleRange.end < visibleRange.start ||
430
- isNaN(visibleRange.start) ||
431
- isNaN(visibleRange.end)
432
- ) {
433
- return;
434
- }
435
-
436
- // Check if range changed
437
- if (
438
- visibleRange.start === currentVisibleRange.start &&
439
- visibleRange.end === currentVisibleRange.end &&
440
- renderedElements.size > 0
441
- ) {
442
- updateItemPositions();
443
- return;
444
- }
445
-
446
- lastRenderTime = Date.now();
447
- const renderStart = Math.max(0, visibleRange.start - overscan);
448
- const renderEnd = Math.min(totalItems - 1, visibleRange.end + overscan);
449
-
450
- // Remove items outside range
451
- Array.from(renderedElements.entries())
452
- .filter(([index]) => index < renderStart || index > renderEnd)
453
- .forEach(([index, element]) => {
454
- if (element.parentNode) releaseElement(element);
455
- renderedElements.delete(index);
456
- });
457
-
458
- // Get items source
459
- const hasCollectionItems = Object.keys(collectionItems).length > 0;
460
- const items = hasCollectionItems
461
- ? collectionItems
462
- : component.items || [];
463
- const missingItems: number[] = [];
464
-
465
- // Render items in range
466
- for (let i = renderStart; i <= renderEnd; i++) {
467
- if (i < 0 || i >= totalItems || renderedElements.has(i)) continue;
468
-
469
- let item = items[i];
470
- if (!item) {
471
- missingItems.push(i);
472
- // Generate placeholder
473
- const placeholders = (component as any).placeholders;
474
- item = placeholders?.generatePlaceholderItem(i) || {
475
- _placeholder: true,
476
- index: i,
477
- id: `placeholder-${i}`,
478
- name: VIEWPORT_CONSTANTS.PLACEHOLDER.MASK_CHARACTER.repeat(15),
479
- text: VIEWPORT_CONSTANTS.PLACEHOLDER.MASK_CHARACTER.repeat(25),
480
- description:
481
- VIEWPORT_CONSTANTS.PLACEHOLDER.MASK_CHARACTER.repeat(40),
482
- };
483
- collectionItems[i] = item;
484
- }
485
-
486
- const element = renderItem(item, i);
487
- if (element) {
488
- const position = calculateItemPosition(
489
- i,
490
- scrollPosition,
491
- totalItems,
492
- itemSize,
493
- virtualTotalSize,
494
- containerSize
495
- );
496
-
497
- Object.assign(element.style, {
498
- position: "absolute",
499
- transform: `translateY(${position}px)`,
500
- width: "100%",
501
- });
502
-
503
- itemsContainer.appendChild(element);
504
- renderedElements.set(i, element);
505
- }
506
- }
507
-
508
- // Request missing items
509
- if (
510
- missingItems.length > 0 &&
511
- component.viewport?.collection?.loadMissingRanges
512
- ) {
513
- component.viewport.collection.loadMissingRanges(
514
- {
515
- start: Math.min(...missingItems),
516
- end: Math.max(...missingItems),
517
- },
518
- "rendering:missing-items"
519
- );
520
- }
521
-
522
- currentVisibleRange = visibleRange;
523
-
524
- // Emit items rendered event with elements for size calculation
525
- const renderedElementsArray = Array.from(renderedElements.values());
526
- component.emit?.("viewport:items-rendered", {
527
- elements: renderedElementsArray,
528
- range: visibleRange,
529
- });
530
-
531
- component.emit?.("viewport:rendered", {
532
- range: visibleRange,
533
- renderedCount: renderedElements.size,
534
- });
535
- updateItemPositions();
536
-
537
- // Load data if no items rendered
538
- if (
539
- renderedElements.size === 0 &&
540
- totalItems > 0 &&
541
- component.viewport?.collection
542
- ) {
543
- component.viewport.collection.loadMissingRanges(
544
- visibleRange,
545
- "rendering:no-items"
546
- );
547
- }
548
- };
549
-
550
- // Extend viewport API
551
- const originalRenderItems = component.viewport.renderItems;
552
- component.viewport.renderItems = () => {
553
- renderItems();
554
- originalRenderItems?.();
555
- };
556
-
557
- // Cleanup
558
- wrapDestroy(component, () => {
559
- renderedElements.forEach((element) => {
560
- if (element.parentNode) releaseElement(element);
561
- });
562
- renderedElements.clear();
563
- elementPool.length = 0;
564
- });
565
-
566
- return component;
567
- };
568
- };