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,234 +0,0 @@
1
- /**
2
- * mtrl-addons List Component
3
- *
4
- * Next-generation list component built on the collection system.
5
- * Uses mtrl's compose system with addons-specific features.
6
- * Follows mtrl patterns for component architecture.
7
- */
8
-
9
- import type {
10
- ListConfig,
11
- ListComponent,
12
- ListItem,
13
- ListPerformanceMetrics,
14
- } from "./types";
15
-
16
- // Import mtrl compose system
17
- import { pipe } from "mtrl/src/core/compose/pipe";
18
- import { createBase, withElement } from "mtrl/src/core/compose/component";
19
- import { withEvents, withLifecycle } from "mtrl/src/core/compose/features";
20
-
21
- // Import list-specific utilities
22
- import { createBaseConfig, getElementConfig, getApiConfig } from "./config";
23
- import { LIST_CLASSES } from "./constants";
24
- import { withListManager } from "./features/list-manager";
25
- import { withAPI } from "./api";
26
-
27
- /**
28
- * Creates list-specific configuration from user config
29
- */
30
- function createListConfig<T extends ListItem = ListItem>(
31
- config: ListConfig<T>
32
- ) {
33
- return {
34
- ...config,
35
- componentName: "list",
36
- prefix: "mtrl",
37
- className: config.className || "addons-list",
38
- ariaLabel: config.ariaLabel || "List",
39
- interactive: true, // Enable touch support
40
- };
41
- }
42
-
43
- /**
44
- * Default list item template
45
- */
46
- function getDefaultListTemplate() {
47
- return {
48
- tag: "div",
49
- className: "mtrl-list-item",
50
- attributes: { "data-id": "{{id}}", role: "listitem" },
51
- children: [
52
- {
53
- tag: "div",
54
- className: "mtrl-list-item__content",
55
- textContent: "{{name || id}}",
56
- },
57
- ],
58
- };
59
- }
60
-
61
- /**
62
- * Creates a new List component using the 3-layer architecture
63
- *
64
- * The List component provides high-performance virtual scrolling with:
65
- * - Collection (Data Layer): Pure data management with API integration
66
- * - List Manager (Performance Layer): Virtual scrolling, element recycling
67
- * - List Component (Presentation Layer): mtrl integration and user interface
68
- *
69
- * This component supports both static data and API-driven dynamic data with
70
- * infinite scrolling, intelligent caching, and optimal DOM performance.
71
- *
72
- * @param {ListConfig<T>} config - List configuration options
73
- * @returns {ListComponent<T>} A fully configured list component instance
74
- *
75
- * @throws {Error} If list creation fails due to invalid configuration
76
- *
77
- * @example
78
- * ```typescript
79
- * // Create a simple static list
80
- * const simpleList = createList({
81
- * items: ['Apple', 'Banana', 'Cherry'],
82
- * container: '#my-list'
83
- * });
84
- *
85
- * // Create an API-driven list with virtual scrolling
86
- * const apiList = createList({
87
- * collection: {
88
- * baseUrl: 'https://api.example.com',
89
- * endpoint: '/items',
90
- * pageSize: 50
91
- * },
92
- * scroll: {
93
- * virtual: true,
94
- * itemSize: 60,
95
- * overscan: 5
96
- * },
97
- * selection: {
98
- * enabled: true,
99
- * mode: 'multiple'
100
- * },
101
- * template: (item, index) => `
102
- * <div class="item">
103
- * <h3>${item.title}</h3>
104
- * <p>${item.description}</p>
105
- * </div>
106
- * `,
107
- * on: {
108
- * onItemClick: (item, index) => {
109
- * console.log('Clicked:', item);
110
- * },
111
- * onLoadMore: async (direction) => {
112
- * console.log('Load more:', direction);
113
- * }
114
- * }
115
- * });
116
- *
117
- * // Add to DOM
118
- * document.querySelector('#app').appendChild(apiList.element);
119
- *
120
- * // Use API methods
121
- * await apiList.loadData();
122
- * apiList.selectItems([0, 1, 2]);
123
- * await apiList.scrollToIndex(100);
124
- * ```
125
- *
126
- * @category Components
127
- * @module components/list
128
- */
129
- export const createList = <T = any>(
130
- config: ListConfig<T> = {}
131
- ): ListComponent<T> => {
132
- try {
133
- // Validate and create base configuration
134
- const baseConfig = createBaseConfig(config as any);
135
-
136
- console.log(`📋 Creating List component with simplified architecture:
137
- - List Manager (with built-in Collection): ${
138
- baseConfig.items && baseConfig.items.length > 0 ? "Static" : "API"
139
- } data source
140
- - Virtual scrolling: true, Element recycling: true
141
- - Template: ${!!baseConfig.template}, Selection: ${!!baseConfig.selection
142
- ?.enabled}`);
143
-
144
- // Create the List component through functional composition
145
- const component = pipe(
146
- // 1. Foundation layer
147
- createBase, // Base component with event system
148
- withEvents(), // Event handling capabilities
149
- withElement(getElementConfig(baseConfig)), // DOM element creation
150
-
151
- // 2. Core integration layer
152
- withListManager<T>(baseConfig as any), // Simplified List Manager with built-in Collection
153
-
154
- // 3. Component lifecycle
155
- withLifecycle(), // Lifecycle management
156
-
157
- // 4. Public API layer
158
- (comp) => withAPI({ component: comp as any, config: baseConfig })(comp) // Clean public API
159
- )(baseConfig);
160
-
161
- // Set up initial event handlers from config
162
- if (baseConfig.on && typeof component.on === "function") {
163
- // Data events
164
- if (baseConfig.on.onLoadMore) {
165
- component.on("load:more", ({ direction }: { direction: string }) => {
166
- baseConfig.on!.onLoadMore!(direction as any);
167
- });
168
- }
169
-
170
- // Scroll events
171
- if (baseConfig.on.onScroll) {
172
- component.on(
173
- "scroll:change",
174
- ({
175
- scrollTop,
176
- direction,
177
- }: {
178
- scrollTop: number;
179
- direction: string;
180
- }) => {
181
- baseConfig.on!.onScroll!(scrollTop, direction as any);
182
- }
183
- );
184
- }
185
-
186
- // Viewport events
187
- if (baseConfig.on.onViewportChange) {
188
- component.on(
189
- "viewport:change",
190
- ({ visibleRange }: { visibleRange: any }) => {
191
- baseConfig.on!.onViewportChange!(visibleRange);
192
- }
193
- );
194
- }
195
-
196
- // Selection events
197
- if (baseConfig.on.onSelectionChange) {
198
- component.on(
199
- "selection:change",
200
- ({
201
- selectedItems,
202
- selectedIndices,
203
- }: {
204
- selectedItems: T[];
205
- selectedIndices: number[];
206
- }) => {
207
- baseConfig.on!.onSelectionChange!(
208
- selectedItems as any,
209
- selectedIndices
210
- );
211
- }
212
- );
213
- }
214
-
215
- // Item events are handled in orchestration layer
216
- }
217
-
218
- // Initialize component
219
- if (component.lifecycle?.init) {
220
- component.lifecycle.init();
221
- }
222
-
223
- console.log("✅ List component created successfully");
224
- return component as ListComponent<T>;
225
- } catch (error) {
226
- console.error("❌ List creation error:", error);
227
- throw new Error(`Failed to create list: ${(error as Error).message}`);
228
- }
229
- };
230
-
231
- /**
232
- * Export the main component creation function
233
- */
234
- export default createList;
@@ -1,100 +0,0 @@
1
- /**
2
- * Base Collection (Minimal Core)
3
- *
4
- * Pure data storage with zero features - everything else is plugins
5
- */
6
-
7
- import type { CollectionItem, CollectionConfig, BaseCollection } from "./types";
8
- import { createCollectionState, type CollectionDataState } from "./state";
9
- import { createCollectionEventEmitter } from "./events";
10
- import { COLLECTION_DEFAULTS, DATA_LOGGING } from "./constants";
11
-
12
- /**
13
- * Creates the minimal base collection
14
- * All features (persistence, validation, etc.) are added via plugins
15
- */
16
- export function createBaseCollection<T extends CollectionItem = CollectionItem>(
17
- config: CollectionConfig<T> = {}
18
- ): BaseCollection<T> {
19
- // Initialize minimal state
20
- const stateStore = createCollectionState<T>({
21
- items: config.items || [],
22
- totalCount: config.items?.length || 0,
23
- pageSize: config.pageSize || COLLECTION_DEFAULTS.PAGE_SIZE,
24
- });
25
-
26
- // Event system
27
- const eventEmitter = createCollectionEventEmitter<T>();
28
-
29
- // Lifecycle tracking
30
- let isDestroyed = false;
31
-
32
- /**
33
- * Base collection API - minimal data operations only
34
- */
35
- const baseCollection: BaseCollection<T> = {
36
- // Core data access
37
- getItems(): T[] {
38
- return stateStore.get().items;
39
- },
40
-
41
- getItem(id: string): T | undefined {
42
- return stateStore.get().items.find((item) => item.id === id);
43
- },
44
-
45
- getSize(): number {
46
- return stateStore.get().items.length;
47
- },
48
-
49
- getTotalCount(): number {
50
- return stateStore.get().totalCount;
51
- },
52
-
53
- // Basic state
54
- isLoading(): boolean {
55
- return stateStore.get().loading;
56
- },
57
-
58
- getError(): Error | null {
59
- return stateStore.get().error;
60
- },
61
-
62
- // State management (for plugins)
63
- getState(): CollectionDataState<T> {
64
- return stateStore.get();
65
- },
66
-
67
- setState(newState: Partial<CollectionDataState<T>>): void {
68
- stateStore.set(newState);
69
- },
70
-
71
- // Event system (for plugins)
72
- subscribe(observer: (payload: any) => void) {
73
- return eventEmitter.subscribe(observer);
74
- },
75
-
76
- emit(event: string, data: any): void {
77
- eventEmitter.emit(event as any, data);
78
- },
79
-
80
- // Lifecycle
81
- destroy(): void {
82
- if (isDestroyed) return;
83
-
84
- stateStore.destroy();
85
- eventEmitter.destroy();
86
- isDestroyed = true;
87
-
88
- console.log(`${DATA_LOGGING.PREFIX} Base collection destroyed`);
89
- },
90
-
91
- // Plugin support
92
- _config: config,
93
- _stateStore: stateStore,
94
- _eventEmitter: eventEmitter,
95
- };
96
-
97
- console.log(`${DATA_LOGGING.PREFIX} Base collection created`);
98
-
99
- return baseCollection;
100
- }
@@ -1,178 +0,0 @@
1
- /**
2
- * Collection Composer (Following Blueprint Architecture)
3
- *
4
- * Uses mtrl's pipe composition pattern to build full Collection from plugins
5
- */
6
-
7
- import type { CollectionItem, CollectionConfig, Collection } from "./types";
8
- import { createBaseCollection } from "./base-collection";
9
- import { withLoading } from "./features/api/loading";
10
- import { withDataOperations } from "./features/operations/data-operations";
11
-
12
- // TODO: Import from mtrl core when available
13
- // For now, implement simple pipe function
14
- function pipe<T>(...fns: Function[]): (value: T) => any {
15
- return (value: T) => fns.reduce((acc, fn) => fn(acc), value);
16
- }
17
-
18
- /**
19
- * Creates a full Collection following the blueprint architecture
20
- *
21
- * Uses functional composition to build features from plugins
22
- */
23
- export function createCollection<T extends CollectionItem = CollectionItem>(
24
- config: CollectionConfig<T> = {}
25
- ): Collection<T> {
26
- // Create full collection using composition pattern
27
- let collection = createBaseCollection<T>(config);
28
-
29
- // Apply plugins sequentially
30
- collection = withDataOperations<T>()(collection);
31
- collection = withLoading<T>({
32
- pageSize: config.pageSize,
33
- sequential: true,
34
- })(collection);
35
-
36
- // TODO: Add more plugins as they're created
37
- // collection = withValidation<T>(config.validation)(collection);
38
- // collection = withCaching<T>(config.cache)(collection);
39
- // collection = withPersistence<T>(config.persistence)(collection);
40
-
41
- // The collection now has all the features from plugins
42
- // but still needs some additional methods to match the full Collection interface
43
-
44
- /**
45
- * Data queries (could be moved to a plugin later)
46
- */
47
- const filter = (predicate: (item: T) => boolean): T[] => {
48
- return collection.getItems().filter(predicate);
49
- };
50
-
51
- const sort = (compareFn: (a: T, b: T) => number): T[] => {
52
- const items = [...collection.getItems()];
53
- return items.sort(compareFn);
54
- };
55
-
56
- const search = (query: string, fields: string[] = ["id"]): T[] => {
57
- if (query.length < 2) {
58
- return collection.getItems();
59
- }
60
-
61
- const searchQuery = query.toLowerCase();
62
- return collection.getItems().filter((item) => {
63
- return fields.some((field) => {
64
- const fieldValue = (item as any)[field];
65
- return (
66
- fieldValue &&
67
- fieldValue.toString().toLowerCase().includes(searchQuery)
68
- );
69
- });
70
- });
71
- };
72
-
73
- const aggregate = (operations: any[]): any => {
74
- const items = collection.getItems();
75
- const results: Record<string, any> = {};
76
-
77
- operations.forEach((op) => {
78
- const alias = op.alias || `${op.operation}_${op.field}`;
79
-
80
- switch (op.operation) {
81
- case "count":
82
- results[alias] = items.length;
83
- break;
84
- case "sum":
85
- results[alias] = items.reduce(
86
- (sum, item) => sum + ((item as any)[op.field] || 0),
87
- 0
88
- );
89
- break;
90
- case "avg":
91
- const sum = items.reduce(
92
- (sum, item) => sum + ((item as any)[op.field] || 0),
93
- 0
94
- );
95
- results[alias] = items.length > 0 ? sum / items.length : 0;
96
- break;
97
- case "min":
98
- results[alias] = Math.min(
99
- ...items.map((item) => (item as any)[op.field] || 0)
100
- );
101
- break;
102
- case "max":
103
- results[alias] = Math.max(
104
- ...items.map((item) => (item as any)[op.field] || 0)
105
- );
106
- break;
107
- case "distinct":
108
- results[alias] = [
109
- ...new Set(items.map((item) => (item as any)[op.field])),
110
- ];
111
- break;
112
- }
113
- });
114
-
115
- return results;
116
- };
117
-
118
- /**
119
- * Persistence methods (stubs for now - would be added via plugins)
120
- */
121
- const save = async (): Promise<void> => {
122
- console.warn("Save method requires persistence plugin");
123
- };
124
-
125
- const load = async (): Promise<void> => {
126
- console.warn("Load method requires persistence plugin");
127
- };
128
-
129
- const clearCache = async (): Promise<void> => {
130
- collection.setState({
131
- items: [],
132
- filteredItems: [],
133
- totalCount: 0,
134
- });
135
- };
136
-
137
- const sync = async (): Promise<void> => {
138
- console.warn("Sync method requires sync plugin");
139
- };
140
-
141
- const prefetch = async (pages: number[]): Promise<void> => {
142
- console.warn("Prefetch method requires prefetch plugin");
143
- };
144
-
145
- /**
146
- * Plugin system (stub for now)
147
- */
148
- const use = (plugin: any): Collection<T> => {
149
- console.warn("Plugin system not yet implemented");
150
- return fullCollection;
151
- };
152
-
153
- // Create the full collection object
154
- const fullCollection: Collection<T> = Object.assign(collection, {
155
- // Data queries
156
- filter,
157
- sort,
158
- search,
159
- aggregate,
160
-
161
- // Data persistence (stubs)
162
- save,
163
- load,
164
- clearCache,
165
- sync,
166
- prefetch,
167
-
168
- // Plugin system
169
- use,
170
- });
171
-
172
- return fullCollection;
173
- }
174
-
175
- /**
176
- * Export for backward compatibility and convenience
177
- */
178
- export { createCollection as createDataCollection };