mtrl-addons 0.1.2 → 0.2.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 (117) hide show
  1. package/AI.md +28 -230
  2. package/CLAUDE.md +882 -0
  3. package/build.js +253 -24
  4. package/package.json +14 -4
  5. package/scripts/debug/vlist-selection.ts +121 -0
  6. package/src/components/index.ts +5 -41
  7. package/src/components/{list → vlist}/config.ts +66 -95
  8. package/src/components/vlist/constants.ts +23 -0
  9. package/src/components/vlist/features/api.ts +626 -0
  10. package/src/components/vlist/features/index.ts +10 -0
  11. package/src/components/vlist/features/selection.ts +436 -0
  12. package/src/components/vlist/features/viewport.ts +59 -0
  13. package/src/components/vlist/index.ts +17 -0
  14. package/src/components/{list → vlist}/types.ts +242 -32
  15. package/src/components/vlist/vlist.ts +92 -0
  16. package/src/core/compose/features/gestures/index.ts +227 -0
  17. package/src/core/compose/features/gestures/longpress.ts +383 -0
  18. package/src/core/compose/features/gestures/pan.ts +424 -0
  19. package/src/core/compose/features/gestures/pinch.ts +475 -0
  20. package/src/core/compose/features/gestures/rotate.ts +485 -0
  21. package/src/core/compose/features/gestures/swipe.ts +492 -0
  22. package/src/core/compose/features/gestures/tap.ts +334 -0
  23. package/src/core/compose/features/index.ts +2 -38
  24. package/src/core/compose/index.ts +13 -29
  25. package/src/core/gestures/index.ts +31 -0
  26. package/src/core/gestures/longpress.ts +68 -0
  27. package/src/core/gestures/manager.ts +418 -0
  28. package/src/core/gestures/pan.ts +48 -0
  29. package/src/core/gestures/pinch.ts +58 -0
  30. package/src/core/gestures/rotate.ts +58 -0
  31. package/src/core/gestures/swipe.ts +66 -0
  32. package/src/core/gestures/tap.ts +45 -0
  33. package/src/core/gestures/types.ts +387 -0
  34. package/src/core/gestures/utils.ts +128 -0
  35. package/src/core/index.ts +27 -151
  36. package/src/core/layout/schema.ts +153 -72
  37. package/src/core/layout/types.ts +5 -2
  38. package/src/core/viewport/constants.ts +145 -0
  39. package/src/core/viewport/features/base.ts +73 -0
  40. package/src/core/viewport/features/collection.ts +1182 -0
  41. package/src/core/viewport/features/events.ts +130 -0
  42. package/src/core/viewport/features/index.ts +20 -0
  43. package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +31 -34
  44. package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
  45. package/src/core/viewport/features/momentum.ts +269 -0
  46. package/src/core/viewport/features/placeholders.ts +335 -0
  47. package/src/core/viewport/features/rendering.ts +962 -0
  48. package/src/core/viewport/features/scrollbar.ts +434 -0
  49. package/src/core/viewport/features/scrolling.ts +634 -0
  50. package/src/core/viewport/features/utils.ts +94 -0
  51. package/src/core/viewport/features/virtual.ts +525 -0
  52. package/src/core/viewport/index.ts +31 -0
  53. package/src/core/viewport/types.ts +133 -0
  54. package/src/core/viewport/utils/speed-tracker.ts +79 -0
  55. package/src/core/viewport/viewport.ts +265 -0
  56. package/src/index.ts +0 -7
  57. package/src/styles/components/_vlist.scss +352 -0
  58. package/src/styles/index.scss +1 -1
  59. package/test/components/vlist-selection.test.ts +240 -0
  60. package/test/components/vlist.test.ts +63 -0
  61. package/test/core/collection/adapter.test.ts +161 -0
  62. package/bun.lock +0 -792
  63. package/src/components/list/api.ts +0 -314
  64. package/src/components/list/constants.ts +0 -56
  65. package/src/components/list/features/api.ts +0 -428
  66. package/src/components/list/features/index.ts +0 -31
  67. package/src/components/list/features/list-manager.ts +0 -502
  68. package/src/components/list/index.ts +0 -39
  69. package/src/components/list/list.ts +0 -234
  70. package/src/core/collection/base-collection.ts +0 -100
  71. package/src/core/collection/collection-composer.ts +0 -178
  72. package/src/core/collection/collection.ts +0 -745
  73. package/src/core/collection/constants.ts +0 -172
  74. package/src/core/collection/events.ts +0 -428
  75. package/src/core/collection/features/api/loading.ts +0 -279
  76. package/src/core/collection/features/operations/data-operations.ts +0 -147
  77. package/src/core/collection/index.ts +0 -104
  78. package/src/core/collection/state.ts +0 -497
  79. package/src/core/collection/types.ts +0 -404
  80. package/src/core/compose/features/collection.ts +0 -119
  81. package/src/core/compose/features/selection.ts +0 -213
  82. package/src/core/compose/features/styling.ts +0 -108
  83. package/src/core/list-manager/api.ts +0 -599
  84. package/src/core/list-manager/config.ts +0 -593
  85. package/src/core/list-manager/constants.ts +0 -268
  86. package/src/core/list-manager/features/api.ts +0 -58
  87. package/src/core/list-manager/features/collection/collection.ts +0 -705
  88. package/src/core/list-manager/features/collection/index.ts +0 -17
  89. package/src/core/list-manager/features/viewport/constants.ts +0 -42
  90. package/src/core/list-manager/features/viewport/index.ts +0 -16
  91. package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
  92. package/src/core/list-manager/features/viewport/rendering.ts +0 -575
  93. package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
  94. package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
  95. package/src/core/list-manager/features/viewport/template.ts +0 -220
  96. package/src/core/list-manager/features/viewport/viewport.ts +0 -654
  97. package/src/core/list-manager/features/viewport/virtual.ts +0 -309
  98. package/src/core/list-manager/index.ts +0 -279
  99. package/src/core/list-manager/list-manager.ts +0 -206
  100. package/src/core/list-manager/types.ts +0 -439
  101. package/src/core/list-manager/utils/calculations.ts +0 -290
  102. package/src/core/list-manager/utils/range-calculator.ts +0 -349
  103. package/src/core/list-manager/utils/speed-tracker.ts +0 -273
  104. package/src/styles/components/_list.scss +0 -244
  105. package/src/types/mtrl.d.ts +0 -6
  106. package/test/components/list.test.ts +0 -256
  107. package/test/core/collection/failed-ranges.test.ts +0 -270
  108. package/test/core/compose/features.test.ts +0 -183
  109. package/test/core/list-manager/features/collection.test.ts +0 -704
  110. package/test/core/list-manager/features/viewport.test.ts +0 -698
  111. package/test/core/list-manager/list-manager.test.ts +0 -593
  112. package/test/core/list-manager/utils/calculations.test.ts +0 -433
  113. package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
  114. package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
  115. package/tsconfig.build.json +0 -23
  116. /package/src/components/{list → vlist}/features.ts +0 -0
  117. /package/src/core/{compose → viewport}/features/performance.ts +0 -0
@@ -1,314 +0,0 @@
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
- });
@@ -1,56 +0,0 @@
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;