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.
- package/AI.md +28 -230
- package/CLAUDE.md +882 -0
- package/build.js +253 -24
- package/package.json +14 -4
- package/scripts/debug/vlist-selection.ts +121 -0
- package/src/components/index.ts +5 -41
- package/src/components/{list → vlist}/config.ts +66 -95
- package/src/components/vlist/constants.ts +23 -0
- package/src/components/vlist/features/api.ts +626 -0
- package/src/components/vlist/features/index.ts +10 -0
- package/src/components/vlist/features/selection.ts +436 -0
- package/src/components/vlist/features/viewport.ts +59 -0
- package/src/components/vlist/index.ts +17 -0
- package/src/components/{list → vlist}/types.ts +242 -32
- package/src/components/vlist/vlist.ts +92 -0
- package/src/core/compose/features/gestures/index.ts +227 -0
- package/src/core/compose/features/gestures/longpress.ts +383 -0
- package/src/core/compose/features/gestures/pan.ts +424 -0
- package/src/core/compose/features/gestures/pinch.ts +475 -0
- package/src/core/compose/features/gestures/rotate.ts +485 -0
- package/src/core/compose/features/gestures/swipe.ts +492 -0
- package/src/core/compose/features/gestures/tap.ts +334 -0
- package/src/core/compose/features/index.ts +2 -38
- package/src/core/compose/index.ts +13 -29
- package/src/core/gestures/index.ts +31 -0
- package/src/core/gestures/longpress.ts +68 -0
- package/src/core/gestures/manager.ts +418 -0
- package/src/core/gestures/pan.ts +48 -0
- package/src/core/gestures/pinch.ts +58 -0
- package/src/core/gestures/rotate.ts +58 -0
- package/src/core/gestures/swipe.ts +66 -0
- package/src/core/gestures/tap.ts +45 -0
- package/src/core/gestures/types.ts +387 -0
- package/src/core/gestures/utils.ts +128 -0
- package/src/core/index.ts +27 -151
- package/src/core/layout/schema.ts +153 -72
- package/src/core/layout/types.ts +5 -2
- package/src/core/viewport/constants.ts +145 -0
- package/src/core/viewport/features/base.ts +73 -0
- package/src/core/viewport/features/collection.ts +1182 -0
- package/src/core/viewport/features/events.ts +130 -0
- package/src/core/viewport/features/index.ts +20 -0
- package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +31 -34
- package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
- package/src/core/viewport/features/momentum.ts +269 -0
- package/src/core/viewport/features/placeholders.ts +335 -0
- package/src/core/viewport/features/rendering.ts +962 -0
- package/src/core/viewport/features/scrollbar.ts +434 -0
- package/src/core/viewport/features/scrolling.ts +634 -0
- package/src/core/viewport/features/utils.ts +94 -0
- package/src/core/viewport/features/virtual.ts +525 -0
- package/src/core/viewport/index.ts +31 -0
- package/src/core/viewport/types.ts +133 -0
- package/src/core/viewport/utils/speed-tracker.ts +79 -0
- package/src/core/viewport/viewport.ts +265 -0
- package/src/index.ts +0 -7
- package/src/styles/components/_vlist.scss +352 -0
- package/src/styles/index.scss +1 -1
- package/test/components/vlist-selection.test.ts +240 -0
- package/test/components/vlist.test.ts +63 -0
- package/test/core/collection/adapter.test.ts +161 -0
- package/bun.lock +0 -792
- package/src/components/list/api.ts +0 -314
- package/src/components/list/constants.ts +0 -56
- package/src/components/list/features/api.ts +0 -428
- package/src/components/list/features/index.ts +0 -31
- package/src/components/list/features/list-manager.ts +0 -502
- package/src/components/list/index.ts +0 -39
- package/src/components/list/list.ts +0 -234
- package/src/core/collection/base-collection.ts +0 -100
- package/src/core/collection/collection-composer.ts +0 -178
- package/src/core/collection/collection.ts +0 -745
- package/src/core/collection/constants.ts +0 -172
- package/src/core/collection/events.ts +0 -428
- package/src/core/collection/features/api/loading.ts +0 -279
- package/src/core/collection/features/operations/data-operations.ts +0 -147
- package/src/core/collection/index.ts +0 -104
- package/src/core/collection/state.ts +0 -497
- package/src/core/collection/types.ts +0 -404
- package/src/core/compose/features/collection.ts +0 -119
- package/src/core/compose/features/selection.ts +0 -213
- package/src/core/compose/features/styling.ts +0 -108
- package/src/core/list-manager/api.ts +0 -599
- package/src/core/list-manager/config.ts +0 -593
- package/src/core/list-manager/constants.ts +0 -268
- package/src/core/list-manager/features/api.ts +0 -58
- package/src/core/list-manager/features/collection/collection.ts +0 -705
- package/src/core/list-manager/features/collection/index.ts +0 -17
- package/src/core/list-manager/features/viewport/constants.ts +0 -42
- package/src/core/list-manager/features/viewport/index.ts +0 -16
- package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
- package/src/core/list-manager/features/viewport/rendering.ts +0 -575
- package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
- package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
- package/src/core/list-manager/features/viewport/template.ts +0 -220
- package/src/core/list-manager/features/viewport/viewport.ts +0 -654
- package/src/core/list-manager/features/viewport/virtual.ts +0 -309
- package/src/core/list-manager/index.ts +0 -279
- package/src/core/list-manager/list-manager.ts +0 -206
- package/src/core/list-manager/types.ts +0 -439
- package/src/core/list-manager/utils/calculations.ts +0 -290
- package/src/core/list-manager/utils/range-calculator.ts +0 -349
- package/src/core/list-manager/utils/speed-tracker.ts +0 -273
- package/src/styles/components/_list.scss +0 -244
- package/src/types/mtrl.d.ts +0 -6
- package/test/components/list.test.ts +0 -256
- package/test/core/collection/failed-ranges.test.ts +0 -270
- package/test/core/compose/features.test.ts +0 -183
- package/test/core/list-manager/features/collection.test.ts +0 -704
- package/test/core/list-manager/features/viewport.test.ts +0 -698
- package/test/core/list-manager/list-manager.test.ts +0 -593
- package/test/core/list-manager/utils/calculations.test.ts +0 -433
- package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
- package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
- package/tsconfig.build.json +0 -23
- /package/src/components/{list → vlist}/features.ts +0 -0
- /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 };
|