mtrl-addons 0.1.1 → 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.
- package/build.js +139 -108
- package/package.json +13 -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 +322 -0
- package/src/components/vlist/features/index.ts +10 -0
- package/src/components/vlist/features/selection.ts +444 -0
- package/src/components/vlist/features/viewport.ts +65 -0
- package/src/components/vlist/index.ts +16 -0
- package/src/components/{list → vlist}/types.ts +104 -26
- 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 +73 -35
- package/src/core/layout/types.ts +5 -2
- package/src/core/viewport/constants.ts +140 -0
- package/src/core/viewport/features/base.ts +73 -0
- package/src/core/viewport/features/collection.ts +882 -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 +27 -30
- package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
- package/src/core/viewport/features/momentum.ts +260 -0
- package/src/core/viewport/features/placeholders.ts +335 -0
- package/src/core/viewport/features/rendering.ts +568 -0
- package/src/core/viewport/features/scrollbar.ts +434 -0
- package/src/core/viewport/features/scrolling.ts +618 -0
- package/src/core/viewport/features/utils.ts +88 -0
- package/src/core/viewport/features/virtual.ts +384 -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 +246 -0
- package/src/index.ts +0 -7
- package/src/styles/components/_vlist.scss +331 -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 -14
- /package/src/components/{list → vlist}/features.ts +0 -0
- /package/src/core/{compose → viewport}/features/performance.ts +0 -0
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Loading Plugin
|
|
3
|
-
*
|
|
4
|
-
* Handles data loading from adapters (loadPage, loadMore, refresh)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type {
|
|
8
|
-
CollectionItem,
|
|
9
|
-
CollectionAdapter,
|
|
10
|
-
BaseCollection,
|
|
11
|
-
AdapterResponse,
|
|
12
|
-
} from "../../types";
|
|
13
|
-
import { CollectionDataEvents } from "../../types";
|
|
14
|
-
import { DATA_LOGGING, COLLECTION_DEFAULTS } from "../../constants";
|
|
15
|
-
import { createEventPayload, CollectionEvents } from "../../events";
|
|
16
|
-
|
|
17
|
-
export interface LoadingConfig {
|
|
18
|
-
pageSize?: number;
|
|
19
|
-
sequential?: boolean; // Whether to load pages sequentially or allow random access
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface LoadingFeatures<T extends CollectionItem> {
|
|
23
|
-
loadPage(page: number): Promise<AdapterResponse<T>>;
|
|
24
|
-
loadMore(): Promise<AdapterResponse<T>>;
|
|
25
|
-
refresh(): Promise<AdapterResponse<T>>;
|
|
26
|
-
getCurrentPage(): number;
|
|
27
|
-
hasNext(): boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* API Loading Plugin (Pure Data Feature)
|
|
32
|
-
*/
|
|
33
|
-
export const withLoading =
|
|
34
|
-
<T extends CollectionItem = CollectionItem>(config: LoadingConfig = {}) =>
|
|
35
|
-
(
|
|
36
|
-
baseCollection: BaseCollection<T>
|
|
37
|
-
): BaseCollection<T> & LoadingFeatures<T> => {
|
|
38
|
-
// Get adapter from config
|
|
39
|
-
const adapter = baseCollection._config.adapter;
|
|
40
|
-
if (!adapter) {
|
|
41
|
-
console.warn(
|
|
42
|
-
`${DATA_LOGGING.PREFIX} No adapter configured for loading plugin`
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Plugin state
|
|
47
|
-
let currentPage = COLLECTION_DEFAULTS.CURRENT_PAGE;
|
|
48
|
-
let pageSize =
|
|
49
|
-
config.pageSize ||
|
|
50
|
-
baseCollection._config.pageSize ||
|
|
51
|
-
COLLECTION_DEFAULTS.PAGE_SIZE;
|
|
52
|
-
let hasMore = true;
|
|
53
|
-
let isLoadingMore = false;
|
|
54
|
-
let totalItemsExpected = 0;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Apply data transformations if configured
|
|
58
|
-
*/
|
|
59
|
-
const applyDataTransformations = (items: any[]): T[] => {
|
|
60
|
-
let processedItems = [...items];
|
|
61
|
-
|
|
62
|
-
// Apply normalization if configured
|
|
63
|
-
if (baseCollection._config.normalize) {
|
|
64
|
-
processedItems = baseCollection._config.normalize(processedItems);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Apply transformation if configured
|
|
68
|
-
if (baseCollection._config.transform) {
|
|
69
|
-
processedItems = processedItems.map(baseCollection._config.transform);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Apply validation if configured
|
|
73
|
-
if (baseCollection._config.validate) {
|
|
74
|
-
processedItems = processedItems.filter(baseCollection._config.validate);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return processedItems;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Determine if there's more data to load
|
|
82
|
-
*/
|
|
83
|
-
const determineHasMore = (
|
|
84
|
-
response: AdapterResponse<T>,
|
|
85
|
-
itemsLength: number
|
|
86
|
-
): boolean => {
|
|
87
|
-
const apiHasNext = response.meta?.hasNext;
|
|
88
|
-
const gotFullPage = itemsLength === pageSize;
|
|
89
|
-
const apiTotal = response.meta?.total;
|
|
90
|
-
|
|
91
|
-
console.log(
|
|
92
|
-
`${DATA_LOGGING.PREFIX} LOADING-PLUGIN determineHasMore debug:`,
|
|
93
|
-
{
|
|
94
|
-
apiTotal,
|
|
95
|
-
apiHasNext,
|
|
96
|
-
gotFullPage,
|
|
97
|
-
itemsLength,
|
|
98
|
-
pageSize,
|
|
99
|
-
totalItemsExpected,
|
|
100
|
-
currentItemsLength: baseCollection.getState().items.length,
|
|
101
|
-
}
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
if (apiTotal !== undefined) {
|
|
105
|
-
const result = totalItemsExpected < apiTotal;
|
|
106
|
-
console.log(
|
|
107
|
-
`${DATA_LOGGING.PREFIX} LOADING-PLUGIN Using apiTotal logic: ${totalItemsExpected} < ${apiTotal} = ${result}`
|
|
108
|
-
);
|
|
109
|
-
return result;
|
|
110
|
-
} else if (apiHasNext !== undefined) {
|
|
111
|
-
console.log(
|
|
112
|
-
`${DATA_LOGGING.PREFIX} LOADING-PLUGIN Using apiHasNext logic: ${apiHasNext}`
|
|
113
|
-
);
|
|
114
|
-
return apiHasNext;
|
|
115
|
-
} else {
|
|
116
|
-
console.log(
|
|
117
|
-
`${DATA_LOGGING.PREFIX} LOADING-PLUGIN Using gotFullPage logic: ${gotFullPage}`
|
|
118
|
-
);
|
|
119
|
-
return gotFullPage;
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Load a specific page of data
|
|
125
|
-
*/
|
|
126
|
-
const loadPage = async (page: number): Promise<AdapterResponse<T>> => {
|
|
127
|
-
if (!adapter) {
|
|
128
|
-
throw new Error("No adapter configured");
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Calculate what data range this page represents
|
|
132
|
-
const startIndex = (page - 1) * pageSize;
|
|
133
|
-
const endIndex = startIndex + pageSize - 1;
|
|
134
|
-
|
|
135
|
-
const currentState = baseCollection.getState();
|
|
136
|
-
const currentItems = currentState.items;
|
|
137
|
-
|
|
138
|
-
// Check if we already have this data
|
|
139
|
-
const hasData = currentItems.length > endIndex;
|
|
140
|
-
|
|
141
|
-
if (hasData) {
|
|
142
|
-
console.log(
|
|
143
|
-
`${DATA_LOGGING.PREFIX} Data already loaded for page ${page} (items ${startIndex}-${endIndex})`
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
// Return the subset we already have
|
|
147
|
-
const pageItems = currentItems.slice(startIndex, startIndex + pageSize);
|
|
148
|
-
|
|
149
|
-
currentPage = page;
|
|
150
|
-
|
|
151
|
-
// Emit items loaded event
|
|
152
|
-
baseCollection.emit(CollectionDataEvents.ITEMS_LOADED, {
|
|
153
|
-
items: pageItems,
|
|
154
|
-
meta: { page, total: totalItemsExpected },
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
items: pageItems,
|
|
159
|
-
meta: {
|
|
160
|
-
total: totalItemsExpected,
|
|
161
|
-
page: page,
|
|
162
|
-
hasNext: hasMore,
|
|
163
|
-
hasPrev: page > 1,
|
|
164
|
-
},
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
console.log(`${DATA_LOGGING.PREFIX} Loading page ${page} from adapter`);
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
baseCollection.emit(
|
|
172
|
-
CollectionDataEvents.LOADING_START,
|
|
173
|
-
createEventPayload.loadingStart(`Loading page ${page}`)
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
baseCollection.setState({ loading: true });
|
|
177
|
-
|
|
178
|
-
const response = await adapter.read({
|
|
179
|
-
page,
|
|
180
|
-
pageSize,
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
if (response.error) {
|
|
184
|
-
throw new Error(response.error.message);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const processedItems = applyDataTransformations(response.items);
|
|
188
|
-
|
|
189
|
-
// Add to existing items (append if sequential)
|
|
190
|
-
let updatedItems: T[];
|
|
191
|
-
if (startIndex === currentItems.length) {
|
|
192
|
-
// Sequential loading - just append
|
|
193
|
-
updatedItems = [...currentItems, ...processedItems];
|
|
194
|
-
} else {
|
|
195
|
-
// Non-sequential - for now just append (could be more sophisticated)
|
|
196
|
-
updatedItems = [...currentItems, ...processedItems];
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
totalItemsExpected = response.meta?.total || updatedItems.length;
|
|
200
|
-
|
|
201
|
-
baseCollection.setState({
|
|
202
|
-
items: updatedItems,
|
|
203
|
-
totalCount: totalItemsExpected,
|
|
204
|
-
loading: false,
|
|
205
|
-
error: null,
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
currentPage = page;
|
|
209
|
-
hasMore = determineHasMore(response, processedItems.length);
|
|
210
|
-
|
|
211
|
-
baseCollection.emit(
|
|
212
|
-
CollectionDataEvents.ITEMS_LOADED,
|
|
213
|
-
createEventPayload.itemsLoaded(processedItems, response.meta)
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
return response;
|
|
217
|
-
} catch (error) {
|
|
218
|
-
const errorObj = error as Error;
|
|
219
|
-
baseCollection.setState({
|
|
220
|
-
loading: false,
|
|
221
|
-
error: errorObj,
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
baseCollection.emit(
|
|
225
|
-
CollectionDataEvents.ERROR_OCCURRED,
|
|
226
|
-
createEventPayload.errorOccurred(errorObj, { page })
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
throw error;
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Load more items (next page)
|
|
235
|
-
*/
|
|
236
|
-
const loadMore = async (): Promise<AdapterResponse<T>> => {
|
|
237
|
-
if (!adapter || isLoadingMore) {
|
|
238
|
-
throw new Error("No adapter configured or already loading");
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
isLoadingMore = true;
|
|
242
|
-
const nextPage = currentPage + 1;
|
|
243
|
-
|
|
244
|
-
try {
|
|
245
|
-
const response = await loadPage(nextPage);
|
|
246
|
-
return response;
|
|
247
|
-
} finally {
|
|
248
|
-
isLoadingMore = false;
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Refresh all data (start from beginning)
|
|
254
|
-
*/
|
|
255
|
-
const refresh = async (): Promise<AdapterResponse<T>> => {
|
|
256
|
-
// Reset state
|
|
257
|
-
currentPage = 0;
|
|
258
|
-
hasMore = true;
|
|
259
|
-
totalItemsExpected = 0;
|
|
260
|
-
|
|
261
|
-
baseCollection.setState({
|
|
262
|
-
items: [],
|
|
263
|
-
filteredItems: [],
|
|
264
|
-
totalCount: 0,
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
return loadMore();
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
console.log(`${DATA_LOGGING.PREFIX} Loading plugin installed`);
|
|
271
|
-
|
|
272
|
-
return Object.assign(baseCollection, {
|
|
273
|
-
loadPage,
|
|
274
|
-
loadMore,
|
|
275
|
-
refresh,
|
|
276
|
-
getCurrentPage: () => currentPage,
|
|
277
|
-
hasNext: () => hasMore,
|
|
278
|
-
});
|
|
279
|
-
};
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Data Operations Plugin
|
|
3
|
-
*
|
|
4
|
-
* Handles CRUD operations on collection items (add, update, remove, clear)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { CollectionItem, BaseCollection } from "../../types";
|
|
8
|
-
import { CollectionDataEvents } from "../../types";
|
|
9
|
-
import { DATA_LOGGING } from "../../constants";
|
|
10
|
-
|
|
11
|
-
export interface DataOperationsFeatures<T extends CollectionItem> {
|
|
12
|
-
addItems(items: T[]): Promise<T[]>;
|
|
13
|
-
updateItems(items: Partial<T>[]): Promise<T[]>;
|
|
14
|
-
removeItems(ids: string[]): Promise<void>;
|
|
15
|
-
clearItems(): Promise<void>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Data Operations Plugin
|
|
20
|
-
*/
|
|
21
|
-
export const withDataOperations =
|
|
22
|
-
<T extends CollectionItem = CollectionItem>() =>
|
|
23
|
-
(
|
|
24
|
-
baseCollection: BaseCollection<T>
|
|
25
|
-
): BaseCollection<T> & DataOperationsFeatures<T> => {
|
|
26
|
-
/**
|
|
27
|
-
* Apply data transformations if configured
|
|
28
|
-
*/
|
|
29
|
-
const applyDataTransformations = (items: any[]): T[] => {
|
|
30
|
-
let processedItems = [...items];
|
|
31
|
-
|
|
32
|
-
// Apply normalization if configured
|
|
33
|
-
if (baseCollection._config.normalize) {
|
|
34
|
-
processedItems = baseCollection._config.normalize(processedItems);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Apply transformation if configured
|
|
38
|
-
if (baseCollection._config.transform) {
|
|
39
|
-
processedItems = processedItems.map(baseCollection._config.transform);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Apply validation if configured
|
|
43
|
-
if (baseCollection._config.validate) {
|
|
44
|
-
processedItems = processedItems.filter(baseCollection._config.validate);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return processedItems;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Add items to the collection
|
|
52
|
-
*/
|
|
53
|
-
const addItems = async (items: T[]): Promise<T[]> => {
|
|
54
|
-
const processedItems = applyDataTransformations(items);
|
|
55
|
-
const currentState = baseCollection.getState();
|
|
56
|
-
|
|
57
|
-
// Add to existing items
|
|
58
|
-
const newItems = [...currentState.items, ...processedItems];
|
|
59
|
-
|
|
60
|
-
baseCollection.setState({
|
|
61
|
-
items: newItems,
|
|
62
|
-
totalCount: newItems.length,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// Emit data event
|
|
66
|
-
baseCollection.emit(CollectionDataEvents.ITEMS_ADDED, {
|
|
67
|
-
items: processedItems,
|
|
68
|
-
indices: [currentState.items.length],
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
return processedItems;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Update existing items in the collection
|
|
76
|
-
*/
|
|
77
|
-
const updateItems = async (items: Partial<T>[]): Promise<T[]> => {
|
|
78
|
-
const currentState = baseCollection.getState();
|
|
79
|
-
const updatedItems: T[] = [];
|
|
80
|
-
|
|
81
|
-
// Update existing items
|
|
82
|
-
const newItems = currentState.items.map((item: T) => {
|
|
83
|
-
const update = items.find((u) => u.id === item.id);
|
|
84
|
-
if (update) {
|
|
85
|
-
const updatedItem = { ...item, ...update };
|
|
86
|
-
updatedItems.push(updatedItem);
|
|
87
|
-
return updatedItem;
|
|
88
|
-
}
|
|
89
|
-
return item;
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
baseCollection.setState({ items: newItems });
|
|
93
|
-
|
|
94
|
-
// Emit data event
|
|
95
|
-
baseCollection.emit(CollectionDataEvents.ITEMS_UPDATED, {
|
|
96
|
-
items: updatedItems,
|
|
97
|
-
indices: [],
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
return updatedItems;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Remove items from the collection
|
|
105
|
-
*/
|
|
106
|
-
const removeItems = async (ids: string[]): Promise<void> => {
|
|
107
|
-
const currentState = baseCollection.getState();
|
|
108
|
-
|
|
109
|
-
// Remove items by ID
|
|
110
|
-
const newItems = currentState.items.filter(
|
|
111
|
-
(item: T) => !ids.includes(item.id)
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
baseCollection.setState({
|
|
115
|
-
items: newItems,
|
|
116
|
-
totalCount: newItems.length,
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Emit data event
|
|
120
|
-
baseCollection.emit(CollectionDataEvents.ITEMS_REMOVED, {
|
|
121
|
-
ids,
|
|
122
|
-
});
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Clear all items from the collection
|
|
127
|
-
*/
|
|
128
|
-
const clearItems = async (): Promise<void> => {
|
|
129
|
-
baseCollection.setState({
|
|
130
|
-
items: [],
|
|
131
|
-
filteredItems: [],
|
|
132
|
-
totalCount: 0,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Emit data event
|
|
136
|
-
baseCollection.emit(CollectionDataEvents.ITEMS_CLEARED, {});
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
console.log(`${DATA_LOGGING.PREFIX} Data Operations plugin installed`);
|
|
140
|
-
|
|
141
|
-
return Object.assign(baseCollection, {
|
|
142
|
-
addItems,
|
|
143
|
-
updateItems,
|
|
144
|
-
removeItems,
|
|
145
|
-
clearItems,
|
|
146
|
-
});
|
|
147
|
-
};
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Collection System - Composition-Based Architecture
|
|
3
|
-
*
|
|
4
|
-
* Following the blueprint with functional composition and plugin system
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { CollectionItem } from "./types";
|
|
8
|
-
|
|
9
|
-
// New composition-based architecture exports
|
|
10
|
-
export { createCollection, createDataCollection } from "./collection-composer";
|
|
11
|
-
export { createBaseCollection } from "./base-collection";
|
|
12
|
-
|
|
13
|
-
// Plugin exports
|
|
14
|
-
export { withLoading } from "./features/api/loading";
|
|
15
|
-
export { withDataOperations } from "./features/operations/data-operations";
|
|
16
|
-
|
|
17
|
-
// Core types
|
|
18
|
-
export type {
|
|
19
|
-
Collection,
|
|
20
|
-
BaseCollection,
|
|
21
|
-
CollectionConfig,
|
|
22
|
-
CollectionItem,
|
|
23
|
-
CollectionAdapter,
|
|
24
|
-
AdapterParams,
|
|
25
|
-
AdapterResponse,
|
|
26
|
-
} from "./types";
|
|
27
|
-
|
|
28
|
-
// Events and state
|
|
29
|
-
export { CollectionDataEvents } from "./types";
|
|
30
|
-
export { CollectionEvents, createEventPayload } from "./events";
|
|
31
|
-
export { createCollectionState } from "./state";
|
|
32
|
-
|
|
33
|
-
// Constants
|
|
34
|
-
export * from "./constants";
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Quick start utility for creating a simple REST adapter
|
|
38
|
-
*/
|
|
39
|
-
export function createRestAdapter<T extends CollectionItem>(
|
|
40
|
-
baseUrl: string,
|
|
41
|
-
options: {
|
|
42
|
-
headers?: Record<string, string>;
|
|
43
|
-
timeout?: number;
|
|
44
|
-
transform?: (response: any) => { items: T[]; meta?: any };
|
|
45
|
-
} = {}
|
|
46
|
-
) {
|
|
47
|
-
return {
|
|
48
|
-
async read(params: any = {}) {
|
|
49
|
-
const url = new URL(baseUrl);
|
|
50
|
-
|
|
51
|
-
// Add query parameters
|
|
52
|
-
if (params.page) url.searchParams.set("page", params.page.toString());
|
|
53
|
-
if (params.pageSize)
|
|
54
|
-
url.searchParams.set("limit", params.pageSize.toString());
|
|
55
|
-
if (params.search) url.searchParams.set("q", params.search);
|
|
56
|
-
if (params.cursor) url.searchParams.set("cursor", params.cursor);
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const response = await fetch(url.toString(), {
|
|
60
|
-
method: "GET",
|
|
61
|
-
headers: {
|
|
62
|
-
"Content-Type": "application/json",
|
|
63
|
-
...options.headers,
|
|
64
|
-
},
|
|
65
|
-
signal: options.timeout
|
|
66
|
-
? AbortSignal.timeout(options.timeout)
|
|
67
|
-
: undefined,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
if (!response.ok) {
|
|
71
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const data = await response.json();
|
|
75
|
-
|
|
76
|
-
// Apply custom transform if provided
|
|
77
|
-
if (options.transform) {
|
|
78
|
-
return options.transform(data);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Default transform
|
|
82
|
-
return {
|
|
83
|
-
items: data.items || data.data || [data].flat(),
|
|
84
|
-
meta: {
|
|
85
|
-
total: data.total,
|
|
86
|
-
page: data.page,
|
|
87
|
-
hasNext: data.hasNext || data.has_next,
|
|
88
|
-
hasPrev: data.hasPrev || data.has_prev,
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
} catch (error) {
|
|
92
|
-
return {
|
|
93
|
-
items: [],
|
|
94
|
-
error: {
|
|
95
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
96
|
-
code: "FETCH_ERROR",
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Collection architecture is now complete!
|