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,497 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* State management for mtrl-addons collection (Pure Data Layer)
|
|
3
|
-
*
|
|
4
|
-
* Handles reactive data state with zero UI concerns
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { CollectionItem } from "./types";
|
|
8
|
-
import {
|
|
9
|
-
COLLECTION_STATE,
|
|
10
|
-
DATA_PAGINATION,
|
|
11
|
-
DATA_CACHE,
|
|
12
|
-
DATA_LOGGING,
|
|
13
|
-
} from "./constants";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Pure data state interface (NO UI STATE)
|
|
17
|
-
*/
|
|
18
|
-
export interface CollectionDataState<
|
|
19
|
-
T extends CollectionItem = CollectionItem
|
|
20
|
-
> {
|
|
21
|
-
// Core data
|
|
22
|
-
items: T[];
|
|
23
|
-
filteredItems: T[];
|
|
24
|
-
totalCount: number;
|
|
25
|
-
|
|
26
|
-
// Data loading state
|
|
27
|
-
loading: boolean;
|
|
28
|
-
error: Error | null;
|
|
29
|
-
lastUpdate: number;
|
|
30
|
-
|
|
31
|
-
// Pagination data
|
|
32
|
-
currentPage: number;
|
|
33
|
-
pageSize: number;
|
|
34
|
-
hasMore: boolean;
|
|
35
|
-
cursor: string | null;
|
|
36
|
-
|
|
37
|
-
// Data operations state
|
|
38
|
-
query: ((item: T) => boolean) | null;
|
|
39
|
-
sort: ((a: T, b: T) => number) | null;
|
|
40
|
-
searchQuery: string | null;
|
|
41
|
-
searchFields: string[];
|
|
42
|
-
|
|
43
|
-
// Cache state
|
|
44
|
-
cacheHits: number;
|
|
45
|
-
cacheMisses: number;
|
|
46
|
-
cacheSize: number;
|
|
47
|
-
lastCacheCleanup: number;
|
|
48
|
-
|
|
49
|
-
// Background processing state
|
|
50
|
-
activeWorkerTasks: number;
|
|
51
|
-
prefetchQueue: number[];
|
|
52
|
-
syncInProgress: boolean;
|
|
53
|
-
lastSync: number;
|
|
54
|
-
|
|
55
|
-
// Data persistence state
|
|
56
|
-
isDirty: boolean;
|
|
57
|
-
lastSave: number;
|
|
58
|
-
persistenceError: Error | null;
|
|
59
|
-
|
|
60
|
-
// Validation state
|
|
61
|
-
validationErrors: string[];
|
|
62
|
-
validationInProgress: boolean;
|
|
63
|
-
|
|
64
|
-
// Transformation state
|
|
65
|
-
transformInProgress: boolean;
|
|
66
|
-
transformError: Error | null;
|
|
67
|
-
|
|
68
|
-
// Data version for optimistic updates
|
|
69
|
-
version: number;
|
|
70
|
-
|
|
71
|
-
// NO UI state: scrollTop, containerHeight, visibleRange, renderRange, etc.
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* State change listener type
|
|
76
|
-
*/
|
|
77
|
-
export type StateChangeListener<T extends CollectionItem = CollectionItem> = (
|
|
78
|
-
state: CollectionDataState<T>
|
|
79
|
-
) => void;
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* State store interface for collection data
|
|
83
|
-
*/
|
|
84
|
-
export interface StateStore<T extends CollectionItem = CollectionItem> {
|
|
85
|
-
// State access
|
|
86
|
-
get(): CollectionDataState<T>;
|
|
87
|
-
set(newState: Partial<CollectionDataState<T>>): void;
|
|
88
|
-
update(
|
|
89
|
-
updater: (state: CollectionDataState<T>) => Partial<CollectionDataState<T>>
|
|
90
|
-
): void;
|
|
91
|
-
|
|
92
|
-
// State subscription
|
|
93
|
-
subscribe(listener: StateChangeListener<T>): () => void;
|
|
94
|
-
|
|
95
|
-
// State utilities
|
|
96
|
-
reset(): void;
|
|
97
|
-
snapshot(): CollectionDataState<T>;
|
|
98
|
-
restore(snapshot: CollectionDataState<T>): void;
|
|
99
|
-
|
|
100
|
-
// State queries
|
|
101
|
-
isLoading(): boolean;
|
|
102
|
-
hasError(): boolean;
|
|
103
|
-
isEmpty(): boolean;
|
|
104
|
-
isDirty(): boolean;
|
|
105
|
-
getVersion(): number;
|
|
106
|
-
|
|
107
|
-
// State lifecycle
|
|
108
|
-
destroy(): void;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Creates initial data state
|
|
113
|
-
*/
|
|
114
|
-
export function createInitialDataState<
|
|
115
|
-
T extends CollectionItem = CollectionItem
|
|
116
|
-
>(
|
|
117
|
-
options: {
|
|
118
|
-
items?: T[];
|
|
119
|
-
pageSize?: number;
|
|
120
|
-
initialCapacity?: number;
|
|
121
|
-
} = {}
|
|
122
|
-
): CollectionDataState<T> {
|
|
123
|
-
const now = Date.now();
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
// Core data
|
|
127
|
-
items: options.items || [],
|
|
128
|
-
filteredItems: options.items || [],
|
|
129
|
-
totalCount: options.items?.length || COLLECTION_STATE.INITIAL_SIZE,
|
|
130
|
-
|
|
131
|
-
// Data loading state
|
|
132
|
-
loading: COLLECTION_STATE.INITIAL_LOADING,
|
|
133
|
-
error: COLLECTION_STATE.INITIAL_ERROR,
|
|
134
|
-
lastUpdate: now,
|
|
135
|
-
|
|
136
|
-
// Pagination data
|
|
137
|
-
currentPage: COLLECTION_STATE.INITIAL_PAGE,
|
|
138
|
-
pageSize: options.pageSize || DATA_PAGINATION.DEFAULT_PAGE_SIZE,
|
|
139
|
-
hasMore: COLLECTION_STATE.INITIAL_HAS_MORE,
|
|
140
|
-
cursor: null,
|
|
141
|
-
|
|
142
|
-
// Data operations state
|
|
143
|
-
query: null,
|
|
144
|
-
sort: null,
|
|
145
|
-
searchQuery: null,
|
|
146
|
-
searchFields: [],
|
|
147
|
-
|
|
148
|
-
// Cache state
|
|
149
|
-
cacheHits: 0,
|
|
150
|
-
cacheMisses: 0,
|
|
151
|
-
cacheSize: 0,
|
|
152
|
-
lastCacheCleanup: now,
|
|
153
|
-
|
|
154
|
-
// Background processing state
|
|
155
|
-
activeWorkerTasks: 0,
|
|
156
|
-
prefetchQueue: [],
|
|
157
|
-
syncInProgress: false,
|
|
158
|
-
lastSync: 0,
|
|
159
|
-
|
|
160
|
-
// Data persistence state
|
|
161
|
-
isDirty: false,
|
|
162
|
-
lastSave: 0,
|
|
163
|
-
persistenceError: null,
|
|
164
|
-
|
|
165
|
-
// Validation state
|
|
166
|
-
validationErrors: [],
|
|
167
|
-
validationInProgress: false,
|
|
168
|
-
|
|
169
|
-
// Transformation state
|
|
170
|
-
transformInProgress: false,
|
|
171
|
-
transformError: null,
|
|
172
|
-
|
|
173
|
-
// Data version
|
|
174
|
-
version: 1,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Creates a reactive data state store
|
|
180
|
-
*/
|
|
181
|
-
export function createCollectionState<
|
|
182
|
-
T extends CollectionItem = CollectionItem
|
|
183
|
-
>(initialState?: Partial<CollectionDataState<T>>): StateStore<T> {
|
|
184
|
-
let state: CollectionDataState<T> = {
|
|
185
|
-
...createInitialDataState<T>(),
|
|
186
|
-
...initialState,
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const listeners = new Set<StateChangeListener<T>>();
|
|
190
|
-
let isDestroyed = false;
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Get current state (immutable copy)
|
|
194
|
-
*/
|
|
195
|
-
const get = (): CollectionDataState<T> => {
|
|
196
|
-
if (isDestroyed) {
|
|
197
|
-
throw new Error("Cannot access destroyed state store");
|
|
198
|
-
}
|
|
199
|
-
return { ...state };
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Set partial state with validation
|
|
204
|
-
*/
|
|
205
|
-
const set = (newState: Partial<CollectionDataState<T>>): void => {
|
|
206
|
-
if (isDestroyed) {
|
|
207
|
-
console.warn(
|
|
208
|
-
`${DATA_LOGGING.PREFIX} Cannot set state on destroyed store`
|
|
209
|
-
);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Validate state changes
|
|
214
|
-
if (newState.items && !Array.isArray(newState.items)) {
|
|
215
|
-
throw new Error("Items must be an array");
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (newState.currentPage && newState.currentPage < 1) {
|
|
219
|
-
throw new Error("Current page must be >= 1");
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (newState.pageSize && newState.pageSize < 1) {
|
|
223
|
-
throw new Error("Page size must be >= 1");
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Apply state changes
|
|
227
|
-
const previousState = { ...state };
|
|
228
|
-
state = {
|
|
229
|
-
...state,
|
|
230
|
-
...newState,
|
|
231
|
-
lastUpdate: Date.now(),
|
|
232
|
-
version: state.version + 1,
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
// Auto-update filtered items if items changed
|
|
236
|
-
if (newState.items && newState.items !== previousState.items) {
|
|
237
|
-
state.filteredItems = applyFiltersAndSort(state.items, state);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Mark as dirty if data changed
|
|
241
|
-
if (newState.items || newState.filteredItems) {
|
|
242
|
-
state.isDirty = true;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Notify listeners
|
|
246
|
-
notifyListeners();
|
|
247
|
-
|
|
248
|
-
// Debug logging
|
|
249
|
-
if (DATA_LOGGING.ENABLE_DEBUG) {
|
|
250
|
-
console.log(`${DATA_LOGGING.PREFIX} State updated:`, {
|
|
251
|
-
changes: Object.keys(newState),
|
|
252
|
-
version: state.version,
|
|
253
|
-
listenerCount: listeners.size,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Update state using updater function
|
|
260
|
-
*/
|
|
261
|
-
const update = (
|
|
262
|
-
updater: (state: CollectionDataState<T>) => Partial<CollectionDataState<T>>
|
|
263
|
-
): void => {
|
|
264
|
-
if (isDestroyed) {
|
|
265
|
-
console.warn(
|
|
266
|
-
`${DATA_LOGGING.PREFIX} Cannot update state on destroyed store`
|
|
267
|
-
);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
const changes = updater({ ...state });
|
|
273
|
-
set(changes);
|
|
274
|
-
} catch (error) {
|
|
275
|
-
console.error(`${DATA_LOGGING.PREFIX} Error in state updater:`, error);
|
|
276
|
-
set({ error: error as Error });
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Subscribe to state changes
|
|
282
|
-
*/
|
|
283
|
-
const subscribe = (listener: StateChangeListener<T>): (() => void) => {
|
|
284
|
-
if (isDestroyed) {
|
|
285
|
-
console.warn(
|
|
286
|
-
`${DATA_LOGGING.PREFIX} Cannot subscribe to destroyed state store`
|
|
287
|
-
);
|
|
288
|
-
return () => {};
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (typeof listener !== "function") {
|
|
292
|
-
throw new Error("State listener must be a function");
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
listeners.add(listener);
|
|
296
|
-
|
|
297
|
-
// Return unsubscribe function
|
|
298
|
-
return () => {
|
|
299
|
-
listeners.delete(listener);
|
|
300
|
-
};
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Notify all listeners of state changes
|
|
305
|
-
*/
|
|
306
|
-
const notifyListeners = (): void => {
|
|
307
|
-
const currentState = { ...state };
|
|
308
|
-
|
|
309
|
-
listeners.forEach((listener) => {
|
|
310
|
-
try {
|
|
311
|
-
listener(currentState);
|
|
312
|
-
} catch (error) {
|
|
313
|
-
console.error(`${DATA_LOGGING.PREFIX} Error in state listener:`, error);
|
|
314
|
-
// Remove problematic listener
|
|
315
|
-
listeners.delete(listener);
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Apply filters and sorting to items
|
|
322
|
-
*/
|
|
323
|
-
const applyFiltersAndSort = (
|
|
324
|
-
items: T[],
|
|
325
|
-
currentState: CollectionDataState<T>
|
|
326
|
-
): T[] => {
|
|
327
|
-
let result = [...items];
|
|
328
|
-
|
|
329
|
-
// Apply query filter
|
|
330
|
-
if (currentState.query) {
|
|
331
|
-
result = result.filter(currentState.query);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Apply search filter
|
|
335
|
-
if (currentState.searchQuery && currentState.searchFields.length > 0) {
|
|
336
|
-
const searchQuery = currentState.searchQuery.toLowerCase();
|
|
337
|
-
result = result.filter((item) => {
|
|
338
|
-
return currentState.searchFields.some((field) => {
|
|
339
|
-
const fieldValue = (item as any)[field];
|
|
340
|
-
return (
|
|
341
|
-
fieldValue &&
|
|
342
|
-
fieldValue.toString().toLowerCase().includes(searchQuery)
|
|
343
|
-
);
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Apply sorting
|
|
349
|
-
if (currentState.sort) {
|
|
350
|
-
result.sort(currentState.sort);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return result;
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Reset state to initial values
|
|
358
|
-
*/
|
|
359
|
-
const reset = (): void => {
|
|
360
|
-
const initialState = createInitialDataState<T>();
|
|
361
|
-
state = { ...initialState };
|
|
362
|
-
notifyListeners();
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Create state snapshot
|
|
367
|
-
*/
|
|
368
|
-
const snapshot = (): CollectionDataState<T> => {
|
|
369
|
-
return JSON.parse(JSON.stringify(state));
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Restore from snapshot
|
|
374
|
-
*/
|
|
375
|
-
const restore = (snapshot: CollectionDataState<T>): void => {
|
|
376
|
-
state = { ...snapshot };
|
|
377
|
-
notifyListeners();
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* State query utilities
|
|
382
|
-
*/
|
|
383
|
-
const isLoading = (): boolean => state.loading;
|
|
384
|
-
const hasError = (): boolean => state.error !== null;
|
|
385
|
-
const isEmpty = (): boolean => state.items.length === 0;
|
|
386
|
-
const isDirty = (): boolean => state.isDirty;
|
|
387
|
-
const getVersion = (): number => state.version;
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Destroy state store
|
|
391
|
-
*/
|
|
392
|
-
const destroy = (): void => {
|
|
393
|
-
listeners.clear();
|
|
394
|
-
isDestroyed = true;
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
return {
|
|
398
|
-
get,
|
|
399
|
-
set,
|
|
400
|
-
update,
|
|
401
|
-
subscribe,
|
|
402
|
-
reset,
|
|
403
|
-
snapshot,
|
|
404
|
-
restore,
|
|
405
|
-
isLoading,
|
|
406
|
-
hasError,
|
|
407
|
-
isEmpty,
|
|
408
|
-
isDirty,
|
|
409
|
-
getVersion,
|
|
410
|
-
destroy,
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* State utilities for debugging and testing
|
|
416
|
-
*/
|
|
417
|
-
export const stateUtils = {
|
|
418
|
-
/**
|
|
419
|
-
* Create state diff between two states
|
|
420
|
-
*/
|
|
421
|
-
createDiff: <T extends CollectionItem>(
|
|
422
|
-
oldState: CollectionDataState<T>,
|
|
423
|
-
newState: CollectionDataState<T>
|
|
424
|
-
): Record<string, { old: any; new: any }> => {
|
|
425
|
-
const diff: Record<string, { old: any; new: any }> = {};
|
|
426
|
-
|
|
427
|
-
Object.keys(newState).forEach((key) => {
|
|
428
|
-
const oldValue = (oldState as any)[key];
|
|
429
|
-
const newValue = (newState as any)[key];
|
|
430
|
-
|
|
431
|
-
if (oldValue !== newValue) {
|
|
432
|
-
diff[key] = { old: oldValue, new: newValue };
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
return diff;
|
|
437
|
-
},
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Validate state integrity
|
|
441
|
-
*/
|
|
442
|
-
validateState: <T extends CollectionItem>(
|
|
443
|
-
state: CollectionDataState<T>
|
|
444
|
-
): string[] => {
|
|
445
|
-
const errors: string[] = [];
|
|
446
|
-
|
|
447
|
-
if (!Array.isArray(state.items)) {
|
|
448
|
-
errors.push("Items must be an array");
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (!Array.isArray(state.filteredItems)) {
|
|
452
|
-
errors.push("Filtered items must be an array");
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (state.currentPage < 1) {
|
|
456
|
-
errors.push("Current page must be >= 1");
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (state.pageSize < 1) {
|
|
460
|
-
errors.push("Page size must be >= 1");
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
if (state.totalCount < 0) {
|
|
464
|
-
errors.push("Total count must be >= 0");
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (state.version < 1) {
|
|
468
|
-
errors.push("Version must be >= 1");
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
return errors;
|
|
472
|
-
},
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Get state summary for debugging
|
|
476
|
-
*/
|
|
477
|
-
getSummary: <T extends CollectionItem>(
|
|
478
|
-
state: CollectionDataState<T>
|
|
479
|
-
): object => ({
|
|
480
|
-
itemCount: state.items.length,
|
|
481
|
-
filteredCount: state.filteredItems.length,
|
|
482
|
-
totalCount: state.totalCount,
|
|
483
|
-
currentPage: state.currentPage,
|
|
484
|
-
pageSize: state.pageSize,
|
|
485
|
-
hasMore: state.hasMore,
|
|
486
|
-
loading: state.loading,
|
|
487
|
-
hasError: !!state.error,
|
|
488
|
-
hasQuery: !!state.query,
|
|
489
|
-
hasSort: !!state.sort,
|
|
490
|
-
hasSearch: !!state.searchQuery,
|
|
491
|
-
version: state.version,
|
|
492
|
-
isDirty: state.isDirty,
|
|
493
|
-
cacheHits: state.cacheHits,
|
|
494
|
-
cacheMisses: state.cacheMisses,
|
|
495
|
-
activeWorkerTasks: state.activeWorkerTasks,
|
|
496
|
-
}),
|
|
497
|
-
};
|