cogsbox-state 0.5.462 → 0.5.464
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/dist/CogsState.d.ts +1 -1
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +1057 -1047
- package/dist/CogsState.jsx.map +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +188 -167
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +78 -48
- package/src/store.ts +86 -98
package/src/store.ts
CHANGED
|
@@ -109,7 +109,6 @@ export type ShadowMetadata = {
|
|
|
109
109
|
lastUpdated?: number;
|
|
110
110
|
value?: any;
|
|
111
111
|
classSignals?: Array<{
|
|
112
|
-
// <-- ADD THIS BLOCK
|
|
113
112
|
id: string;
|
|
114
113
|
effect: string;
|
|
115
114
|
lastClasses: string;
|
|
@@ -174,8 +173,8 @@ export type CogsEvent =
|
|
|
174
173
|
| { type: 'INSERT'; path: string; itemKey: string; index: number }
|
|
175
174
|
| { type: 'REMOVE'; path: string; itemKey: string }
|
|
176
175
|
| { type: 'UPDATE'; path: string; newValue: any }
|
|
177
|
-
| { type: 'ITEMHEIGHT'; itemKey: string; height: number }
|
|
178
|
-
| { type: 'RELOAD'; path: string };
|
|
176
|
+
| { type: 'ITEMHEIGHT'; itemKey: string; height: number }
|
|
177
|
+
| { type: 'RELOAD'; path: string };
|
|
179
178
|
export type CogsGlobalState = {
|
|
180
179
|
updateQueue: Set<() => void>;
|
|
181
180
|
isFlushScheduled: boolean;
|
|
@@ -256,10 +255,6 @@ export type CogsGlobalState = {
|
|
|
256
255
|
getInitialOptions: (key: string) => OptionsType | undefined;
|
|
257
256
|
setInitialStateOptions: (key: string, value: OptionsType) => void;
|
|
258
257
|
|
|
259
|
-
// --- Validation ---
|
|
260
|
-
|
|
261
|
-
// --- Server Sync and Logging ---
|
|
262
|
-
|
|
263
258
|
serverStateUpdates: Map<
|
|
264
259
|
string,
|
|
265
260
|
{
|
|
@@ -279,29 +274,17 @@ export type CogsGlobalState = {
|
|
|
279
274
|
getSyncInfo: (key: string) => SyncInfo | null;
|
|
280
275
|
};
|
|
281
276
|
const isSimpleObject = (value: any): boolean => {
|
|
277
|
+
// Most common cases first
|
|
282
278
|
if (value === null || typeof value !== 'object') return false;
|
|
283
279
|
|
|
284
|
-
//
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
value instanceof Float32Array ||
|
|
293
|
-
value instanceof Float64Array ||
|
|
294
|
-
value instanceof ArrayBuffer ||
|
|
295
|
-
value instanceof Date ||
|
|
296
|
-
value instanceof RegExp ||
|
|
297
|
-
value instanceof Map ||
|
|
298
|
-
value instanceof Set
|
|
299
|
-
) {
|
|
300
|
-
return false; // Treat as primitive
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Arrays and plain objects are complex
|
|
304
|
-
return Array.isArray(value) || value.constructor === Object;
|
|
280
|
+
// Arrays are simple objects
|
|
281
|
+
if (Array.isArray(value)) return true;
|
|
282
|
+
|
|
283
|
+
// Plain objects second most common
|
|
284
|
+
if (value.constructor === Object) return true;
|
|
285
|
+
|
|
286
|
+
// Everything else is not simple
|
|
287
|
+
return false;
|
|
305
288
|
};
|
|
306
289
|
export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
307
290
|
updateQueue: new Set<() => void>(),
|
|
@@ -360,20 +343,11 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
360
343
|
},
|
|
361
344
|
registerComponent: (stateKey, fullComponentId, registration) => {
|
|
362
345
|
set((state) => {
|
|
363
|
-
// Create a new Map to ensure Zustand detects the change
|
|
364
346
|
const newShadowStore = new Map(state.shadowStateStore);
|
|
365
|
-
|
|
366
|
-
// Get the metadata for the ROOT of the state (where the components map lives)
|
|
367
347
|
const rootMeta = newShadowStore.get(stateKey) || {};
|
|
368
|
-
|
|
369
|
-
// Also clone the components map to avoid direct mutation
|
|
370
348
|
const components = new Map(rootMeta.components);
|
|
371
349
|
components.set(fullComponentId, registration);
|
|
372
|
-
|
|
373
|
-
// Update the root metadata with the new components map
|
|
374
350
|
newShadowStore.set(stateKey, { ...rootMeta, components });
|
|
375
|
-
|
|
376
|
-
// Return the updated state
|
|
377
351
|
return { shadowStateStore: newShadowStore };
|
|
378
352
|
});
|
|
379
353
|
},
|
|
@@ -382,8 +356,6 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
382
356
|
set((state) => {
|
|
383
357
|
const newShadowStore = new Map(state.shadowStateStore);
|
|
384
358
|
const rootMeta = newShadowStore.get(stateKey);
|
|
385
|
-
|
|
386
|
-
// If there's no metadata or no components map, do nothing
|
|
387
359
|
if (!rootMeta?.components) {
|
|
388
360
|
return state; // Return original state, no change needed
|
|
389
361
|
}
|
|
@@ -401,40 +373,45 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
401
373
|
});
|
|
402
374
|
},
|
|
403
375
|
markAsDirty: (key: string, path: string[], options = { bubble: true }) => {
|
|
404
|
-
const
|
|
405
|
-
|
|
376
|
+
const { shadowStateStore } = get();
|
|
377
|
+
const updates = new Map<string, ShadowMetadata>();
|
|
406
378
|
|
|
407
379
|
const setDirty = (currentPath: string[]) => {
|
|
408
380
|
const fullKey = [key, ...currentPath].join('.');
|
|
409
|
-
const meta =
|
|
410
|
-
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
newShadowStore.set(fullKey, { ...meta, isDirty: true });
|
|
415
|
-
changed = true;
|
|
416
|
-
} else if (!meta) {
|
|
417
|
-
// If there's no metadata, create it and mark it as dirty.
|
|
418
|
-
// This handles newly created fields within an object.
|
|
419
|
-
newShadowStore.set(fullKey, { isDirty: true });
|
|
420
|
-
changed = true;
|
|
381
|
+
const meta = shadowStateStore.get(fullKey) || {};
|
|
382
|
+
|
|
383
|
+
// If already dirty, no need to update
|
|
384
|
+
if (meta.isDirty === true) {
|
|
385
|
+
return true; // Return true to indicate parent is dirty
|
|
421
386
|
}
|
|
387
|
+
|
|
388
|
+
updates.set(fullKey, { ...meta, isDirty: true });
|
|
389
|
+
return false; // Not previously dirty
|
|
422
390
|
};
|
|
423
391
|
|
|
424
|
-
//
|
|
392
|
+
// Mark the target path
|
|
425
393
|
setDirty(path);
|
|
426
394
|
|
|
427
|
-
//
|
|
395
|
+
// Bubble up if requested
|
|
428
396
|
if (options.bubble) {
|
|
429
397
|
let parentPath = [...path];
|
|
430
398
|
while (parentPath.length > 0) {
|
|
431
399
|
parentPath.pop();
|
|
432
|
-
setDirty(parentPath);
|
|
400
|
+
const wasDirty = setDirty(parentPath);
|
|
401
|
+
if (wasDirty) {
|
|
402
|
+
break; // Stop bubbling if parent was already dirty
|
|
403
|
+
}
|
|
433
404
|
}
|
|
434
405
|
}
|
|
435
406
|
|
|
436
|
-
|
|
437
|
-
|
|
407
|
+
// Apply all updates at once
|
|
408
|
+
if (updates.size > 0) {
|
|
409
|
+
set((state) => {
|
|
410
|
+
updates.forEach((meta, key) => {
|
|
411
|
+
state.shadowStateStore.set(key, meta);
|
|
412
|
+
});
|
|
413
|
+
return state;
|
|
414
|
+
});
|
|
438
415
|
}
|
|
439
416
|
},
|
|
440
417
|
serverStateUpdates: new Map(),
|
|
@@ -485,7 +462,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
485
462
|
set((state) => {
|
|
486
463
|
// 1. Make a copy of the current store to modify it
|
|
487
464
|
const newShadowStore = new Map(state.shadowStateStore);
|
|
488
|
-
|
|
465
|
+
console.log('initializeShadowState');
|
|
489
466
|
// 2. PRESERVE the existing components map before doing anything else
|
|
490
467
|
const existingRootMeta = newShadowStore.get(key);
|
|
491
468
|
const preservedComponents = existingRootMeta?.components;
|
|
@@ -542,19 +519,13 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
542
519
|
},
|
|
543
520
|
|
|
544
521
|
getShadowValue: (fullKey: string, validArrayIds?: string[]) => {
|
|
545
|
-
// The cache is created here. It's temporary and exists only for this one top-level call.
|
|
546
522
|
const memo = new Map<string, any>();
|
|
547
|
-
|
|
548
|
-
// This is the inner recursive function that does the real work.
|
|
549
523
|
const reconstruct = (keyToBuild: string, ids?: string[]): any => {
|
|
550
|
-
// --- STEP 1: Check the cache first ---
|
|
551
|
-
// If we have already built the object for this path *during this call*, return it instantly.
|
|
552
524
|
if (memo.has(keyToBuild)) {
|
|
553
525
|
return memo.get(keyToBuild);
|
|
554
526
|
}
|
|
555
527
|
|
|
556
528
|
const shadowMeta = get().shadowStateStore.get(keyToBuild);
|
|
557
|
-
|
|
558
529
|
if (!shadowMeta) {
|
|
559
530
|
return undefined;
|
|
560
531
|
}
|
|
@@ -567,19 +538,14 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
567
538
|
|
|
568
539
|
if (shadowMeta.arrayKeys) {
|
|
569
540
|
const keys = ids ?? shadowMeta.arrayKeys;
|
|
570
|
-
// --- IMPORTANT: Set the cache BEFORE the recursive calls ---
|
|
571
|
-
// This handles circular references gracefully. We put an empty array in the cache now.
|
|
572
541
|
result = [];
|
|
573
542
|
memo.set(keyToBuild, result);
|
|
574
|
-
// Now, fill the array with the recursively constructed items.
|
|
575
543
|
keys.forEach((itemKey) => {
|
|
576
|
-
result.push(reconstruct(itemKey));
|
|
544
|
+
result.push(reconstruct(itemKey));
|
|
577
545
|
});
|
|
578
546
|
} else if (shadowMeta.fields) {
|
|
579
|
-
// --- IMPORTANT: Set the cache BEFORE the recursive calls ---
|
|
580
547
|
result = {};
|
|
581
548
|
memo.set(keyToBuild, result);
|
|
582
|
-
// Now, fill the object with the recursively constructed items.
|
|
583
549
|
Object.entries(shadowMeta.fields).forEach(([key, fieldPath]) => {
|
|
584
550
|
result[key] = reconstruct(fieldPath as string);
|
|
585
551
|
});
|
|
@@ -594,13 +560,8 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
594
560
|
// Start the process by calling the inner function on the root key.
|
|
595
561
|
return reconstruct(fullKey, validArrayIds);
|
|
596
562
|
},
|
|
597
|
-
getShadowMetadata: (
|
|
598
|
-
key: string,
|
|
599
|
-
path: string[],
|
|
600
|
-
validArrayIds?: string[]
|
|
601
|
-
) => {
|
|
563
|
+
getShadowMetadata: (key: string, path: string[]) => {
|
|
602
564
|
const fullKey = [key, ...path].join('.');
|
|
603
|
-
let data = get().shadowStateStore.get(fullKey);
|
|
604
565
|
|
|
605
566
|
return get().shadowStateStore.get(fullKey);
|
|
606
567
|
},
|
|
@@ -766,36 +727,63 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
766
727
|
itemKey: itemKey, // The exact ID of the removed item
|
|
767
728
|
});
|
|
768
729
|
},
|
|
730
|
+
|
|
769
731
|
updateShadowAtPath: (key, path, newValue) => {
|
|
770
|
-
const newShadowStore = new Map(get().shadowStateStore);
|
|
771
732
|
const fullKey = [key, ...path].join('.');
|
|
772
733
|
|
|
773
|
-
|
|
774
|
-
|
|
734
|
+
// Optimization: Only update if value actually changed
|
|
735
|
+
const existingMeta = get().shadowStateStore.get(fullKey);
|
|
736
|
+
if (existingMeta?.value === newValue && !isSimpleObject(newValue)) {
|
|
737
|
+
return; // Skip update for unchanged primitives
|
|
738
|
+
}
|
|
775
739
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
if (Object.prototype.hasOwnProperty.call(valueToSet, fieldKey)) {
|
|
780
|
-
const childPath = meta.fields[fieldKey];
|
|
781
|
-
const childValue = valueToSet[fieldKey];
|
|
740
|
+
// CHANGE: Don't clone the entire Map, just update in place
|
|
741
|
+
set((state) => {
|
|
742
|
+
const store = state.shadowStateStore;
|
|
782
743
|
|
|
783
|
-
|
|
784
|
-
|
|
744
|
+
if (!isSimpleObject(newValue)) {
|
|
745
|
+
const meta = store.get(fullKey) || {};
|
|
746
|
+
store.set(fullKey, { ...meta, value: newValue });
|
|
747
|
+
} else {
|
|
748
|
+
// Handle objects by iterating
|
|
749
|
+
const processObject = (currentPath: string[], objectToSet: any) => {
|
|
750
|
+
const currentFullKey = [key, ...currentPath].join('.');
|
|
751
|
+
const meta = store.get(currentFullKey);
|
|
752
|
+
|
|
753
|
+
if (meta && meta.fields) {
|
|
754
|
+
for (const fieldKey in objectToSet) {
|
|
755
|
+
if (Object.prototype.hasOwnProperty.call(objectToSet, fieldKey)) {
|
|
756
|
+
const childValue = objectToSet[fieldKey];
|
|
757
|
+
const childFullPath = meta.fields[fieldKey];
|
|
758
|
+
|
|
759
|
+
if (childFullPath) {
|
|
760
|
+
if (isSimpleObject(childValue)) {
|
|
761
|
+
processObject(
|
|
762
|
+
childFullPath.split('.').slice(1),
|
|
763
|
+
childValue
|
|
764
|
+
);
|
|
765
|
+
} else {
|
|
766
|
+
const existingChildMeta = store.get(childFullPath) || {};
|
|
767
|
+
store.set(childFullPath, {
|
|
768
|
+
...existingChildMeta,
|
|
769
|
+
value: childValue,
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
785
774
|
}
|
|
786
775
|
}
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
// This gives you useState-like behavior
|
|
791
|
-
const existing = newShadowStore.get(currentKey) || {};
|
|
792
|
-
newShadowStore.set(currentKey, { ...existing, value: valueToSet });
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
processObject(path, newValue);
|
|
793
779
|
}
|
|
794
|
-
};
|
|
795
780
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
781
|
+
// Only notify after all changes are made
|
|
782
|
+
get().notifyPathSubscribers(fullKey, { type: 'UPDATE', newValue });
|
|
783
|
+
|
|
784
|
+
// Return same reference if using Zustand's immer middleware
|
|
785
|
+
return state;
|
|
786
|
+
});
|
|
799
787
|
},
|
|
800
788
|
selectedIndicesMap: new Map<string, string>(),
|
|
801
789
|
getSelectedIndex: (arrayKey: string, validIds?: string[]): number => {
|