mtrl-addons 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/.cursorrules +117 -0
  2. package/AI.md +241 -0
  3. package/build.js +148 -0
  4. package/bun.lock +792 -0
  5. package/index.ts +7 -0
  6. package/package.json +10 -17
  7. package/scripts/analyze-orphaned-functions.ts +387 -0
  8. package/src/components/index.ts +45 -0
  9. package/src/components/list/api.ts +314 -0
  10. package/src/components/list/config.ts +352 -0
  11. package/src/components/list/constants.ts +56 -0
  12. package/src/components/list/features/api.ts +428 -0
  13. package/src/components/list/features/index.ts +31 -0
  14. package/src/components/list/features/list-manager.ts +502 -0
  15. package/src/components/list/features.ts +112 -0
  16. package/src/components/list/index.ts +39 -0
  17. package/src/components/list/list.ts +234 -0
  18. package/src/components/list/types.ts +513 -0
  19. package/src/core/collection/base-collection.ts +100 -0
  20. package/src/core/collection/collection-composer.ts +178 -0
  21. package/src/core/collection/collection.ts +745 -0
  22. package/src/core/collection/constants.ts +172 -0
  23. package/src/core/collection/events.ts +428 -0
  24. package/src/core/collection/features/api/loading.ts +279 -0
  25. package/src/core/collection/features/operations/data-operations.ts +147 -0
  26. package/src/core/collection/index.ts +104 -0
  27. package/src/core/collection/state.ts +497 -0
  28. package/src/core/collection/types.ts +404 -0
  29. package/src/core/compose/features/collection.ts +119 -0
  30. package/src/core/compose/features/index.ts +39 -0
  31. package/src/core/compose/features/performance.ts +161 -0
  32. package/src/core/compose/features/selection.ts +213 -0
  33. package/src/core/compose/features/styling.ts +108 -0
  34. package/src/core/compose/index.ts +31 -0
  35. package/src/core/index.ts +167 -0
  36. package/src/core/layout/config.ts +102 -0
  37. package/src/core/layout/index.ts +168 -0
  38. package/src/core/layout/jsx.ts +174 -0
  39. package/src/core/layout/schema.ts +963 -0
  40. package/src/core/layout/types.ts +92 -0
  41. package/src/core/list-manager/api.ts +599 -0
  42. package/src/core/list-manager/config.ts +593 -0
  43. package/src/core/list-manager/constants.ts +268 -0
  44. package/src/core/list-manager/features/api.ts +58 -0
  45. package/src/core/list-manager/features/collection/collection.ts +705 -0
  46. package/src/core/list-manager/features/collection/index.ts +17 -0
  47. package/src/core/list-manager/features/viewport/constants.ts +42 -0
  48. package/src/core/list-manager/features/viewport/index.ts +16 -0
  49. package/src/core/list-manager/features/viewport/item-size.ts +274 -0
  50. package/src/core/list-manager/features/viewport/loading.ts +263 -0
  51. package/src/core/list-manager/features/viewport/placeholders.ts +281 -0
  52. package/src/core/list-manager/features/viewport/rendering.ts +575 -0
  53. package/src/core/list-manager/features/viewport/scrollbar.ts +495 -0
  54. package/src/core/list-manager/features/viewport/scrolling.ts +795 -0
  55. package/src/core/list-manager/features/viewport/template.ts +220 -0
  56. package/src/core/list-manager/features/viewport/viewport.ts +654 -0
  57. package/src/core/list-manager/features/viewport/virtual.ts +309 -0
  58. package/src/core/list-manager/index.ts +279 -0
  59. package/src/core/list-manager/list-manager.ts +206 -0
  60. package/src/core/list-manager/types.ts +439 -0
  61. package/src/core/list-manager/utils/calculations.ts +290 -0
  62. package/src/core/list-manager/utils/range-calculator.ts +349 -0
  63. package/src/core/list-manager/utils/speed-tracker.ts +273 -0
  64. package/src/index.ts +17 -0
  65. package/src/styles/components/_list.scss +244 -0
  66. package/src/styles/index.scss +12 -0
  67. package/src/types/mtrl.d.ts +6 -0
  68. package/test/benchmarks/layout/advanced.test.ts +656 -0
  69. package/test/benchmarks/layout/comparison.test.ts +519 -0
  70. package/test/benchmarks/layout/performance-comparison.test.ts +274 -0
  71. package/test/benchmarks/layout/real-components.test.ts +733 -0
  72. package/test/benchmarks/layout/simple.test.ts +321 -0
  73. package/test/benchmarks/layout/stress.test.ts +990 -0
  74. package/test/collection/basic.test.ts +304 -0
  75. package/test/components/list.test.ts +256 -0
  76. package/test/core/collection/collection.test.ts +394 -0
  77. package/test/core/collection/failed-ranges.test.ts +270 -0
  78. package/test/core/compose/features.test.ts +183 -0
  79. package/test/core/layout/layout.test.ts +201 -0
  80. package/test/core/list-manager/features/collection.test.ts +704 -0
  81. package/test/core/list-manager/features/viewport.test.ts +698 -0
  82. package/test/core/list-manager/list-manager.test.ts +593 -0
  83. package/test/core/list-manager/utils/calculations.test.ts +433 -0
  84. package/test/core/list-manager/utils/range-calculator.test.ts +569 -0
  85. package/test/core/list-manager/utils/speed-tracker.test.ts +530 -0
  86. package/test/utils/dom-helpers.ts +275 -0
  87. package/test/utils/performance-helpers.ts +392 -0
  88. package/tsconfig.build.json +23 -0
  89. package/tsconfig.json +20 -0
  90. package/dist/index.d.ts +0 -5
  91. package/dist/index.js +0 -38
  92. package/dist/index.mjs +0 -8
@@ -0,0 +1,314 @@
1
+ import type { ListComponent, ListConfig, ListAPI } from "./types";
2
+
3
+ /**
4
+ * API configuration interface
5
+ */
6
+ export interface ApiConfig<T = any> {
7
+ component: ListComponent<T>;
8
+ config: ListConfig<T>;
9
+ }
10
+
11
+ /**
12
+ * Creates the public API layer for the List component
13
+ * Following mtrl's withAPI pattern
14
+ */
15
+ export const withAPI =
16
+ <T = any>(apiConfig: ApiConfig<T>) =>
17
+ <C extends Partial<ListComponent<T>>>(component: C): C & ListAPI<T> => {
18
+ const { config } = apiConfig;
19
+
20
+ // Store references to orchestration methods before API overwrites them
21
+ const orchestrationMethods = {
22
+ scrollToIndex: component.scrollToIndex,
23
+ scrollToTop: component.scrollToTop,
24
+ scrollToBottom: component.scrollToBottom,
25
+ loadData: component.loadData,
26
+ reload: component.reload,
27
+ clear: component.clear,
28
+ };
29
+
30
+ // Create clean public API interface
31
+ const api: ListAPI<T> = {
32
+ // Data management API
33
+ async loadData(): Promise<void> {
34
+ try {
35
+ if (orchestrationMethods.loadData) {
36
+ await orchestrationMethods.loadData();
37
+ } else {
38
+ await component.collection?.loadPage?.(1);
39
+ }
40
+ } catch (error) {
41
+ console.error("List: Failed to load data", error);
42
+ throw error;
43
+ }
44
+ },
45
+
46
+ async reload(): Promise<void> {
47
+ try {
48
+ if (orchestrationMethods.reload) {
49
+ await orchestrationMethods.reload();
50
+ } else {
51
+ await component.collection?.refresh?.();
52
+ }
53
+ } catch (error) {
54
+ console.error("List: Failed to reload data", error);
55
+ throw error;
56
+ }
57
+ },
58
+
59
+ clear(): void {
60
+ if (orchestrationMethods.clear) {
61
+ orchestrationMethods.clear();
62
+ } else {
63
+ component.collection?.clearItems?.();
64
+ component.clearSelection?.();
65
+ }
66
+ },
67
+
68
+ addItems(items: T[], position: "start" | "end" = "end"): void {
69
+ if (!Array.isArray(items)) {
70
+ throw new Error("Items must be an array");
71
+ }
72
+ component.addItems?.(items, position);
73
+ },
74
+
75
+ removeItems(indices: number[]): void {
76
+ if (!Array.isArray(indices)) {
77
+ throw new Error("Indices must be an array");
78
+ }
79
+ component.removeItems?.(indices);
80
+ },
81
+
82
+ updateItem(index: number, item: T): void {
83
+ if (typeof index !== "number" || index < 0) {
84
+ throw new Error("Index must be a non-negative number");
85
+ }
86
+ component.updateItem?.(index, item);
87
+ },
88
+
89
+ getItem(index: number): T | undefined {
90
+ if (typeof index !== "number") {
91
+ return undefined;
92
+ }
93
+ return component.getItem?.(index);
94
+ },
95
+
96
+ getItems(): T[] {
97
+ return component.getItems?.() || [];
98
+ },
99
+
100
+ getItemCount(): number {
101
+ return component.collection?.getSize?.() || 0;
102
+ },
103
+
104
+ // Scrolling API
105
+ async scrollToIndex(
106
+ index: number,
107
+ alignment: "start" | "center" | "end" = "start"
108
+ ): Promise<void> {
109
+ if (typeof index !== "number" || index < 0) {
110
+ throw new Error("Index must be a non-negative number");
111
+ }
112
+
113
+ // For virtual scrolling with infinite data, don't do strict bounds checking
114
+ // The orchestration layer will handle loading data as needed
115
+ try {
116
+ if (orchestrationMethods.scrollToIndex) {
117
+ await orchestrationMethods.scrollToIndex(index, alignment);
118
+ } else {
119
+ console.warn("scrollToIndex not available");
120
+ }
121
+ } catch (error) {
122
+ console.error("List: Failed to scroll to index", error);
123
+ throw error;
124
+ }
125
+ },
126
+
127
+ async scrollToTop(): Promise<void> {
128
+ try {
129
+ if (orchestrationMethods.scrollToTop) {
130
+ await orchestrationMethods.scrollToTop();
131
+ } else {
132
+ console.warn("scrollToTop not available");
133
+ }
134
+ } catch (error) {
135
+ console.error("List: Failed to scroll to top", error);
136
+ throw error;
137
+ }
138
+ },
139
+
140
+ async scrollToBottom(): Promise<void> {
141
+ try {
142
+ if (orchestrationMethods.scrollToBottom) {
143
+ await orchestrationMethods.scrollToBottom();
144
+ } else {
145
+ console.warn("scrollToBottom not available");
146
+ }
147
+ } catch (error) {
148
+ console.error("List: Failed to scroll to bottom", error);
149
+ throw error;
150
+ }
151
+ },
152
+
153
+ getScrollPosition(): number {
154
+ return component.getScrollPosition?.() || 0;
155
+ },
156
+
157
+ // Selection API
158
+ selectItems(indices: number[]): void {
159
+ if (!config.selection?.enabled) {
160
+ console.warn("List: Selection is not enabled");
161
+ return;
162
+ }
163
+
164
+ if (!Array.isArray(indices)) {
165
+ throw new Error("Indices must be an array");
166
+ }
167
+
168
+ const itemCount = this.getItemCount();
169
+ const validIndices = indices.filter(
170
+ (index) =>
171
+ typeof index === "number" && index >= 0 && index < itemCount
172
+ );
173
+
174
+ if (config.selection.mode === "single" && validIndices.length > 1) {
175
+ console.warn("List: Single selection mode allows only one item");
176
+ component.selectItems?.([validIndices[0]]);
177
+ } else {
178
+ component.selectItems?.(validIndices);
179
+ }
180
+ },
181
+
182
+ deselectItems(indices: number[]): void {
183
+ if (!config.selection?.enabled) {
184
+ console.warn("List: Selection is not enabled");
185
+ return;
186
+ }
187
+
188
+ if (!Array.isArray(indices)) {
189
+ throw new Error("Indices must be an array");
190
+ }
191
+
192
+ component.deselectItems?.(indices);
193
+ },
194
+
195
+ clearSelection(): void {
196
+ if (!config.selection?.enabled) {
197
+ console.warn("List: Selection is not enabled");
198
+ return;
199
+ }
200
+
201
+ component.clearSelection?.();
202
+ },
203
+
204
+ getSelectedItems(): T[] {
205
+ return component.getSelectedItems?.() || [];
206
+ },
207
+
208
+ getSelectedIndices(): number[] {
209
+ return component.getSelectedIndices?.() || [];
210
+ },
211
+
212
+ isSelected(index: number): boolean {
213
+ if (typeof index !== "number") {
214
+ return false;
215
+ }
216
+ return component.isSelected?.(index) || false;
217
+ },
218
+
219
+ // State API
220
+ getState() {
221
+ return (
222
+ component.getState?.() || {
223
+ isLoading: false,
224
+ error: null,
225
+ isEmpty: true,
226
+ scrollTop: 0,
227
+ visibleRange: { start: 0, end: 0, count: 0 },
228
+ renderRange: { start: 0, end: 0, count: 0 },
229
+ selectedIndices: [],
230
+ totalItems: 0,
231
+ isVirtual: false,
232
+ }
233
+ );
234
+ },
235
+
236
+ isLoading(): boolean {
237
+ return component.isLoading?.() || false;
238
+ },
239
+
240
+ hasError(): boolean {
241
+ return component.hasError?.() || false;
242
+ },
243
+
244
+ isEmpty(): boolean {
245
+ return component.isEmpty?.() || true;
246
+ },
247
+
248
+ // Rendering API
249
+ render(): void {
250
+ component.render?.();
251
+ },
252
+
253
+ updateViewport(): void {
254
+ component.updateViewport?.();
255
+ },
256
+
257
+ getVisibleRange() {
258
+ return component.getVisibleRange?.() || { start: 0, end: 0, count: 0 };
259
+ },
260
+
261
+ getRenderRange() {
262
+ return component.getRenderRange?.() || { start: 0, end: 0, count: 0 };
263
+ },
264
+
265
+ // Template API
266
+ setTemplate(template) {
267
+ if (typeof template !== "function") {
268
+ throw new Error("Template must be a function");
269
+ }
270
+ component.setTemplate?.(template);
271
+ },
272
+
273
+ setLoadingTemplate(template) {
274
+ // Store in config for future use
275
+ config.loadingTemplate = template;
276
+ },
277
+
278
+ setEmptyTemplate(template) {
279
+ // Store in config for future use
280
+ config.emptyTemplate = template;
281
+ },
282
+
283
+ setErrorTemplate(template) {
284
+ // Store in config for future use
285
+ config.errorTemplate = template;
286
+ },
287
+
288
+ // Configuration API
289
+ updateConfig(newConfig: Partial<ListConfig<T>>): void {
290
+ if (typeof newConfig !== "object" || newConfig === null) {
291
+ throw new Error("Config must be an object");
292
+ }
293
+
294
+ component.updateConfig?.(newConfig);
295
+ },
296
+
297
+ getConfig(): ListConfig<T> {
298
+ return component.getConfig?.() || config;
299
+ },
300
+ };
301
+
302
+ // Return component with API methods
303
+ return Object.assign(component, api) as C & ListAPI<T>;
304
+ };
305
+
306
+ /**
307
+ * Helper to get API configuration
308
+ */
309
+ export const getApiConfig = <T = any>(
310
+ component: ListComponent<T>
311
+ ): ApiConfig<T> => ({
312
+ component,
313
+ config: component.config,
314
+ });
@@ -0,0 +1,352 @@
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 {
9
+ createComponentConfig,
10
+ createElementConfig as coreCreateElementConfig,
11
+ } from "mtrl/src/core/config/component";
12
+ import { LIST_DEFAULTS, LIST_CLASSES } from "./constants";
13
+ import type { ListConfig, ListItem } from "./types";
14
+ import type { ListComponent } from "./types";
15
+ import { DATA_PAGINATION } from "../../core/collection/constants";
16
+ import { VIRTUAL_SCROLLING } from "../../core/list-manager/constants";
17
+ import {
18
+ convertRenderItemToTemplate,
19
+ getDefaultTemplate,
20
+ } from "../../core/list-manager/features/viewport/template";
21
+
22
+ /**
23
+ * Default configuration for the mtrl-addons List component
24
+ */
25
+ export const defaultConfig: Partial<ListConfig> = {
26
+ // Collection settings (for API-connected lists)
27
+ adapter: undefined,
28
+
29
+ // Static data (for in-memory lists)
30
+ items: [],
31
+
32
+ // Template settings
33
+ template: undefined, // Will use default template if not provided
34
+
35
+ // Selection settings
36
+ selection: {
37
+ enabled: false,
38
+ mode: "none",
39
+ selectedIndices: [],
40
+ },
41
+
42
+ // Scroll settings
43
+ scroll: {
44
+ virtual: true,
45
+ itemSize: "auto",
46
+ estimatedItemSize: 50,
47
+ overscan: 5,
48
+ animation: false,
49
+ restorePosition: false,
50
+ },
51
+
52
+ // Component settings
53
+ className: LIST_CLASSES.BASE,
54
+ prefix: "mtrl",
55
+ componentName: "list",
56
+ ariaLabel: "List",
57
+ debug: false,
58
+ };
59
+
60
+ /**
61
+ * Creates the base configuration for List component
62
+ * @param {ListConfig} config - User provided configuration
63
+ * @returns {ListConfig} Complete configuration with defaults applied
64
+ */
65
+ export function createBaseConfig<T extends ListItem = ListItem>(
66
+ config: ListConfig<T> = {}
67
+ ): Required<ListConfig<T>> {
68
+ console.log("🔧 [MTRL-ADDONS-LIST] Creating base configuration");
69
+
70
+ // Validate required configuration
71
+ if (!config.items && !config.adapter) {
72
+ throw new Error(
73
+ "List requires either static items or an adapter for data loading"
74
+ );
75
+ }
76
+
77
+ // Use mtrl core config system for proper merging and validation
78
+ const mergedConfig = createComponentConfig(defaultConfig, config, "list");
79
+
80
+ // Convert renderItem object to template function if provided
81
+ if (
82
+ (config as any).renderItem &&
83
+ typeof (config as any).renderItem === "object" &&
84
+ !mergedConfig.template
85
+ ) {
86
+ mergedConfig.template = convertRenderItemToTemplate(
87
+ (config as any).renderItem
88
+ );
89
+ }
90
+
91
+ // Validate selection configuration
92
+ if (
93
+ mergedConfig.selection?.enabled &&
94
+ mergedConfig.selection?.mode === undefined
95
+ ) {
96
+ mergedConfig.selection.mode = "single";
97
+ }
98
+
99
+ return mergedConfig as Required<ListConfig<T>>;
100
+ }
101
+
102
+ /**
103
+ * Creates element configuration for withElement
104
+ * @param {ListConfig} config - List configuration
105
+ * @returns {Object} Element configuration
106
+ */
107
+ export function getElementConfig<T extends ListItem = ListItem>(
108
+ config: ListConfig<T>
109
+ ): any {
110
+ const attributes = {
111
+ "data-component": "list",
112
+ "data-addons": "true",
113
+ role: "list",
114
+ "aria-label": config.ariaLabel || "List",
115
+ };
116
+
117
+ // Create element config using mtrl core system
118
+ return coreCreateElementConfig(config, {
119
+ tag: "div",
120
+ attributes,
121
+ className: [LIST_CLASSES.BASE, LIST_CLASSES.ADDONS], // Clean: list list-addons
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Creates API configuration for the List component
127
+ * @param {Object} component - Component with list functionality
128
+ * @returns {Object} API configuration object for withApi
129
+ */
130
+ export function getApiConfig<T extends ListItem = ListItem>(component: any) {
131
+ return {
132
+ // Data operations
133
+ data: {
134
+ add: component.add,
135
+ update: component.update,
136
+ remove: component.remove,
137
+ clear: component.clear,
138
+ refresh: component.refresh,
139
+ getItems: component.getItems,
140
+ getItem: component.getItem,
141
+ query: component.query,
142
+ sort: component.sort,
143
+ getSize: component.getSize,
144
+ isEmpty: component.isEmpty,
145
+ isLoading: component.isLoading,
146
+ getError: component.getError,
147
+ },
148
+
149
+ // Selection operations (if enabled)
150
+ selection: component.config?.selection?.enabled
151
+ ? {
152
+ selectItem: component.selectItem,
153
+ deselectItem: component.deselectItem,
154
+ selectAll: component.selectAll,
155
+ deselectAll: component.deselectAll,
156
+ getSelectedItems: component.getSelectedItems,
157
+ getSelectedIds: component.getSelectedIds,
158
+ }
159
+ : undefined,
160
+
161
+ // Scrolling operations
162
+ scrolling: {
163
+ scrollToItem: component.scrollToItem,
164
+ scrollToIndex: component.scrollToIndex,
165
+ scrollToPage: component.scrollToPage,
166
+ },
167
+
168
+ // Performance operations
169
+ performance: {
170
+ getMetrics: component.getMetrics,
171
+ resetMetrics: component.resetMetrics,
172
+ },
173
+
174
+ // Template operations
175
+ template: {
176
+ setTemplate: component.setTemplate,
177
+ getTemplate: component.getTemplate,
178
+ },
179
+
180
+ // Events system
181
+ events: {
182
+ on: component.on,
183
+ off: component.off,
184
+ emit: component.emit,
185
+ subscribe: component.subscribe,
186
+ },
187
+
188
+ // Lifecycle operations
189
+ lifecycle: {
190
+ destroy: component.destroy,
191
+ },
192
+
193
+ // Configuration access
194
+ config: {
195
+ selection: component.config?.selection,
196
+ scroll: component.config?.scroll,
197
+ },
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Validates list configuration
203
+ */
204
+ export const validateConfig = (config: ListConfig): void => {
205
+ // Validate container
206
+ if (config.container) {
207
+ if (typeof config.container === "string") {
208
+ const element = document.querySelector(config.container);
209
+ if (!element) {
210
+ throw new Error(
211
+ `List container element not found: ${config.container}`
212
+ );
213
+ }
214
+ } else if (!(config.container instanceof HTMLElement)) {
215
+ throw new Error(
216
+ "List container must be an HTMLElement or CSS selector string"
217
+ );
218
+ }
219
+ }
220
+
221
+ // Validate template
222
+ if (config.template && typeof config.template !== "function") {
223
+ throw new Error("List template must be a function");
224
+ }
225
+
226
+ // Validate items
227
+ if (config.items && !Array.isArray(config.items)) {
228
+ throw new Error("List items must be an array");
229
+ }
230
+
231
+ // Validate selection mode
232
+ if (
233
+ config.selection?.mode &&
234
+ !["single", "multiple", "none"].includes(config.selection.mode)
235
+ ) {
236
+ throw new Error(
237
+ 'List selection mode must be "single", "multiple", or "none"'
238
+ );
239
+ }
240
+
241
+ // Validate scroll configuration
242
+ if (
243
+ config.scroll?.itemSize !== "auto" &&
244
+ typeof config.scroll?.itemSize === "number" &&
245
+ config.scroll.itemSize <= 0
246
+ ) {
247
+ throw new Error('List item size must be positive number or "auto"');
248
+ }
249
+
250
+ // Validate density
251
+ if (
252
+ config.density &&
253
+ !["default", "compact", "comfortable"].includes(config.density)
254
+ ) {
255
+ throw new Error(
256
+ 'List density must be "default", "compact", or "comfortable"'
257
+ );
258
+ }
259
+
260
+ // Validate variant
261
+ if (
262
+ config.variant &&
263
+ !["default", "dense", "comfortable"].includes(config.variant)
264
+ ) {
265
+ throw new Error(
266
+ 'List variant must be "default", "dense", or "comfortable"'
267
+ );
268
+ }
269
+ };
270
+
271
+ /**
272
+ * Creates Collection configuration from List config
273
+ */
274
+ export const getCollectionConfig = (config: ListConfig) => ({
275
+ adapter: config.adapter,
276
+ items: config.items,
277
+ pageSize: config.collection?.limit || DATA_PAGINATION.DEFAULT_PAGE_SIZE,
278
+ cache: config.collection?.cache || {
279
+ enabled: true,
280
+ maxSize: 1000,
281
+ ttl: 300000, // 5 minutes
282
+ },
283
+ });
284
+
285
+ /**
286
+ * Creates List Manager configuration from List config
287
+ */
288
+ export const getListManagerConfig = (config: ListConfig) => ({
289
+ // Container
290
+ container: config.container,
291
+
292
+ // Collection configuration
293
+ collection: getCollectionConfig(config),
294
+
295
+ // Template configuration
296
+ template: {
297
+ template: config.template,
298
+ },
299
+
300
+ // Component prefix
301
+ prefix: config.prefix || "mtrl",
302
+
303
+ // Orientation configuration
304
+ orientation: {
305
+ orientation: config.orientation?.orientation || "vertical",
306
+ autoDetect: config.orientation?.autoDetect || false,
307
+ reverse: config.orientation?.reverse || false,
308
+ crossAxisAlignment: config.orientation?.crossAxisAlignment || "stretch",
309
+ },
310
+
311
+ // Virtual scrolling
312
+ virtual: {
313
+ enabled: config.scroll?.virtual ?? true,
314
+ itemSize: config.scroll?.itemSize ?? "auto",
315
+ estimatedItemSize: config.scroll?.estimatedItemSize ?? 50,
316
+ overscan: config.scroll?.overscan ?? VIRTUAL_SCROLLING.DEFAULT_OVERSCAN,
317
+ ...config.listManager?.virtual,
318
+ },
319
+
320
+ // Element recycling
321
+ recycling: {
322
+ enabled: config.listManager?.recycling?.enabled ?? true,
323
+ maxPoolSize: config.listManager?.recycling?.maxPoolSize ?? 100,
324
+ minPoolSize: config.listManager?.recycling?.minPoolSize ?? 10,
325
+ ...config.listManager?.recycling,
326
+ },
327
+
328
+ // Intersection observers
329
+ intersection: {
330
+ pagination: {
331
+ enabled: true,
332
+ rootMargin: "200px",
333
+ threshold: 0.1,
334
+ ...config.listManager?.intersection?.pagination,
335
+ },
336
+ loading: {
337
+ enabled: true,
338
+ ...config.listManager?.intersection?.loading,
339
+ },
340
+ },
341
+
342
+ // Performance configuration
343
+ performance: {
344
+ frameScheduling: true,
345
+ memoryCleanup: true,
346
+ ...config.listManager?.performance,
347
+ },
348
+
349
+ // Debug and styling
350
+ debug: config.debug || false,
351
+ componentName: "list-manager",
352
+ });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * mtrl-addons List Component Constants
3
+ *
4
+ * Constants for the addons list component.
5
+ * Note: Base mtrl list functionality is handled by mtrl core.
6
+ * These constants are for addons-specific features only.
7
+ */
8
+
9
+ /**
10
+ * CSS class names for List component
11
+ * Following BEM convention: component__element--modifier
12
+ * Note: mtrl prefix is added automatically by core DOM classes system
13
+ */
14
+ export const LIST_CLASSES = {
15
+ BASE: "list",
16
+ ADDONS: "list-addons", // Addons-specific class
17
+ } as const;
18
+
19
+ /**
20
+ * Default values for List component configuration
21
+ * These supplement mtrl's base list defaults with addons-specific values
22
+ */
23
+ export const LIST_DEFAULTS = {
24
+ // Collection defaults (addons-specific)
25
+ INITIAL_CAPACITY: 100,
26
+ RENDER_BUFFER_SIZE: 10,
27
+ RENDER_DEBOUNCE: 16, // ~1 frame at 60fps
28
+
29
+ // Template defaults (addons-specific)
30
+ TEMPLATE_ENGINE: "object" as const,
31
+
32
+ // Performance defaults (addons-specific)
33
+ PERFORMANCE_TRACKING: true,
34
+ } as const;
35
+
36
+ /**
37
+ * Event names for List component
38
+ * Following mtrl event naming conventions
39
+ */
40
+ export const LIST_EVENTS = {
41
+ // Core events (inherited from mtrl base)
42
+ CREATED: "list:created",
43
+ DESTROYED: "list:destroyed",
44
+
45
+ // Data events (addons-specific)
46
+ DATA_LOADED: "list:data-loaded",
47
+ DATA_ERROR: "list:data-error",
48
+ DATA_UPDATED: "list:data-updated",
49
+
50
+ // Selection events (addons-specific enhancements)
51
+ SELECTION_CHANGED: "list:selection-changed",
52
+ SELECTION_CLEARED: "list:selection-cleared",
53
+
54
+ // Performance events (addons-specific)
55
+ PERFORMANCE_METRICS: "list:performance-metrics",
56
+ } as const;