mtrl-addons 0.1.2 → 0.2.1

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