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.
- package/{src/components/index.ts → dist/components/index.d.ts} +0 -2
- package/dist/components/vlist/config.d.ts +86 -0
- package/{src/components/vlist/constants.ts → dist/components/vlist/constants.d.ts} +10 -11
- package/dist/components/vlist/features/api.d.ts +7 -0
- package/{src/components/vlist/features/index.ts → dist/components/vlist/features/index.d.ts} +0 -2
- package/dist/components/vlist/features/selection.d.ts +6 -0
- package/dist/components/vlist/features/viewport.d.ts +9 -0
- package/dist/components/vlist/features.d.ts +31 -0
- package/{src/components/vlist/index.ts → dist/components/vlist/index.d.ts} +1 -9
- package/dist/components/vlist/types.d.ts +596 -0
- package/dist/components/vlist/vlist.d.ts +29 -0
- package/dist/core/compose/features/gestures/index.d.ts +86 -0
- package/dist/core/compose/features/gestures/longpress.d.ts +85 -0
- package/dist/core/compose/features/gestures/pan.d.ts +108 -0
- package/dist/core/compose/features/gestures/pinch.d.ts +111 -0
- package/dist/core/compose/features/gestures/rotate.d.ts +111 -0
- package/dist/core/compose/features/gestures/swipe.d.ts +149 -0
- package/dist/core/compose/features/gestures/tap.d.ts +79 -0
- package/{src/core/compose/features/index.ts → dist/core/compose/features/index.d.ts} +1 -2
- package/{src/core/compose/index.ts → dist/core/compose/index.d.ts} +2 -11
- package/{src/core/gestures/index.ts → dist/core/gestures/index.d.ts} +1 -20
- package/dist/core/gestures/longpress.d.ts +23 -0
- package/dist/core/gestures/manager.d.ts +14 -0
- package/dist/core/gestures/pan.d.ts +12 -0
- package/dist/core/gestures/pinch.d.ts +14 -0
- package/dist/core/gestures/rotate.d.ts +14 -0
- package/dist/core/gestures/swipe.d.ts +20 -0
- package/dist/core/gestures/tap.d.ts +12 -0
- package/dist/core/gestures/types.d.ts +320 -0
- package/dist/core/gestures/utils.d.ts +57 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/layout/config.d.ts +33 -0
- package/dist/core/layout/index.d.ts +51 -0
- package/dist/core/layout/jsx.d.ts +65 -0
- package/dist/core/layout/schema.d.ts +112 -0
- package/dist/core/layout/types.d.ts +69 -0
- package/dist/core/viewport/constants.d.ts +105 -0
- package/dist/core/viewport/features/base.d.ts +14 -0
- package/dist/core/viewport/features/collection.d.ts +41 -0
- package/dist/core/viewport/features/events.d.ts +13 -0
- package/{src/core/viewport/features/index.ts → dist/core/viewport/features/index.d.ts} +0 -7
- package/dist/core/viewport/features/item-size.d.ts +30 -0
- package/dist/core/viewport/features/loading.d.ts +34 -0
- package/dist/core/viewport/features/momentum.d.ts +17 -0
- package/dist/core/viewport/features/performance.d.ts +53 -0
- package/dist/core/viewport/features/placeholders.d.ts +38 -0
- package/dist/core/viewport/features/rendering.d.ts +16 -0
- package/dist/core/viewport/features/scrollbar.d.ts +26 -0
- package/dist/core/viewport/features/scrolling.d.ts +16 -0
- package/dist/core/viewport/features/utils.d.ts +43 -0
- package/dist/core/viewport/features/virtual.d.ts +18 -0
- package/{src/core/viewport/index.ts → dist/core/viewport/index.d.ts} +1 -17
- package/dist/core/viewport/types.d.ts +96 -0
- package/dist/core/viewport/utils/speed-tracker.d.ts +22 -0
- package/dist/core/viewport/viewport.d.ts +11 -0
- package/{src/index.ts → dist/index.d.ts} +0 -4
- package/dist/index.js +5143 -0
- package/dist/index.mjs +5111 -0
- package/dist/styles.css +254 -0
- package/dist/styles.css.map +1 -0
- package/package.json +6 -1
- package/src/styles/components/_vlist.scss +234 -213
- package/.cursorrules +0 -117
- package/AI.md +0 -241
- package/build.js +0 -201
- package/scripts/analyze-orphaned-functions.ts +0 -387
- package/scripts/debug/vlist-selection.ts +0 -121
- package/src/components/vlist/config.ts +0 -323
- package/src/components/vlist/features/api.ts +0 -322
- package/src/components/vlist/features/selection.ts +0 -444
- package/src/components/vlist/features/viewport.ts +0 -65
- package/src/components/vlist/features.ts +0 -112
- package/src/components/vlist/types.ts +0 -591
- package/src/components/vlist/vlist.ts +0 -92
- package/src/core/compose/features/gestures/index.ts +0 -227
- package/src/core/compose/features/gestures/longpress.ts +0 -383
- package/src/core/compose/features/gestures/pan.ts +0 -424
- package/src/core/compose/features/gestures/pinch.ts +0 -475
- package/src/core/compose/features/gestures/rotate.ts +0 -485
- package/src/core/compose/features/gestures/swipe.ts +0 -492
- package/src/core/compose/features/gestures/tap.ts +0 -334
- package/src/core/gestures/longpress.ts +0 -68
- package/src/core/gestures/manager.ts +0 -418
- package/src/core/gestures/pan.ts +0 -48
- package/src/core/gestures/pinch.ts +0 -58
- package/src/core/gestures/rotate.ts +0 -58
- package/src/core/gestures/swipe.ts +0 -66
- package/src/core/gestures/tap.ts +0 -45
- package/src/core/gestures/types.ts +0 -387
- package/src/core/gestures/utils.ts +0 -128
- package/src/core/index.ts +0 -43
- package/src/core/layout/config.ts +0 -102
- package/src/core/layout/index.ts +0 -168
- package/src/core/layout/jsx.ts +0 -174
- package/src/core/layout/schema.ts +0 -1001
- package/src/core/layout/types.ts +0 -95
- package/src/core/viewport/constants.ts +0 -140
- package/src/core/viewport/features/base.ts +0 -73
- package/src/core/viewport/features/collection.ts +0 -882
- package/src/core/viewport/features/events.ts +0 -130
- package/src/core/viewport/features/item-size.ts +0 -271
- package/src/core/viewport/features/loading.ts +0 -263
- package/src/core/viewport/features/momentum.ts +0 -260
- package/src/core/viewport/features/performance.ts +0 -161
- package/src/core/viewport/features/placeholders.ts +0 -335
- package/src/core/viewport/features/rendering.ts +0 -568
- package/src/core/viewport/features/scrollbar.ts +0 -434
- package/src/core/viewport/features/scrolling.ts +0 -618
- package/src/core/viewport/features/utils.ts +0 -88
- package/src/core/viewport/features/virtual.ts +0 -384
- package/src/core/viewport/types.ts +0 -133
- package/src/core/viewport/utils/speed-tracker.ts +0 -79
- package/src/core/viewport/viewport.ts +0 -246
- package/test/benchmarks/layout/advanced.test.ts +0 -656
- package/test/benchmarks/layout/comparison.test.ts +0 -519
- package/test/benchmarks/layout/performance-comparison.test.ts +0 -274
- package/test/benchmarks/layout/real-components.test.ts +0 -733
- package/test/benchmarks/layout/simple.test.ts +0 -321
- package/test/benchmarks/layout/stress.test.ts +0 -990
- package/test/collection/basic.test.ts +0 -304
- package/test/components/vlist-selection.test.ts +0 -240
- package/test/components/vlist.test.ts +0 -63
- package/test/core/collection/adapter.test.ts +0 -161
- package/test/core/collection/collection.test.ts +0 -394
- package/test/core/layout/layout.test.ts +0 -201
- package/test/utils/dom-helpers.ts +0 -275
- package/test/utils/performance-helpers.ts +0 -392
- package/tsconfig.json +0 -20
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mtrl-addons List Component Configuration
|
|
3
|
-
*
|
|
4
|
-
* Configuration utilities and defaults for the list component.
|
|
5
|
-
* Follows mtrl patterns for component configuration.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createComponentConfig, createElementConfig } from "mtrl";
|
|
9
|
-
import { VLIST_CLASSES } from "./constants";
|
|
10
|
-
import type { ListConfig, ListItem } from "./types";
|
|
11
|
-
import type { ListComponent } from "./types";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Default configuration for the mtrl-addons List component
|
|
15
|
-
*/
|
|
16
|
-
export const defaultConfig: Partial<ListConfig> = {
|
|
17
|
-
// Collection settings (for API-connected lists)
|
|
18
|
-
adapter: undefined,
|
|
19
|
-
|
|
20
|
-
// Static data (for in-memory lists)
|
|
21
|
-
items: [],
|
|
22
|
-
|
|
23
|
-
// Template settings
|
|
24
|
-
template: undefined, // Will use default template if not provided
|
|
25
|
-
|
|
26
|
-
// Selection settings
|
|
27
|
-
selection: {
|
|
28
|
-
enabled: false,
|
|
29
|
-
mode: "none",
|
|
30
|
-
selectedIndices: [],
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
// Scroll settings
|
|
34
|
-
scroll: {
|
|
35
|
-
virtual: true,
|
|
36
|
-
itemSize: 50,
|
|
37
|
-
overscan: 5,
|
|
38
|
-
animation: false,
|
|
39
|
-
restorePosition: false,
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
// Component settings
|
|
43
|
-
className: VLIST_CLASSES.LIST,
|
|
44
|
-
prefix: "mtrl",
|
|
45
|
-
componentName: "list",
|
|
46
|
-
ariaLabel: "List",
|
|
47
|
-
debug: false,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Creates the base configuration for List component
|
|
52
|
-
* @param {ListConfig} config - User provided configuration
|
|
53
|
-
* @returns {ListConfig} Complete configuration with defaults applied
|
|
54
|
-
*/
|
|
55
|
-
// Minimal converter to avoid hardcoding external helpers
|
|
56
|
-
function convertRenderItemToTemplate(render: any) {
|
|
57
|
-
if (typeof render === "function") return render;
|
|
58
|
-
if (render && typeof render === "object") {
|
|
59
|
-
const { headline, supporting, leading, trailing } = render as any;
|
|
60
|
-
return (item: any, index: number) => {
|
|
61
|
-
const container = document.createElement("div");
|
|
62
|
-
container.className = "mtrl-list-item";
|
|
63
|
-
if (leading) {
|
|
64
|
-
const el = document.createElement("div");
|
|
65
|
-
el.className = "mtrl-list-item__leading";
|
|
66
|
-
el.innerHTML =
|
|
67
|
-
typeof leading === "function"
|
|
68
|
-
? leading(item, index)
|
|
69
|
-
: String(leading);
|
|
70
|
-
container.appendChild(el);
|
|
71
|
-
}
|
|
72
|
-
const content = document.createElement("div");
|
|
73
|
-
content.className = "mtrl-list-item__content";
|
|
74
|
-
if (headline) {
|
|
75
|
-
const h = document.createElement("div");
|
|
76
|
-
h.className = "mtrl-list-item__headline";
|
|
77
|
-
h.innerHTML =
|
|
78
|
-
typeof headline === "function"
|
|
79
|
-
? headline(item, index)
|
|
80
|
-
: String(headline);
|
|
81
|
-
content.appendChild(h);
|
|
82
|
-
}
|
|
83
|
-
if (supporting) {
|
|
84
|
-
const s = document.createElement("div");
|
|
85
|
-
s.className = "mtrl-list-item__supporting";
|
|
86
|
-
s.innerHTML =
|
|
87
|
-
typeof supporting === "function"
|
|
88
|
-
? supporting(item, index)
|
|
89
|
-
: String(supporting);
|
|
90
|
-
content.appendChild(s);
|
|
91
|
-
}
|
|
92
|
-
container.appendChild(content);
|
|
93
|
-
if (trailing) {
|
|
94
|
-
const el = document.createElement("div");
|
|
95
|
-
el.className = "mtrl-list-item__trailing";
|
|
96
|
-
el.innerHTML =
|
|
97
|
-
typeof trailing === "function"
|
|
98
|
-
? trailing(item, index)
|
|
99
|
-
: String(trailing);
|
|
100
|
-
container.appendChild(el);
|
|
101
|
-
}
|
|
102
|
-
return container;
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
return (item: any) => String(item);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function createBaseConfig<T extends ListItem = ListItem>(
|
|
109
|
-
config: ListConfig<T> = {}
|
|
110
|
-
): Required<ListConfig<T>> {
|
|
111
|
-
console.log("🔧 [MTRL-ADDONS-LIST] Creating base configuration");
|
|
112
|
-
|
|
113
|
-
// Validate required configuration
|
|
114
|
-
if (!config.items && !config.adapter) {
|
|
115
|
-
throw new Error(
|
|
116
|
-
"List requires either static items or an adapter for data loading"
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const mergedConfig = createComponentConfig(
|
|
121
|
-
defaultConfig as any,
|
|
122
|
-
config as any,
|
|
123
|
-
"list"
|
|
124
|
-
) as any;
|
|
125
|
-
|
|
126
|
-
// Convert renderItem object to template function if provided
|
|
127
|
-
if ((config as any).renderItem && !mergedConfig.template) {
|
|
128
|
-
mergedConfig.template = convertRenderItemToTemplate(
|
|
129
|
-
(config as any).renderItem
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Validate selection configuration
|
|
134
|
-
if (
|
|
135
|
-
mergedConfig.selection?.enabled &&
|
|
136
|
-
mergedConfig.selection?.mode === undefined
|
|
137
|
-
) {
|
|
138
|
-
mergedConfig.selection.mode = "single";
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return mergedConfig as Required<ListConfig<T>>;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Creates element configuration for withElement
|
|
146
|
-
* @param {ListConfig} config - List configuration
|
|
147
|
-
* @returns {Object} Element configuration
|
|
148
|
-
*/
|
|
149
|
-
export function getElementConfig<T extends ListItem = ListItem>(
|
|
150
|
-
config: ListConfig<T>
|
|
151
|
-
): any {
|
|
152
|
-
const attributes = {
|
|
153
|
-
"data-component": "list",
|
|
154
|
-
"data-addons": "true",
|
|
155
|
-
role: "list",
|
|
156
|
-
"aria-label": config.ariaLabel || "List",
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
return createElementConfig(config as any, {
|
|
160
|
-
tag: "div",
|
|
161
|
-
attributes,
|
|
162
|
-
className: VLIST_CLASSES.LIST,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Creates API configuration for the List component
|
|
168
|
-
* @param {Object} component - Component with list functionality
|
|
169
|
-
* @returns {Object} API configuration object for withApi
|
|
170
|
-
*/
|
|
171
|
-
export function getApiConfig<T extends ListItem = ListItem>(component: any) {
|
|
172
|
-
return {
|
|
173
|
-
// Data operations
|
|
174
|
-
data: {
|
|
175
|
-
add: component.add,
|
|
176
|
-
update: component.update,
|
|
177
|
-
remove: component.remove,
|
|
178
|
-
clear: component.clear,
|
|
179
|
-
refresh: component.refresh,
|
|
180
|
-
getItems: component.getItems,
|
|
181
|
-
getItem: component.getItem,
|
|
182
|
-
query: component.query,
|
|
183
|
-
sort: component.sort,
|
|
184
|
-
getSize: component.getSize,
|
|
185
|
-
isEmpty: component.isEmpty,
|
|
186
|
-
isLoading: component.isLoading,
|
|
187
|
-
getError: component.getError,
|
|
188
|
-
},
|
|
189
|
-
|
|
190
|
-
// Selection operations (if enabled)
|
|
191
|
-
selection: component.config?.selection?.enabled
|
|
192
|
-
? {
|
|
193
|
-
selectItem: component.selectItem,
|
|
194
|
-
deselectItem: component.deselectItem,
|
|
195
|
-
selectAll: component.selectAll,
|
|
196
|
-
deselectAll: component.deselectAll,
|
|
197
|
-
getSelectedItems: component.getSelectedItems,
|
|
198
|
-
getSelectedIds: component.getSelectedIds,
|
|
199
|
-
}
|
|
200
|
-
: undefined,
|
|
201
|
-
|
|
202
|
-
// Scrolling operations
|
|
203
|
-
scrolling: {
|
|
204
|
-
scrollToItem: component.scrollToItem,
|
|
205
|
-
scrollToIndex: component.scrollToIndex,
|
|
206
|
-
scrollToPage: component.scrollToPage,
|
|
207
|
-
},
|
|
208
|
-
|
|
209
|
-
// Performance operations
|
|
210
|
-
performance: {
|
|
211
|
-
getMetrics: component.getMetrics,
|
|
212
|
-
resetMetrics: component.resetMetrics,
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
// Template operations
|
|
216
|
-
template: {
|
|
217
|
-
setTemplate: component.setTemplate,
|
|
218
|
-
getTemplate: component.getTemplate,
|
|
219
|
-
},
|
|
220
|
-
|
|
221
|
-
// Events system
|
|
222
|
-
events: {
|
|
223
|
-
on: component.on,
|
|
224
|
-
off: component.off,
|
|
225
|
-
emit: component.emit,
|
|
226
|
-
subscribe: component.subscribe,
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
// Lifecycle operations
|
|
230
|
-
lifecycle: {
|
|
231
|
-
destroy: component.destroy,
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
// Configuration access
|
|
235
|
-
config: {
|
|
236
|
-
selection: component.config?.selection,
|
|
237
|
-
scroll: component.config?.scroll,
|
|
238
|
-
},
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Validates list configuration
|
|
244
|
-
*/
|
|
245
|
-
export const validateConfig = (config: ListConfig): void => {
|
|
246
|
-
// Validate container
|
|
247
|
-
if (config.container) {
|
|
248
|
-
if (typeof config.container === "string") {
|
|
249
|
-
const element = document.querySelector(config.container);
|
|
250
|
-
if (!element) {
|
|
251
|
-
throw new Error(
|
|
252
|
-
`List container element not found: ${config.container}`
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
} else if (!(config.container instanceof HTMLElement)) {
|
|
256
|
-
throw new Error(
|
|
257
|
-
"List container must be an HTMLElement or CSS selector string"
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Validate template
|
|
263
|
-
if (config.template && typeof config.template !== "function") {
|
|
264
|
-
throw new Error("List template must be a function");
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Validate items
|
|
268
|
-
if (config.items && !Array.isArray(config.items)) {
|
|
269
|
-
throw new Error("List items must be an array");
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Validate selection mode
|
|
273
|
-
if (
|
|
274
|
-
config.selection?.mode &&
|
|
275
|
-
!["single", "multiple", "none"].includes(config.selection.mode)
|
|
276
|
-
) {
|
|
277
|
-
throw new Error(
|
|
278
|
-
'List selection mode must be "single", "multiple", or "none"'
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Validate scroll configuration
|
|
283
|
-
if (
|
|
284
|
-
config.scroll?.itemSize !== "auto" &&
|
|
285
|
-
typeof config.scroll?.itemSize === "number" &&
|
|
286
|
-
config.scroll.itemSize <= 0
|
|
287
|
-
) {
|
|
288
|
-
throw new Error('List item size must be positive number or "auto"');
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Validate density
|
|
292
|
-
if (
|
|
293
|
-
config.density &&
|
|
294
|
-
!["default", "compact", "comfortable"].includes(config.density)
|
|
295
|
-
) {
|
|
296
|
-
throw new Error(
|
|
297
|
-
'List density must be "default", "compact", or "comfortable"'
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Validate variant
|
|
302
|
-
if (
|
|
303
|
-
config.variant &&
|
|
304
|
-
!["default", "dense", "comfortable"].includes(config.variant)
|
|
305
|
-
) {
|
|
306
|
-
throw new Error(
|
|
307
|
-
'List variant must be "default", "dense", or "comfortable"'
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Creates Collection configuration from List config
|
|
314
|
-
*/
|
|
315
|
-
export const getCollectionConfig = (config: ListConfig) => ({
|
|
316
|
-
adapter: config.adapter,
|
|
317
|
-
items: config.items,
|
|
318
|
-
cache: (config.collection as any)?.cache || {
|
|
319
|
-
enabled: true,
|
|
320
|
-
maxSize: 1000,
|
|
321
|
-
ttl: 300000, // 5 minutes
|
|
322
|
-
},
|
|
323
|
-
});
|
|
@@ -1,322 +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 } from "../types";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Adds public API methods to VList
|
|
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
|
-
// Listen for collection events
|
|
27
|
-
component.on?.("collection:range-loaded", () => {
|
|
28
|
-
isLoading = false;
|
|
29
|
-
});
|
|
30
|
-
component.on?.("collection:range-failed", () => {
|
|
31
|
-
isLoading = false;
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
...component,
|
|
36
|
-
|
|
37
|
-
// Data operations
|
|
38
|
-
setItems(items: T[]) {
|
|
39
|
-
if (component.viewport?.collection) {
|
|
40
|
-
component.viewport.collection.setItems(items);
|
|
41
|
-
} else {
|
|
42
|
-
component.items = items;
|
|
43
|
-
component.totalItems = items.length;
|
|
44
|
-
}
|
|
45
|
-
component.emit?.("items:set", { items, total: items.length });
|
|
46
|
-
},
|
|
47
|
-
|
|
48
|
-
getItems(): T[] {
|
|
49
|
-
// Collection is added directly to component, not to viewport.collection
|
|
50
|
-
if ((component as any).collection?.getItems) {
|
|
51
|
-
return (component as any).collection.getItems();
|
|
52
|
-
}
|
|
53
|
-
return component.items || [];
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
getVisibleItems(): T[] {
|
|
57
|
-
const range = component.viewport?.getVisibleRange() || {
|
|
58
|
-
start: 0,
|
|
59
|
-
end: 0,
|
|
60
|
-
};
|
|
61
|
-
const items = this.getItems();
|
|
62
|
-
return items.slice(range.start, range.end);
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
getItemCount(): number {
|
|
66
|
-
if (component.viewport?.collection) {
|
|
67
|
-
return component.viewport.collection.getTotalItems();
|
|
68
|
-
}
|
|
69
|
-
return component.totalItems || component.items?.length || 0;
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
// Loading operations
|
|
73
|
-
async loadRange(
|
|
74
|
-
page: number,
|
|
75
|
-
limit: number,
|
|
76
|
-
strategy: string = "page",
|
|
77
|
-
alignment?: string
|
|
78
|
-
) {
|
|
79
|
-
isLoading = true;
|
|
80
|
-
|
|
81
|
-
if (component.viewport?.collection) {
|
|
82
|
-
const offset = strategy === "page" ? (page - 1) * limit : page;
|
|
83
|
-
try {
|
|
84
|
-
await component.viewport.collection.loadRange(offset, limit);
|
|
85
|
-
component.emit?.("load", { page, limit, strategy });
|
|
86
|
-
|
|
87
|
-
// Scroll to the loaded range if alignment is specified
|
|
88
|
-
if (alignment) {
|
|
89
|
-
const index = offset;
|
|
90
|
-
this.scrollToIndex(index, alignment);
|
|
91
|
-
}
|
|
92
|
-
} catch (error) {
|
|
93
|
-
component.emit?.("error", { error });
|
|
94
|
-
throw error;
|
|
95
|
-
} finally {
|
|
96
|
-
isLoading = false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
|
|
101
|
-
// Data loading
|
|
102
|
-
loadNext: async function () {
|
|
103
|
-
console.log(`[VList] loadNext()`);
|
|
104
|
-
|
|
105
|
-
if (component.viewport?.collection) {
|
|
106
|
-
const totalItems = component.viewport.collection.getTotalItems();
|
|
107
|
-
const loadedRanges = component.viewport.collection.getLoadedRanges();
|
|
108
|
-
|
|
109
|
-
// Find the next unloaded range
|
|
110
|
-
let nextOffset = 0;
|
|
111
|
-
for (const rangeId of loadedRanges) {
|
|
112
|
-
const rangeEnd = (rangeId + 1) * (config.pagination?.limit || 20);
|
|
113
|
-
if (rangeEnd > nextOffset) {
|
|
114
|
-
nextOffset = rangeEnd;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (nextOffset < totalItems) {
|
|
119
|
-
await component.viewport.collection.loadRange(
|
|
120
|
-
nextOffset,
|
|
121
|
-
config.pagination?.limit || 20
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return Promise.resolve();
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
isLoading(): boolean {
|
|
130
|
-
return isLoading;
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
hasNext(): boolean {
|
|
134
|
-
if (component.viewport?.collection) {
|
|
135
|
-
const total = component.viewport.collection.getTotalItems();
|
|
136
|
-
const items = component.viewport.collection.getItems();
|
|
137
|
-
return items.length < total;
|
|
138
|
-
}
|
|
139
|
-
return false;
|
|
140
|
-
},
|
|
141
|
-
|
|
142
|
-
// Note: Selection operations are handled by the withSelection feature
|
|
143
|
-
// These are kept for backward compatibility but delegate to selection feature if available
|
|
144
|
-
getSelectedIds(): (string | number)[] {
|
|
145
|
-
if (typeof component.getSelectedIndices === "function") {
|
|
146
|
-
// Use selection feature if available
|
|
147
|
-
const items = this.getItems();
|
|
148
|
-
const indices = component.getSelectedIndices();
|
|
149
|
-
return indices.map((idx: number) => (items[idx] as any)?.id || idx);
|
|
150
|
-
}
|
|
151
|
-
// Fallback to local tracking
|
|
152
|
-
return Array.from(selectedIds);
|
|
153
|
-
},
|
|
154
|
-
|
|
155
|
-
selectItem(id: string | number) {
|
|
156
|
-
if (typeof component.selectItems === "function") {
|
|
157
|
-
// Use selection feature if available
|
|
158
|
-
const items = this.getItems();
|
|
159
|
-
const index = items.findIndex((item: any) => item.id === id);
|
|
160
|
-
if (index >= 0) {
|
|
161
|
-
component.selectItems([index]);
|
|
162
|
-
}
|
|
163
|
-
} else {
|
|
164
|
-
// Fallback to local tracking
|
|
165
|
-
selectedIds.add(id);
|
|
166
|
-
component.emit?.("selection:change", {
|
|
167
|
-
selected: this.getSelectedIds(),
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
|
|
172
|
-
deselectItem(id: string | number) {
|
|
173
|
-
if (typeof component.deselectItems === "function") {
|
|
174
|
-
// Use selection feature if available
|
|
175
|
-
const items = this.getItems();
|
|
176
|
-
const index = items.findIndex((item: any) => item.id === id);
|
|
177
|
-
if (index >= 0) {
|
|
178
|
-
component.deselectItems([index]);
|
|
179
|
-
}
|
|
180
|
-
} else {
|
|
181
|
-
// Fallback to local tracking
|
|
182
|
-
selectedIds.delete(id);
|
|
183
|
-
component.emit?.("selection:change", {
|
|
184
|
-
selected: this.getSelectedIds(),
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
clearSelection() {
|
|
190
|
-
if (typeof component.clearSelection === "function") {
|
|
191
|
-
// Use selection feature if available
|
|
192
|
-
component.clearSelection();
|
|
193
|
-
} else {
|
|
194
|
-
// Fallback to local tracking
|
|
195
|
-
selectedIds.clear();
|
|
196
|
-
component.emit?.("selection:change", { selected: [] });
|
|
197
|
-
}
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
// Scrolling methods
|
|
201
|
-
scrollToIndex: (
|
|
202
|
-
index: number,
|
|
203
|
-
alignment: "start" | "center" | "end" = "start",
|
|
204
|
-
animate?: boolean
|
|
205
|
-
) => {
|
|
206
|
-
if (component.viewport) {
|
|
207
|
-
component.viewport.scrollToIndex(index, alignment);
|
|
208
|
-
}
|
|
209
|
-
return Promise.resolve();
|
|
210
|
-
},
|
|
211
|
-
|
|
212
|
-
scrollToItem: async function (
|
|
213
|
-
itemId: string,
|
|
214
|
-
alignment: "start" | "center" | "end" = "start",
|
|
215
|
-
animate?: boolean
|
|
216
|
-
) {
|
|
217
|
-
const items = this.getItems();
|
|
218
|
-
const index = items.findIndex(
|
|
219
|
-
(item: any) => String(item.id) === String(itemId)
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
if (index >= 0) {
|
|
223
|
-
return this.scrollToIndex(index, alignment, animate);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
console.warn(`Item ${itemId} not found`);
|
|
227
|
-
return Promise.resolve();
|
|
228
|
-
},
|
|
229
|
-
|
|
230
|
-
scrollToTop: function () {
|
|
231
|
-
return this.scrollToIndex(0, "start");
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
scrollToBottom: function () {
|
|
235
|
-
const totalItems = this.getItemCount();
|
|
236
|
-
if (totalItems > 0) {
|
|
237
|
-
return this.scrollToIndex(totalItems - 1, "end");
|
|
238
|
-
}
|
|
239
|
-
return Promise.resolve();
|
|
240
|
-
},
|
|
241
|
-
|
|
242
|
-
scrollToPage: async function (
|
|
243
|
-
pageNum: number,
|
|
244
|
-
alignment: "start" | "center" | "end" = "start",
|
|
245
|
-
animate?: boolean
|
|
246
|
-
) {
|
|
247
|
-
// console.log(`[VList] scrollToPage(${pageNum})`);
|
|
248
|
-
|
|
249
|
-
// Get limit from config (rangeSize) or default
|
|
250
|
-
const limit = config.pagination?.limit || 20;
|
|
251
|
-
|
|
252
|
-
// Check if we're in cursor mode
|
|
253
|
-
if (config.pagination?.strategy === "cursor") {
|
|
254
|
-
// In cursor mode, we need to handle sequential loading
|
|
255
|
-
const collection = (component.viewport as any)?.collection;
|
|
256
|
-
if (collection) {
|
|
257
|
-
const loadedRanges = collection.getLoadedRanges();
|
|
258
|
-
const highestLoadedPage = loadedRanges.size;
|
|
259
|
-
|
|
260
|
-
if (pageNum > highestLoadedPage + 1) {
|
|
261
|
-
console.warn(
|
|
262
|
-
`[VList] Cannot jump to page ${pageNum} in cursor mode. ` +
|
|
263
|
-
`Loading pages sequentially from ${
|
|
264
|
-
highestLoadedPage + 1
|
|
265
|
-
} to ${pageNum}`
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Use viewport's scrollToPage if available
|
|
272
|
-
if ((component.viewport as any)?.scrollToPage) {
|
|
273
|
-
(component.viewport as any).scrollToPage(pageNum, limit, alignment);
|
|
274
|
-
} else {
|
|
275
|
-
// Fallback to scrollToIndex
|
|
276
|
-
const targetIndex = (pageNum - 1) * limit;
|
|
277
|
-
await this.scrollToIndex(targetIndex, alignment);
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
|
|
281
|
-
getScrollPosition: () => {
|
|
282
|
-
return component.viewport?.getScrollPosition() || 0;
|
|
283
|
-
},
|
|
284
|
-
|
|
285
|
-
getCurrentCursor: () => {
|
|
286
|
-
const collection = (component.viewport as any)?.collection;
|
|
287
|
-
return collection?.getCurrentCursor?.() || null;
|
|
288
|
-
},
|
|
289
|
-
|
|
290
|
-
setScrollAnimation(enabled: boolean) {
|
|
291
|
-
// Store animation preference (would be used by viewport scrolling feature)
|
|
292
|
-
component.scrollAnimation = enabled;
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
// State
|
|
296
|
-
getState() {
|
|
297
|
-
return {
|
|
298
|
-
items: this.getItems(),
|
|
299
|
-
totalItems: component.viewport?.collection
|
|
300
|
-
? component.viewport.collection.getTotalItems()
|
|
301
|
-
: component.totalItems || 0,
|
|
302
|
-
visibleRange: component.viewport?.getVisibleRange() || {
|
|
303
|
-
start: 0,
|
|
304
|
-
end: 0,
|
|
305
|
-
},
|
|
306
|
-
scrollPosition: component.viewport?.getScrollPosition() || 0,
|
|
307
|
-
selectedIds: this.getSelectedIds(),
|
|
308
|
-
isLoading: this.isLoading(),
|
|
309
|
-
};
|
|
310
|
-
},
|
|
311
|
-
|
|
312
|
-
// Lifecycle
|
|
313
|
-
destroy() {
|
|
314
|
-
if (component.viewport?.destroy) {
|
|
315
|
-
component.viewport.destroy();
|
|
316
|
-
}
|
|
317
|
-
selectedIds.clear();
|
|
318
|
-
component.emit?.("destroyed");
|
|
319
|
-
},
|
|
320
|
-
};
|
|
321
|
-
};
|
|
322
|
-
};
|