cogsbox-state 0.5.460 → 0.5.462
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 +8 -8
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +1425 -1468
- package/dist/CogsState.jsx.map +1 -1
- package/dist/TRPCValidationLink.d.ts.map +1 -1
- package/dist/TRPCValidationLink.js.map +1 -1
- package/dist/store.d.ts +5 -4
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +179 -165
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +1061 -1128
- package/src/TRPCValidationLink.ts +0 -1
- package/src/store.ts +91 -48
package/src/CogsState.tsx
CHANGED
|
@@ -284,7 +284,7 @@ export type EndType<T, IsArrayElement = false> = {
|
|
|
284
284
|
hideMessage?: boolean;
|
|
285
285
|
}) => JSX.Element;
|
|
286
286
|
lastSynced?: SyncInfo;
|
|
287
|
-
} & (IsArrayElement extends true ? {
|
|
287
|
+
} & (IsArrayElement extends true ? { cutThis: () => void } : {});
|
|
288
288
|
|
|
289
289
|
export type StateObject<T> = (T extends any[]
|
|
290
290
|
? ArrayEndType<T>
|
|
@@ -419,13 +419,7 @@ export type OptionsType<T extends unknown = unknown, TApiParams = never> = {
|
|
|
419
419
|
onSuccess?: (data: any) => void;
|
|
420
420
|
onError?: (error: any) => void;
|
|
421
421
|
};
|
|
422
|
-
middleware?: ({
|
|
423
|
-
updateLog,
|
|
424
|
-
update,
|
|
425
|
-
}: {
|
|
426
|
-
updateLog: UpdateTypeDetail[] | undefined;
|
|
427
|
-
update: UpdateTypeDetail;
|
|
428
|
-
}) => void;
|
|
422
|
+
middleware?: ({ update }: { update: UpdateTypeDetail }) => void;
|
|
429
423
|
|
|
430
424
|
modifyState?: (state: T) => T;
|
|
431
425
|
localStorage?: {
|
|
@@ -574,6 +568,7 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
574
568
|
__syncNotifications?: Record<string, Function>;
|
|
575
569
|
__apiParamsMap?: Record<string, any>;
|
|
576
570
|
__useSync?: UseSyncType<State>;
|
|
571
|
+
__syncSchemas?: Record<string, any>;
|
|
577
572
|
}
|
|
578
573
|
) => {
|
|
579
574
|
let newInitialState = initialState;
|
|
@@ -621,7 +616,12 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
621
616
|
mergedOptions.validation.key = `${opt.validation.key}.${key}`;
|
|
622
617
|
}
|
|
623
618
|
}
|
|
624
|
-
|
|
619
|
+
if (opt?.__syncSchemas?.[key]?.schemas?.validation) {
|
|
620
|
+
mergedOptions.validation = {
|
|
621
|
+
zodSchemaV4: opt.__syncSchemas[key].schemas.validation,
|
|
622
|
+
...existingOptions.validation,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
625
|
if (Object.keys(mergedOptions).length > 0) {
|
|
626
626
|
const existingGlobalOptions = getInitialOptions(key);
|
|
627
627
|
|
|
@@ -692,38 +692,33 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
|
|
|
692
692
|
notifyComponents(stateKey as string);
|
|
693
693
|
}
|
|
694
694
|
|
|
695
|
-
return { useCogsState, setCogsOptions } as CogsApi<State>;
|
|
695
|
+
return { useCogsState, setCogsOptions } as CogsApi<State, never>;
|
|
696
696
|
};
|
|
697
|
-
|
|
698
|
-
// Fix for UseCogsStateHook to support per-key apiParams
|
|
699
697
|
type UseCogsStateHook<
|
|
700
698
|
T extends Record<string, any>,
|
|
701
|
-
TApiParamsMap extends Record<string, any> =
|
|
699
|
+
TApiParamsMap extends Record<string, any> = never,
|
|
702
700
|
> = <StateKey extends keyof TransformedStateType<T> & string>(
|
|
703
701
|
stateKey: StateKey,
|
|
704
|
-
options?:
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
>
|
|
719
|
-
>;
|
|
720
|
-
})
|
|
721
|
-
>
|
|
702
|
+
options?: [TApiParamsMap] extends [never]
|
|
703
|
+
? // When TApiParamsMap is never (no sync)
|
|
704
|
+
Prettify<OptionsType<TransformedStateType<T>[StateKey]>>
|
|
705
|
+
: // When TApiParamsMap exists (sync enabled)
|
|
706
|
+
StateKey extends keyof TApiParamsMap
|
|
707
|
+
? Prettify<
|
|
708
|
+
OptionsType<
|
|
709
|
+
TransformedStateType<T>[StateKey],
|
|
710
|
+
TApiParamsMap[StateKey]
|
|
711
|
+
> & {
|
|
712
|
+
syncOptions: Prettify<SyncOptionsType<TApiParamsMap[StateKey]>>;
|
|
713
|
+
}
|
|
714
|
+
>
|
|
715
|
+
: Prettify<OptionsType<TransformedStateType<T>[StateKey]>>
|
|
722
716
|
) => StateObject<TransformedStateType<T>[StateKey]>;
|
|
723
|
-
|
|
717
|
+
|
|
718
|
+
// Update CogsApi to default to never instead of Record<string, never>
|
|
724
719
|
type CogsApi<
|
|
725
720
|
T extends Record<string, any>,
|
|
726
|
-
TApiParamsMap extends Record<string, any> =
|
|
721
|
+
TApiParamsMap extends Record<string, any> = never,
|
|
727
722
|
> = {
|
|
728
723
|
useCogsState: UseCogsStateHook<T, TApiParamsMap>;
|
|
729
724
|
setCogsOptions: SetCogsOptionsFunc<T>;
|
|
@@ -781,12 +776,13 @@ export function createCogsStateFromSync<
|
|
|
781
776
|
__syncNotifications: syncSchema.notifications,
|
|
782
777
|
__apiParamsMap: apiParamsMap,
|
|
783
778
|
__useSync: useSync,
|
|
779
|
+
__syncSchemas: schemas,
|
|
784
780
|
}) as any;
|
|
785
781
|
}
|
|
786
782
|
const {
|
|
787
783
|
getInitialOptions,
|
|
788
784
|
|
|
789
|
-
|
|
785
|
+
addStateLog,
|
|
790
786
|
updateInitialStateGlobal,
|
|
791
787
|
} = getGlobalStore.getState();
|
|
792
788
|
const saveToLocalStorage = <T,>(
|
|
@@ -1006,8 +1002,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1006
1002
|
|
|
1007
1003
|
let noStateKey = stateKey ? false : true;
|
|
1008
1004
|
const [thisKey] = useState(stateKey ?? uuidv4());
|
|
1009
|
-
|
|
1010
|
-
const componentUpdatesRef = useRef(new Set<string>());
|
|
1005
|
+
|
|
1011
1006
|
const componentIdRef = useRef(componentId ?? uuidv4());
|
|
1012
1007
|
const latestInitialOptionsRef = useRef<OptionsType<TStateObject> | null>(
|
|
1013
1008
|
null
|
|
@@ -1252,6 +1247,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1252
1247
|
notifyComponents(thisKey);
|
|
1253
1248
|
}
|
|
1254
1249
|
}, [thisKey, ...(dependencies || [])]);
|
|
1250
|
+
|
|
1255
1251
|
useLayoutEffect(() => {
|
|
1256
1252
|
if (noStateKey) {
|
|
1257
1253
|
setAndMergeOptions(thisKey as string, {
|
|
@@ -1321,19 +1317,16 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1321
1317
|
}, []);
|
|
1322
1318
|
|
|
1323
1319
|
const syncApiRef = useRef<SyncApi | null>(null);
|
|
1320
|
+
|
|
1324
1321
|
const effectiveSetState = (
|
|
1325
1322
|
newStateOrFunction: UpdateArg<TStateObject> | InsertParams<TStateObject>,
|
|
1326
1323
|
path: string[],
|
|
1327
1324
|
updateObj: UpdateOptions
|
|
1328
1325
|
) => {
|
|
1329
1326
|
const fullPath = [thisKey, ...path].join('.');
|
|
1330
|
-
|
|
1331
|
-
const pathKey = `${thisKey}-${path.join('.')}`;
|
|
1332
|
-
componentUpdatesRef.current.add(pathKey);
|
|
1333
|
-
}
|
|
1327
|
+
|
|
1334
1328
|
const store = getGlobalStore.getState();
|
|
1335
1329
|
|
|
1336
|
-
// FETCH ONCE at the beginning
|
|
1337
1330
|
const shadowMeta = store.getShadowMetadata(thisKey, path);
|
|
1338
1331
|
const nestedShadowValue = store.getShadowValue(fullPath) as TStateObject;
|
|
1339
1332
|
|
|
@@ -1561,15 +1554,11 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1561
1554
|
}
|
|
1562
1555
|
}
|
|
1563
1556
|
|
|
1564
|
-
|
|
1565
|
-
// Assumes `isDeepEqual` is available in this scope.
|
|
1566
|
-
|
|
1567
|
-
const newState = getGlobalStore.getState().getShadowValue(thisKey);
|
|
1568
|
-
const rootMeta = getGlobalStore.getState().getShadowMetadata(thisKey, []);
|
|
1557
|
+
const rootMeta = store.getShadowMetadata(thisKey, []);
|
|
1569
1558
|
const notifiedComponents = new Set<string>();
|
|
1570
1559
|
|
|
1571
1560
|
if (!rootMeta?.components) {
|
|
1572
|
-
return
|
|
1561
|
+
return;
|
|
1573
1562
|
}
|
|
1574
1563
|
|
|
1575
1564
|
// --- PASS 1: Notify specific subscribers based on update type ---
|
|
@@ -1751,26 +1740,7 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1751
1740
|
}
|
|
1752
1741
|
});
|
|
1753
1742
|
notifiedComponents.clear();
|
|
1754
|
-
|
|
1755
|
-
const logs = [...(prevLogs ?? []), newUpdate];
|
|
1756
|
-
const aggregatedLogs = new Map<string, typeof newUpdate>();
|
|
1757
|
-
|
|
1758
|
-
logs.forEach((log) => {
|
|
1759
|
-
const uniqueKey = `${log.stateKey}:${JSON.stringify(log.path)}`;
|
|
1760
|
-
const existing = aggregatedLogs.get(uniqueKey);
|
|
1761
|
-
|
|
1762
|
-
if (existing) {
|
|
1763
|
-
existing.timeStamp = Math.max(existing.timeStamp, log.timeStamp);
|
|
1764
|
-
existing.newValue = log.newValue;
|
|
1765
|
-
existing.oldValue = existing.oldValue ?? log.oldValue;
|
|
1766
|
-
existing.updateType = log.updateType;
|
|
1767
|
-
} else {
|
|
1768
|
-
aggregatedLogs.set(uniqueKey, { ...(log as any) });
|
|
1769
|
-
}
|
|
1770
|
-
});
|
|
1771
|
-
|
|
1772
|
-
return Array.from(aggregatedLogs.values());
|
|
1773
|
-
});
|
|
1743
|
+
addStateLog(thisKey, newUpdate);
|
|
1774
1744
|
|
|
1775
1745
|
saveToLocalStorage(
|
|
1776
1746
|
payload,
|
|
@@ -1781,12 +1751,9 @@ export function useCogsStateFn<TStateObject extends unknown>(
|
|
|
1781
1751
|
|
|
1782
1752
|
if (latestInitialOptionsRef.current?.middleware) {
|
|
1783
1753
|
latestInitialOptionsRef.current!.middleware({
|
|
1784
|
-
updateLog: stateLog,
|
|
1785
1754
|
update: newUpdate,
|
|
1786
1755
|
});
|
|
1787
1756
|
}
|
|
1788
|
-
|
|
1789
|
-
return newState;
|
|
1790
1757
|
};
|
|
1791
1758
|
|
|
1792
1759
|
if (!getGlobalStore.getState().initialStateGlobal[thisKey]) {
|
|
@@ -1959,48 +1926,33 @@ const notifySelectionComponents = (
|
|
|
1959
1926
|
}
|
|
1960
1927
|
}
|
|
1961
1928
|
};
|
|
1929
|
+
|
|
1962
1930
|
function createProxyHandler<T>(
|
|
1963
1931
|
stateKey: string,
|
|
1964
1932
|
effectiveSetState: EffectiveSetState<T>,
|
|
1965
1933
|
componentId: string,
|
|
1966
1934
|
sessionId?: string
|
|
1967
1935
|
): StateObject<T> {
|
|
1968
|
-
|
|
1969
|
-
proxy: any;
|
|
1970
|
-
stateVersion: number;
|
|
1971
|
-
};
|
|
1972
|
-
|
|
1973
|
-
const shapeCache = new Map<string, CacheEntry>();
|
|
1936
|
+
const proxyCache = new Map<string, any>();
|
|
1974
1937
|
let stateVersion = 0;
|
|
1975
1938
|
|
|
1976
|
-
const invalidateCachePath = (path: string[]) => {
|
|
1977
|
-
const pathKey = path.join('.');
|
|
1978
|
-
for (const [key] of shapeCache) {
|
|
1979
|
-
if (key === pathKey || key.startsWith(pathKey + '.')) {
|
|
1980
|
-
shapeCache.delete(key);
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
stateVersion++;
|
|
1984
|
-
};
|
|
1985
|
-
|
|
1986
1939
|
function rebuildStateShape({
|
|
1987
|
-
currentState,
|
|
1988
1940
|
path = [],
|
|
1989
1941
|
meta,
|
|
1990
1942
|
componentId,
|
|
1991
1943
|
}: {
|
|
1992
|
-
currentState: T;
|
|
1993
1944
|
path: string[];
|
|
1994
1945
|
componentId: string;
|
|
1995
1946
|
meta?: MetaData;
|
|
1996
1947
|
}): any {
|
|
1997
|
-
const
|
|
1948
|
+
const derivationSignature = meta
|
|
1949
|
+
? JSON.stringify(meta.validIds || meta.transforms)
|
|
1950
|
+
: '';
|
|
1951
|
+
const cacheKey = path.join('.') + ':' + derivationSignature;
|
|
1998
1952
|
const stateKeyPathKey = [stateKey, ...path].join('.');
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
.getShadowValue(stateKeyPathKey, meta?.validIds);
|
|
2003
|
-
|
|
1953
|
+
if (proxyCache.has(cacheKey)) {
|
|
1954
|
+
return proxyCache.get(cacheKey);
|
|
1955
|
+
}
|
|
2004
1956
|
type CallableStateObject<T> = {
|
|
2005
1957
|
(): T;
|
|
2006
1958
|
} & {
|
|
@@ -2198,1167 +2150,1154 @@ function createProxyHandler<T>(
|
|
|
2198
2150
|
return [];
|
|
2199
2151
|
};
|
|
2200
2152
|
}
|
|
2201
|
-
if (Array.isArray(currentState)) {
|
|
2202
|
-
if (prop === 'getSelected') {
|
|
2203
|
-
return () => {
|
|
2204
|
-
const fullKey = stateKey + '.' + path.join('.');
|
|
2205
|
-
registerComponentDependency(stateKey, componentId, [
|
|
2206
|
-
...path,
|
|
2207
|
-
'getSelected',
|
|
2208
|
-
]);
|
|
2209
|
-
|
|
2210
|
-
const selectedIndicesMap =
|
|
2211
|
-
getGlobalStore.getState().selectedIndicesMap;
|
|
2212
|
-
if (!selectedIndicesMap || !selectedIndicesMap.has(fullKey)) {
|
|
2213
|
-
return undefined;
|
|
2214
|
-
}
|
|
2215
|
-
|
|
2216
|
-
const selectedItemKey = selectedIndicesMap.get(fullKey)!;
|
|
2217
|
-
if (meta?.validIds) {
|
|
2218
|
-
if (!meta.validIds.includes(selectedItemKey)) {
|
|
2219
|
-
return undefined;
|
|
2220
|
-
}
|
|
2221
|
-
}
|
|
2222
2153
|
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2154
|
+
if (prop === 'getSelected') {
|
|
2155
|
+
return () => {
|
|
2156
|
+
const fullKey = stateKey + '.' + path.join('.');
|
|
2157
|
+
registerComponentDependency(stateKey, componentId, [
|
|
2158
|
+
...path,
|
|
2159
|
+
'getSelected',
|
|
2160
|
+
]);
|
|
2161
|
+
|
|
2162
|
+
const selectedIndicesMap =
|
|
2163
|
+
getGlobalStore.getState().selectedIndicesMap;
|
|
2164
|
+
if (!selectedIndicesMap || !selectedIndicesMap.has(fullKey)) {
|
|
2165
|
+
return undefined;
|
|
2166
|
+
}
|
|
2226
2167
|
|
|
2227
|
-
|
|
2168
|
+
const selectedItemKey = selectedIndicesMap.get(fullKey)!;
|
|
2169
|
+
if (meta?.validIds) {
|
|
2170
|
+
if (!meta.validIds.includes(selectedItemKey)) {
|
|
2228
2171
|
return undefined;
|
|
2229
2172
|
}
|
|
2173
|
+
}
|
|
2230
2174
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
componentId: componentId!,
|
|
2235
|
-
});
|
|
2236
|
-
};
|
|
2237
|
-
}
|
|
2238
|
-
if (prop === 'getSelectedIndex') {
|
|
2239
|
-
return () => {
|
|
2240
|
-
const selectedIndex = getGlobalStore
|
|
2241
|
-
.getState()
|
|
2242
|
-
.getSelectedIndex(
|
|
2243
|
-
stateKey + '.' + path.join('.'),
|
|
2244
|
-
meta?.validIds
|
|
2245
|
-
);
|
|
2246
|
-
|
|
2247
|
-
return selectedIndex;
|
|
2248
|
-
};
|
|
2249
|
-
}
|
|
2250
|
-
if (prop === 'clearSelected') {
|
|
2251
|
-
notifySelectionComponents(stateKey, path);
|
|
2252
|
-
return () => {
|
|
2253
|
-
getGlobalStore.getState().clearSelectedIndex({
|
|
2254
|
-
arrayKey: stateKey + '.' + path.join('.'),
|
|
2255
|
-
});
|
|
2256
|
-
};
|
|
2257
|
-
}
|
|
2175
|
+
const value = getGlobalStore
|
|
2176
|
+
.getState()
|
|
2177
|
+
.getShadowValue(selectedItemKey);
|
|
2258
2178
|
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
): VirtualStateObjectResult<any[]> => {
|
|
2263
|
-
const {
|
|
2264
|
-
itemHeight = 50,
|
|
2265
|
-
overscan = 6,
|
|
2266
|
-
stickToBottom = false,
|
|
2267
|
-
scrollStickTolerance = 75,
|
|
2268
|
-
} = options;
|
|
2269
|
-
|
|
2270
|
-
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
2271
|
-
const [range, setRange] = useState({
|
|
2272
|
-
startIndex: 0,
|
|
2273
|
-
endIndex: 10,
|
|
2274
|
-
});
|
|
2275
|
-
const [rerender, forceUpdate] = useState({});
|
|
2276
|
-
const initialScrollRef = useRef(true);
|
|
2277
|
-
|
|
2278
|
-
// Scroll state management
|
|
2279
|
-
const scrollStateRef = useRef({
|
|
2280
|
-
isUserScrolling: false,
|
|
2281
|
-
lastScrollTop: 0,
|
|
2282
|
-
scrollUpCount: 0,
|
|
2283
|
-
isNearBottom: true,
|
|
2284
|
-
});
|
|
2179
|
+
if (!value) {
|
|
2180
|
+
return undefined;
|
|
2181
|
+
}
|
|
2285
2182
|
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2183
|
+
return rebuildStateShape({
|
|
2184
|
+
path: selectedItemKey.split('.').slice(1) as string[],
|
|
2185
|
+
componentId: componentId!,
|
|
2186
|
+
});
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
if (prop === 'getSelectedIndex') {
|
|
2190
|
+
return () => {
|
|
2191
|
+
const selectedIndex = getGlobalStore
|
|
2192
|
+
.getState()
|
|
2193
|
+
.getSelectedIndex(
|
|
2194
|
+
stateKey + '.' + path.join('.'),
|
|
2195
|
+
meta?.validIds
|
|
2289
2196
|
);
|
|
2290
2197
|
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2198
|
+
return selectedIndex;
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
if (prop === 'clearSelected') {
|
|
2202
|
+
notifySelectionComponents(stateKey, path);
|
|
2203
|
+
return () => {
|
|
2204
|
+
getGlobalStore.getState().clearSelectedIndex({
|
|
2205
|
+
arrayKey: stateKey + '.' + path.join('.'),
|
|
2206
|
+
});
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2299
2209
|
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2210
|
+
if (prop === 'useVirtualView') {
|
|
2211
|
+
return (
|
|
2212
|
+
options: VirtualViewOptions
|
|
2213
|
+
): VirtualStateObjectResult<any[]> => {
|
|
2214
|
+
const {
|
|
2215
|
+
itemHeight = 50,
|
|
2216
|
+
overscan = 6,
|
|
2217
|
+
stickToBottom = false,
|
|
2218
|
+
scrollStickTolerance = 75,
|
|
2219
|
+
} = options;
|
|
2220
|
+
|
|
2221
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
2222
|
+
const [range, setRange] = useState({
|
|
2223
|
+
startIndex: 0,
|
|
2224
|
+
endIndex: 10,
|
|
2225
|
+
});
|
|
2226
|
+
const [rerender, forceUpdate] = useState({});
|
|
2227
|
+
const initialScrollRef = useRef(true);
|
|
2228
|
+
|
|
2229
|
+
// Scroll state management
|
|
2230
|
+
const scrollStateRef = useRef({
|
|
2231
|
+
isUserScrolling: false,
|
|
2232
|
+
lastScrollTop: 0,
|
|
2233
|
+
scrollUpCount: 0,
|
|
2234
|
+
isNearBottom: true,
|
|
2235
|
+
});
|
|
2236
|
+
|
|
2237
|
+
// Measurement cache
|
|
2238
|
+
const measurementCache = useRef(
|
|
2239
|
+
new Map<string, { height: number; offset: number }>()
|
|
2240
|
+
);
|
|
2241
|
+
|
|
2242
|
+
// Separate effect for handling rerender updates
|
|
2243
|
+
useLayoutEffect(() => {
|
|
2244
|
+
if (
|
|
2245
|
+
!stickToBottom ||
|
|
2246
|
+
!containerRef.current ||
|
|
2247
|
+
scrollStateRef.current.isUserScrolling
|
|
2248
|
+
)
|
|
2249
|
+
return;
|
|
2306
2250
|
|
|
2307
|
-
const
|
|
2251
|
+
const container = containerRef.current;
|
|
2252
|
+
container.scrollTo({
|
|
2253
|
+
top: container.scrollHeight,
|
|
2254
|
+
behavior: initialScrollRef.current ? 'instant' : 'smooth',
|
|
2255
|
+
});
|
|
2256
|
+
}, [rerender, stickToBottom]);
|
|
2257
|
+
|
|
2258
|
+
const arrayKeys =
|
|
2259
|
+
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2260
|
+
?.arrayKeys || [];
|
|
2261
|
+
|
|
2262
|
+
// Calculate total height and offsets
|
|
2263
|
+
const { totalHeight, itemOffsets } = useMemo(() => {
|
|
2264
|
+
let runningOffset = 0;
|
|
2265
|
+
const offsets = new Map<
|
|
2266
|
+
string,
|
|
2267
|
+
{ height: number; offset: number }
|
|
2268
|
+
>();
|
|
2269
|
+
const allItemKeys =
|
|
2308
2270
|
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2309
2271
|
?.arrayKeys || [];
|
|
2310
2272
|
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
const allItemKeys =
|
|
2319
|
-
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2320
|
-
?.arrayKeys || [];
|
|
2321
|
-
|
|
2322
|
-
allItemKeys.forEach((itemKey) => {
|
|
2323
|
-
const itemPath = itemKey.split('.').slice(1);
|
|
2324
|
-
const measuredHeight =
|
|
2325
|
-
getGlobalStore
|
|
2326
|
-
.getState()
|
|
2327
|
-
.getShadowMetadata(stateKey, itemPath)?.virtualizer
|
|
2328
|
-
?.itemHeight || itemHeight;
|
|
2329
|
-
|
|
2330
|
-
offsets.set(itemKey, {
|
|
2331
|
-
height: measuredHeight,
|
|
2332
|
-
offset: runningOffset,
|
|
2333
|
-
});
|
|
2273
|
+
allItemKeys.forEach((itemKey) => {
|
|
2274
|
+
const itemPath = itemKey.split('.').slice(1);
|
|
2275
|
+
const measuredHeight =
|
|
2276
|
+
getGlobalStore
|
|
2277
|
+
.getState()
|
|
2278
|
+
.getShadowMetadata(stateKey, itemPath)?.virtualizer
|
|
2279
|
+
?.itemHeight || itemHeight;
|
|
2334
2280
|
|
|
2335
|
-
|
|
2281
|
+
offsets.set(itemKey, {
|
|
2282
|
+
height: measuredHeight,
|
|
2283
|
+
offset: runningOffset,
|
|
2336
2284
|
});
|
|
2337
2285
|
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
}, [arrayKeys.length, itemHeight]);
|
|
2341
|
-
|
|
2342
|
-
// Improved initial positioning effect
|
|
2343
|
-
useLayoutEffect(() => {
|
|
2344
|
-
if (
|
|
2345
|
-
stickToBottom &&
|
|
2346
|
-
arrayKeys.length > 0 &&
|
|
2347
|
-
containerRef.current &&
|
|
2348
|
-
!scrollStateRef.current.isUserScrolling &&
|
|
2349
|
-
initialScrollRef.current
|
|
2350
|
-
) {
|
|
2351
|
-
const container = containerRef.current;
|
|
2352
|
-
|
|
2353
|
-
// Wait for container to have dimensions
|
|
2354
|
-
const waitForContainer = () => {
|
|
2355
|
-
if (container.clientHeight > 0) {
|
|
2356
|
-
const visibleCount = Math.ceil(
|
|
2357
|
-
container.clientHeight / itemHeight
|
|
2358
|
-
);
|
|
2359
|
-
const endIndex = arrayKeys.length - 1;
|
|
2360
|
-
const startIndex = Math.max(
|
|
2361
|
-
0,
|
|
2362
|
-
endIndex - visibleCount - overscan
|
|
2363
|
-
);
|
|
2286
|
+
runningOffset += measuredHeight;
|
|
2287
|
+
});
|
|
2364
2288
|
|
|
2365
|
-
|
|
2289
|
+
measurementCache.current = offsets;
|
|
2290
|
+
return { totalHeight: runningOffset, itemOffsets: offsets };
|
|
2291
|
+
}, [arrayKeys.length, itemHeight]);
|
|
2366
2292
|
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2293
|
+
// Improved initial positioning effect
|
|
2294
|
+
useLayoutEffect(() => {
|
|
2295
|
+
if (
|
|
2296
|
+
stickToBottom &&
|
|
2297
|
+
arrayKeys.length > 0 &&
|
|
2298
|
+
containerRef.current &&
|
|
2299
|
+
!scrollStateRef.current.isUserScrolling &&
|
|
2300
|
+
initialScrollRef.current
|
|
2301
|
+
) {
|
|
2302
|
+
const container = containerRef.current;
|
|
2377
2303
|
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2304
|
+
// Wait for container to have dimensions
|
|
2305
|
+
const waitForContainer = () => {
|
|
2306
|
+
if (container.clientHeight > 0) {
|
|
2307
|
+
const visibleCount = Math.ceil(
|
|
2308
|
+
container.clientHeight / itemHeight
|
|
2309
|
+
);
|
|
2310
|
+
const endIndex = arrayKeys.length - 1;
|
|
2311
|
+
const startIndex = Math.max(
|
|
2312
|
+
0,
|
|
2313
|
+
endIndex - visibleCount - overscan
|
|
2314
|
+
);
|
|
2381
2315
|
|
|
2382
|
-
|
|
2383
|
-
const handleScroll = useCallback(() => {
|
|
2384
|
-
const container = containerRef.current;
|
|
2385
|
-
if (!container) return;
|
|
2316
|
+
setRange({ startIndex, endIndex });
|
|
2386
2317
|
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
scrollState.isNearBottom =
|
|
2396
|
-
distanceFromBottom <= scrollStickTolerance;
|
|
2397
|
-
|
|
2398
|
-
// Detect scroll direction
|
|
2399
|
-
if (currentScrollTop < scrollState.lastScrollTop) {
|
|
2400
|
-
// User scrolled up
|
|
2401
|
-
scrollState.scrollUpCount++;
|
|
2402
|
-
|
|
2403
|
-
if (scrollState.scrollUpCount > 3 && wasNearBottom) {
|
|
2404
|
-
// User has deliberately scrolled away from bottom
|
|
2405
|
-
scrollState.isUserScrolling = true;
|
|
2406
|
-
console.log('User scrolled away from bottom');
|
|
2318
|
+
// Ensure scroll after range is set
|
|
2319
|
+
requestAnimationFrame(() => {
|
|
2320
|
+
scrollToBottom('instant');
|
|
2321
|
+
initialScrollRef.current = false; // Mark initial scroll as done
|
|
2322
|
+
});
|
|
2323
|
+
} else {
|
|
2324
|
+
// Container not ready, try again
|
|
2325
|
+
requestAnimationFrame(waitForContainer);
|
|
2407
2326
|
}
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2327
|
+
};
|
|
2328
|
+
|
|
2329
|
+
waitForContainer();
|
|
2330
|
+
}
|
|
2331
|
+
}, [arrayKeys.length, stickToBottom, itemHeight, overscan]);
|
|
2332
|
+
|
|
2333
|
+
// Combined scroll handler
|
|
2334
|
+
const handleScroll = useCallback(() => {
|
|
2335
|
+
const container = containerRef.current;
|
|
2336
|
+
if (!container) return;
|
|
2337
|
+
|
|
2338
|
+
const currentScrollTop = container.scrollTop;
|
|
2339
|
+
const { scrollHeight, clientHeight } = container;
|
|
2340
|
+
const scrollState = scrollStateRef.current;
|
|
2341
|
+
|
|
2342
|
+
// Check if user is near bottom
|
|
2343
|
+
const distanceFromBottom =
|
|
2344
|
+
scrollHeight - (currentScrollTop + clientHeight);
|
|
2345
|
+
const wasNearBottom = scrollState.isNearBottom;
|
|
2346
|
+
scrollState.isNearBottom =
|
|
2347
|
+
distanceFromBottom <= scrollStickTolerance;
|
|
2348
|
+
|
|
2349
|
+
// Detect scroll direction
|
|
2350
|
+
if (currentScrollTop < scrollState.lastScrollTop) {
|
|
2351
|
+
// User scrolled up
|
|
2352
|
+
scrollState.scrollUpCount++;
|
|
2353
|
+
|
|
2354
|
+
if (scrollState.scrollUpCount > 3 && wasNearBottom) {
|
|
2355
|
+
// User has deliberately scrolled away from bottom
|
|
2356
|
+
scrollState.isUserScrolling = true;
|
|
2357
|
+
console.log('User scrolled away from bottom');
|
|
2412
2358
|
}
|
|
2359
|
+
} else if (scrollState.isNearBottom) {
|
|
2360
|
+
// Reset if we're back near the bottom
|
|
2361
|
+
scrollState.isUserScrolling = false;
|
|
2362
|
+
scrollState.scrollUpCount = 0;
|
|
2363
|
+
}
|
|
2413
2364
|
|
|
2414
|
-
|
|
2365
|
+
scrollState.lastScrollTop = currentScrollTop;
|
|
2415
2366
|
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
}
|
|
2367
|
+
// Update visible range
|
|
2368
|
+
let newStartIndex = 0;
|
|
2369
|
+
for (let i = 0; i < arrayKeys.length; i++) {
|
|
2370
|
+
const itemKey = arrayKeys[i];
|
|
2371
|
+
const item = measurementCache.current.get(itemKey!);
|
|
2372
|
+
if (item && item.offset + item.height > currentScrollTop) {
|
|
2373
|
+
newStartIndex = i;
|
|
2374
|
+
break;
|
|
2425
2375
|
}
|
|
2376
|
+
}
|
|
2426
2377
|
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2378
|
+
// Only update if range actually changed
|
|
2379
|
+
if (newStartIndex !== range.startIndex) {
|
|
2380
|
+
const visibleCount = Math.ceil(clientHeight / itemHeight);
|
|
2381
|
+
setRange({
|
|
2382
|
+
startIndex: Math.max(0, newStartIndex - overscan),
|
|
2383
|
+
endIndex: Math.min(
|
|
2384
|
+
arrayKeys.length - 1,
|
|
2385
|
+
newStartIndex + visibleCount + overscan
|
|
2386
|
+
),
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
}, [
|
|
2390
|
+
arrayKeys.length,
|
|
2391
|
+
range.startIndex,
|
|
2392
|
+
itemHeight,
|
|
2393
|
+
overscan,
|
|
2394
|
+
scrollStickTolerance,
|
|
2395
|
+
]);
|
|
2396
|
+
|
|
2397
|
+
// Set up scroll listener
|
|
2398
|
+
useEffect(() => {
|
|
2399
|
+
const container = containerRef.current;
|
|
2400
|
+
if (!container || !stickToBottom) return;
|
|
2401
|
+
|
|
2402
|
+
container.addEventListener('scroll', handleScroll, {
|
|
2403
|
+
passive: true,
|
|
2404
|
+
});
|
|
2445
2405
|
|
|
2446
|
-
|
|
2447
|
-
|
|
2406
|
+
return () => {
|
|
2407
|
+
container.removeEventListener('scroll', handleScroll);
|
|
2408
|
+
};
|
|
2409
|
+
}, [handleScroll, stickToBottom]);
|
|
2410
|
+
const scrollToBottom = useCallback(
|
|
2411
|
+
(behavior: ScrollBehavior = 'smooth') => {
|
|
2448
2412
|
const container = containerRef.current;
|
|
2449
|
-
if (!container
|
|
2413
|
+
if (!container) return;
|
|
2450
2414
|
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2415
|
+
// Reset scroll state
|
|
2416
|
+
scrollStateRef.current.isUserScrolling = false;
|
|
2417
|
+
scrollStateRef.current.isNearBottom = true;
|
|
2418
|
+
scrollStateRef.current.scrollUpCount = 0;
|
|
2454
2419
|
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
const scrollToBottom = useCallback(
|
|
2460
|
-
(behavior: ScrollBehavior = 'smooth') => {
|
|
2461
|
-
const container = containerRef.current;
|
|
2462
|
-
if (!container) return;
|
|
2463
|
-
|
|
2464
|
-
// Reset scroll state
|
|
2465
|
-
scrollStateRef.current.isUserScrolling = false;
|
|
2466
|
-
scrollStateRef.current.isNearBottom = true;
|
|
2467
|
-
scrollStateRef.current.scrollUpCount = 0;
|
|
2468
|
-
|
|
2469
|
-
const performScroll = () => {
|
|
2470
|
-
// Multiple attempts to ensure we hit the bottom
|
|
2471
|
-
const attemptScroll = (attempts = 0) => {
|
|
2472
|
-
if (attempts > 5) return; // Prevent infinite loops
|
|
2473
|
-
|
|
2474
|
-
const currentHeight = container.scrollHeight;
|
|
2475
|
-
const currentScroll = container.scrollTop;
|
|
2476
|
-
const clientHeight = container.clientHeight;
|
|
2477
|
-
|
|
2478
|
-
// Check if we're already at the bottom
|
|
2479
|
-
if (currentScroll + clientHeight >= currentHeight - 1) {
|
|
2480
|
-
return;
|
|
2481
|
-
}
|
|
2420
|
+
const performScroll = () => {
|
|
2421
|
+
// Multiple attempts to ensure we hit the bottom
|
|
2422
|
+
const attemptScroll = (attempts = 0) => {
|
|
2423
|
+
if (attempts > 5) return; // Prevent infinite loops
|
|
2482
2424
|
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
});
|
|
2487
|
-
|
|
2488
|
-
// In slow environments, check again after a short delay
|
|
2489
|
-
setTimeout(() => {
|
|
2490
|
-
const newHeight = container.scrollHeight;
|
|
2491
|
-
const newScroll = container.scrollTop;
|
|
2492
|
-
|
|
2493
|
-
// If height changed or we're not at bottom, try again
|
|
2494
|
-
if (
|
|
2495
|
-
newHeight !== currentHeight ||
|
|
2496
|
-
newScroll + clientHeight < newHeight - 1
|
|
2497
|
-
) {
|
|
2498
|
-
attemptScroll(attempts + 1);
|
|
2499
|
-
}
|
|
2500
|
-
}, 50);
|
|
2501
|
-
};
|
|
2425
|
+
const currentHeight = container.scrollHeight;
|
|
2426
|
+
const currentScroll = container.scrollTop;
|
|
2427
|
+
const clientHeight = container.clientHeight;
|
|
2502
2428
|
|
|
2503
|
-
|
|
2504
|
-
|
|
2429
|
+
// Check if we're already at the bottom
|
|
2430
|
+
if (currentScroll + clientHeight >= currentHeight - 1) {
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2505
2433
|
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
} else {
|
|
2510
|
-
// Fallback to rAF chain
|
|
2511
|
-
requestAnimationFrame(() => {
|
|
2512
|
-
requestAnimationFrame(performScroll);
|
|
2434
|
+
container.scrollTo({
|
|
2435
|
+
top: currentHeight,
|
|
2436
|
+
behavior: behavior,
|
|
2513
2437
|
});
|
|
2514
|
-
}
|
|
2515
|
-
},
|
|
2516
|
-
[]
|
|
2517
|
-
);
|
|
2518
|
-
// Auto-scroll to bottom when new content arrives
|
|
2519
|
-
// Consolidated auto-scroll effect with debouncing
|
|
2520
|
-
useEffect(() => {
|
|
2521
|
-
if (!stickToBottom || !containerRef.current) return;
|
|
2522
|
-
|
|
2523
|
-
const container = containerRef.current;
|
|
2524
|
-
const scrollState = scrollStateRef.current;
|
|
2525
2438
|
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
scrollTimeout = setTimeout(() => {
|
|
2531
|
-
if (
|
|
2532
|
-
!scrollState.isUserScrolling &&
|
|
2533
|
-
scrollState.isNearBottom
|
|
2534
|
-
) {
|
|
2535
|
-
scrollToBottom(
|
|
2536
|
-
initialScrollRef.current ? 'instant' : 'smooth'
|
|
2537
|
-
);
|
|
2538
|
-
}
|
|
2539
|
-
}, 100);
|
|
2540
|
-
};
|
|
2439
|
+
// In slow environments, check again after a short delay
|
|
2440
|
+
setTimeout(() => {
|
|
2441
|
+
const newHeight = container.scrollHeight;
|
|
2442
|
+
const newScroll = container.scrollTop;
|
|
2541
2443
|
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2444
|
+
// If height changed or we're not at bottom, try again
|
|
2445
|
+
if (
|
|
2446
|
+
newHeight !== currentHeight ||
|
|
2447
|
+
newScroll + clientHeight < newHeight - 1
|
|
2448
|
+
) {
|
|
2449
|
+
attemptScroll(attempts + 1);
|
|
2450
|
+
}
|
|
2451
|
+
}, 50);
|
|
2452
|
+
};
|
|
2548
2453
|
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
subtree: true,
|
|
2552
|
-
attributes: true,
|
|
2553
|
-
attributeFilter: ['style', 'class'], // More specific than just 'height'
|
|
2554
|
-
});
|
|
2454
|
+
attemptScroll();
|
|
2455
|
+
};
|
|
2555
2456
|
|
|
2556
|
-
//
|
|
2557
|
-
|
|
2457
|
+
// Use requestIdleCallback for better performance in slow environments
|
|
2458
|
+
if ('requestIdleCallback' in window) {
|
|
2459
|
+
requestIdleCallback(performScroll, { timeout: 100 });
|
|
2460
|
+
} else {
|
|
2461
|
+
// Fallback to rAF chain
|
|
2462
|
+
requestAnimationFrame(() => {
|
|
2463
|
+
requestAnimationFrame(performScroll);
|
|
2464
|
+
});
|
|
2465
|
+
}
|
|
2466
|
+
},
|
|
2467
|
+
[]
|
|
2468
|
+
);
|
|
2469
|
+
// Auto-scroll to bottom when new content arrives
|
|
2470
|
+
// Consolidated auto-scroll effect with debouncing
|
|
2471
|
+
useEffect(() => {
|
|
2472
|
+
if (!stickToBottom || !containerRef.current) return;
|
|
2473
|
+
|
|
2474
|
+
const container = containerRef.current;
|
|
2475
|
+
const scrollState = scrollStateRef.current;
|
|
2476
|
+
|
|
2477
|
+
// Debounced scroll function
|
|
2478
|
+
let scrollTimeout: NodeJS.Timeout;
|
|
2479
|
+
const debouncedScrollToBottom = () => {
|
|
2480
|
+
clearTimeout(scrollTimeout);
|
|
2481
|
+
scrollTimeout = setTimeout(() => {
|
|
2558
2482
|
if (
|
|
2559
|
-
|
|
2560
|
-
|
|
2483
|
+
!scrollState.isUserScrolling &&
|
|
2484
|
+
scrollState.isNearBottom
|
|
2561
2485
|
) {
|
|
2562
|
-
|
|
2486
|
+
scrollToBottom(
|
|
2487
|
+
initialScrollRef.current ? 'instant' : 'smooth'
|
|
2488
|
+
);
|
|
2563
2489
|
}
|
|
2564
|
-
};
|
|
2490
|
+
}, 100);
|
|
2491
|
+
};
|
|
2492
|
+
|
|
2493
|
+
// Single MutationObserver for all DOM changes
|
|
2494
|
+
const observer = new MutationObserver(() => {
|
|
2495
|
+
if (!scrollState.isUserScrolling) {
|
|
2496
|
+
debouncedScrollToBottom();
|
|
2497
|
+
}
|
|
2498
|
+
});
|
|
2565
2499
|
|
|
2566
|
-
|
|
2500
|
+
observer.observe(container, {
|
|
2501
|
+
childList: true,
|
|
2502
|
+
subtree: true,
|
|
2503
|
+
attributes: true,
|
|
2504
|
+
attributeFilter: ['style', 'class'], // More specific than just 'height'
|
|
2505
|
+
});
|
|
2567
2506
|
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
} else {
|
|
2507
|
+
// Handle image loads with event delegation
|
|
2508
|
+
const handleImageLoad = (e: Event) => {
|
|
2509
|
+
if (
|
|
2510
|
+
e.target instanceof HTMLImageElement &&
|
|
2511
|
+
!scrollState.isUserScrolling
|
|
2512
|
+
) {
|
|
2575
2513
|
debouncedScrollToBottom();
|
|
2576
2514
|
}
|
|
2515
|
+
};
|
|
2577
2516
|
|
|
2578
|
-
|
|
2579
|
-
clearTimeout(scrollTimeout);
|
|
2580
|
-
observer.disconnect();
|
|
2581
|
-
container.removeEventListener('load', handleImageLoad, true);
|
|
2582
|
-
};
|
|
2583
|
-
}, [stickToBottom, arrayKeys.length, scrollToBottom]);
|
|
2584
|
-
// Create virtual state
|
|
2585
|
-
const virtualState = useMemo(() => {
|
|
2586
|
-
const store = getGlobalStore.getState();
|
|
2587
|
-
const sourceArray = store.getShadowValue(
|
|
2588
|
-
[stateKey, ...path].join('.')
|
|
2589
|
-
) as any[];
|
|
2590
|
-
const currentKeys =
|
|
2591
|
-
store.getShadowMetadata(stateKey, path)?.arrayKeys || [];
|
|
2592
|
-
|
|
2593
|
-
const slicedArray = sourceArray.slice(
|
|
2594
|
-
range.startIndex,
|
|
2595
|
-
range.endIndex + 1
|
|
2596
|
-
);
|
|
2597
|
-
const slicedIds = currentKeys.slice(
|
|
2598
|
-
range.startIndex,
|
|
2599
|
-
range.endIndex + 1
|
|
2600
|
-
);
|
|
2517
|
+
container.addEventListener('load', handleImageLoad, true);
|
|
2601
2518
|
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
});
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2519
|
+
// Initial scroll with proper timing
|
|
2520
|
+
if (initialScrollRef.current) {
|
|
2521
|
+
// For initial load, wait for next tick to ensure DOM is ready
|
|
2522
|
+
setTimeout(() => {
|
|
2523
|
+
scrollToBottom('instant');
|
|
2524
|
+
}, 0);
|
|
2525
|
+
} else {
|
|
2526
|
+
debouncedScrollToBottom();
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
return () => {
|
|
2530
|
+
clearTimeout(scrollTimeout);
|
|
2531
|
+
observer.disconnect();
|
|
2532
|
+
container.removeEventListener('load', handleImageLoad, true);
|
|
2533
|
+
};
|
|
2534
|
+
}, [stickToBottom, arrayKeys.length, scrollToBottom]);
|
|
2535
|
+
// Create virtual state
|
|
2536
|
+
const virtualState = useMemo(() => {
|
|
2537
|
+
const store = getGlobalStore.getState();
|
|
2538
|
+
const sourceArray = store.getShadowValue(
|
|
2539
|
+
[stateKey, ...path].join('.')
|
|
2540
|
+
) as any[];
|
|
2541
|
+
const currentKeys =
|
|
2542
|
+
store.getShadowMetadata(stateKey, path)?.arrayKeys || [];
|
|
2543
|
+
|
|
2544
|
+
const slicedArray = sourceArray.slice(
|
|
2545
|
+
range.startIndex,
|
|
2546
|
+
range.endIndex + 1
|
|
2547
|
+
);
|
|
2548
|
+
const slicedIds = currentKeys.slice(
|
|
2549
|
+
range.startIndex,
|
|
2550
|
+
range.endIndex + 1
|
|
2551
|
+
);
|
|
2552
|
+
|
|
2553
|
+
return rebuildStateShape({
|
|
2554
|
+
path,
|
|
2555
|
+
componentId: componentId!,
|
|
2556
|
+
meta: { ...meta, validIds: slicedIds },
|
|
2557
|
+
});
|
|
2558
|
+
}, [range.startIndex, range.endIndex, arrayKeys.length]);
|
|
2559
|
+
|
|
2560
|
+
return {
|
|
2561
|
+
virtualState,
|
|
2562
|
+
virtualizerProps: {
|
|
2563
|
+
outer: {
|
|
2564
|
+
ref: containerRef,
|
|
2565
|
+
style: {
|
|
2566
|
+
overflowY: 'auto',
|
|
2567
|
+
height: '100%',
|
|
2568
|
+
position: 'relative',
|
|
2626
2569
|
},
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
)?.offset || 0
|
|
2633
|
-
}px)`,
|
|
2634
|
-
},
|
|
2570
|
+
},
|
|
2571
|
+
inner: {
|
|
2572
|
+
style: {
|
|
2573
|
+
height: `${totalHeight}px`,
|
|
2574
|
+
position: 'relative',
|
|
2635
2575
|
},
|
|
2636
2576
|
},
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
measurementCache.current.get(arrayKeys[index]!)?.offset ||
|
|
2645
|
-
0;
|
|
2646
|
-
containerRef.current.scrollTo({ top: offset, behavior });
|
|
2647
|
-
}
|
|
2577
|
+
list: {
|
|
2578
|
+
style: {
|
|
2579
|
+
transform: `translateY(${
|
|
2580
|
+
measurementCache.current.get(arrayKeys[range.startIndex]!)
|
|
2581
|
+
?.offset || 0
|
|
2582
|
+
}px)`,
|
|
2583
|
+
},
|
|
2648
2584
|
},
|
|
2649
|
-
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
if (prop === 'stateMap') {
|
|
2653
|
-
return (
|
|
2654
|
-
callbackfn: (
|
|
2655
|
-
setter: any,
|
|
2585
|
+
},
|
|
2586
|
+
scrollToBottom,
|
|
2587
|
+
scrollToIndex: (
|
|
2656
2588
|
index: number,
|
|
2589
|
+
behavior: ScrollBehavior = 'smooth'
|
|
2590
|
+
) => {
|
|
2591
|
+
if (containerRef.current && arrayKeys[index]) {
|
|
2592
|
+
const offset =
|
|
2593
|
+
measurementCache.current.get(arrayKeys[index]!)?.offset ||
|
|
2594
|
+
0;
|
|
2595
|
+
containerRef.current.scrollTo({ top: offset, behavior });
|
|
2596
|
+
}
|
|
2597
|
+
},
|
|
2598
|
+
};
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
if (prop === 'stateMap') {
|
|
2602
|
+
return (
|
|
2603
|
+
callbackfn: (
|
|
2604
|
+
setter: any,
|
|
2605
|
+
index: number,
|
|
2606
|
+
|
|
2607
|
+
arraySetter: any
|
|
2608
|
+
) => void
|
|
2609
|
+
) => {
|
|
2610
|
+
const [arrayKeys, setArrayKeys] = useState<any>(
|
|
2611
|
+
meta?.validIds ??
|
|
2612
|
+
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2613
|
+
?.arrayKeys
|
|
2614
|
+
);
|
|
2615
|
+
// getGlobalStore.getState().subscribeToPath(stateKeyPathKey, () => {
|
|
2616
|
+
// console.log(
|
|
2617
|
+
// "stateKeyPathKeyccccccccccccccccc",
|
|
2618
|
+
// stateKeyPathKey
|
|
2619
|
+
// );
|
|
2620
|
+
// setArrayKeys(
|
|
2621
|
+
// getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2622
|
+
// );
|
|
2623
|
+
// });
|
|
2624
|
+
|
|
2625
|
+
const shadowValue = getGlobalStore
|
|
2626
|
+
.getState()
|
|
2627
|
+
.getShadowValue(stateKeyPathKey, meta?.validIds) as any[];
|
|
2628
|
+
if (!arrayKeys) {
|
|
2629
|
+
throw new Error('No array keys found for mapping');
|
|
2630
|
+
}
|
|
2631
|
+
const arraySetter = rebuildStateShape({
|
|
2632
|
+
path,
|
|
2633
|
+
componentId: componentId!,
|
|
2634
|
+
meta,
|
|
2635
|
+
});
|
|
2657
2636
|
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
meta?.validIds ??
|
|
2663
|
-
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2664
|
-
?.arrayKeys
|
|
2665
|
-
);
|
|
2666
|
-
// getGlobalStore.getState().subscribeToPath(stateKeyPathKey, () => {
|
|
2667
|
-
// console.log(
|
|
2668
|
-
// "stateKeyPathKeyccccccccccccccccc",
|
|
2669
|
-
// stateKeyPathKey
|
|
2670
|
-
// );
|
|
2671
|
-
// setArrayKeys(
|
|
2672
|
-
// getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2673
|
-
// );
|
|
2674
|
-
// });
|
|
2675
|
-
|
|
2676
|
-
const shadowValue = getGlobalStore
|
|
2677
|
-
.getState()
|
|
2678
|
-
.getShadowValue(stateKeyPathKey, meta?.validIds) as any[];
|
|
2679
|
-
if (!arrayKeys) {
|
|
2680
|
-
throw new Error('No array keys found for mapping');
|
|
2681
|
-
}
|
|
2682
|
-
const arraySetter = rebuildStateShape({
|
|
2683
|
-
currentState: shadowValue as any,
|
|
2684
|
-
path,
|
|
2637
|
+
return shadowValue.map((item, index) => {
|
|
2638
|
+
const itemPath = arrayKeys[index]?.split('.').slice(1);
|
|
2639
|
+
const itemSetter = rebuildStateShape({
|
|
2640
|
+
path: itemPath as any,
|
|
2685
2641
|
componentId: componentId!,
|
|
2686
2642
|
meta,
|
|
2687
2643
|
});
|
|
2688
2644
|
|
|
2689
|
-
return
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
currentState: item,
|
|
2693
|
-
path: itemPath as any,
|
|
2694
|
-
componentId: componentId!,
|
|
2695
|
-
meta,
|
|
2696
|
-
});
|
|
2697
|
-
|
|
2698
|
-
return callbackfn(
|
|
2699
|
-
itemSetter,
|
|
2700
|
-
index,
|
|
2645
|
+
return callbackfn(
|
|
2646
|
+
itemSetter,
|
|
2647
|
+
index,
|
|
2701
2648
|
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2649
|
+
arraySetter
|
|
2650
|
+
);
|
|
2651
|
+
});
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2707
2654
|
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2655
|
+
if (prop === '$stateMap') {
|
|
2656
|
+
return (callbackfn: any) =>
|
|
2657
|
+
createElement(SignalMapRenderer, {
|
|
2658
|
+
proxy: {
|
|
2659
|
+
_stateKey: stateKey,
|
|
2660
|
+
_path: path,
|
|
2661
|
+
_mapFn: callbackfn,
|
|
2662
|
+
_meta: meta,
|
|
2663
|
+
},
|
|
2664
|
+
rebuildStateShape,
|
|
2665
|
+
});
|
|
2666
|
+
} // In createProxyHandler -> handler -> get -> if (Array.isArray(currentState))
|
|
2667
|
+
|
|
2668
|
+
if (prop === 'stateFind') {
|
|
2669
|
+
return (
|
|
2670
|
+
callbackfn: (value: any, index: number) => boolean
|
|
2671
|
+
): StateObject<any> | undefined => {
|
|
2672
|
+
// 1. Use the correct set of keys: filtered/sorted from meta, or all keys from the store.
|
|
2673
|
+
const arrayKeys =
|
|
2674
|
+
meta?.validIds ??
|
|
2675
|
+
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2676
|
+
?.arrayKeys;
|
|
2677
|
+
|
|
2678
|
+
if (!arrayKeys) {
|
|
2679
|
+
return undefined;
|
|
2680
|
+
}
|
|
2720
2681
|
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
// 1. Use the correct set of keys: filtered/sorted from meta, or all keys from the store.
|
|
2726
|
-
const arrayKeys =
|
|
2727
|
-
meta?.validIds ??
|
|
2728
|
-
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2729
|
-
?.arrayKeys;
|
|
2682
|
+
// 2. Iterate through the keys, get the value for each, and run the callback.
|
|
2683
|
+
for (let i = 0; i < arrayKeys.length; i++) {
|
|
2684
|
+
const itemKey = arrayKeys[i];
|
|
2685
|
+
if (!itemKey) continue; // Safety check
|
|
2730
2686
|
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2687
|
+
const itemValue = getGlobalStore
|
|
2688
|
+
.getState()
|
|
2689
|
+
.getShadowValue(itemKey);
|
|
2734
2690
|
|
|
2735
|
-
//
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2691
|
+
// 3. If the callback returns true, we've found our item.
|
|
2692
|
+
if (callbackfn(itemValue, i)) {
|
|
2693
|
+
// Get the item's path relative to the stateKey (e.g., ['messages', '42'] -> ['42'])
|
|
2694
|
+
const itemPath = itemKey.split('.').slice(1);
|
|
2739
2695
|
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
// Get the item's path relative to the stateKey (e.g., ['messages', '42'] -> ['42'])
|
|
2747
|
-
const itemPath = itemKey.split('.').slice(1);
|
|
2748
|
-
|
|
2749
|
-
// 4. Rebuild a new, fully functional StateObject for just that item and return it.
|
|
2750
|
-
return rebuildStateShape({
|
|
2751
|
-
currentState: itemValue,
|
|
2752
|
-
path: itemPath,
|
|
2753
|
-
componentId: componentId,
|
|
2754
|
-
meta, // Pass along meta for potential further chaining
|
|
2755
|
-
});
|
|
2756
|
-
}
|
|
2696
|
+
// 4. Rebuild a new, fully functional StateObject for just that item and return it.
|
|
2697
|
+
return rebuildStateShape({
|
|
2698
|
+
path: itemPath,
|
|
2699
|
+
componentId: componentId,
|
|
2700
|
+
meta, // Pass along meta for potential further chaining
|
|
2701
|
+
});
|
|
2757
2702
|
}
|
|
2703
|
+
}
|
|
2758
2704
|
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2705
|
+
// 5. If the loop finishes without finding anything, return undefined.
|
|
2706
|
+
return undefined;
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
if (prop === 'stateFilter') {
|
|
2710
|
+
return (callbackfn: (value: any, index: number) => boolean) => {
|
|
2711
|
+
const currentState = getGlobalStore
|
|
2712
|
+
.getState()
|
|
2713
|
+
.getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
|
|
2714
|
+
if (!Array.isArray(currentState)) return [];
|
|
2715
|
+
const arrayKeys =
|
|
2716
|
+
meta?.validIds ??
|
|
2717
|
+
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2718
|
+
?.arrayKeys;
|
|
2719
|
+
|
|
2720
|
+
if (!arrayKeys) {
|
|
2721
|
+
throw new Error('No array keys found for filtering.');
|
|
2722
|
+
}
|
|
2773
2723
|
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
}
|
|
2782
|
-
return false;
|
|
2724
|
+
const newValidIds: string[] = [];
|
|
2725
|
+
const filteredArray = currentState.filter(
|
|
2726
|
+
(val: any, index: number) => {
|
|
2727
|
+
const didPass = callbackfn(val, index);
|
|
2728
|
+
if (didPass) {
|
|
2729
|
+
newValidIds.push(arrayKeys[index]!);
|
|
2730
|
+
return true;
|
|
2783
2731
|
}
|
|
2784
|
-
|
|
2732
|
+
return false;
|
|
2733
|
+
}
|
|
2734
|
+
);
|
|
2785
2735
|
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
transforms
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2736
|
+
return rebuildStateShape({
|
|
2737
|
+
path,
|
|
2738
|
+
componentId: componentId!,
|
|
2739
|
+
meta: {
|
|
2740
|
+
validIds: newValidIds,
|
|
2741
|
+
transforms: [
|
|
2742
|
+
...(meta?.transforms || []),
|
|
2743
|
+
{
|
|
2744
|
+
type: 'filter',
|
|
2745
|
+
fn: callbackfn,
|
|
2746
|
+
},
|
|
2747
|
+
],
|
|
2748
|
+
},
|
|
2749
|
+
});
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
if (prop === 'stateSort') {
|
|
2753
|
+
return (compareFn: (a: any, b: any) => number) => {
|
|
2754
|
+
const currentState = getGlobalStore
|
|
2755
|
+
.getState()
|
|
2756
|
+
.getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
|
|
2757
|
+
if (!Array.isArray(currentState)) return []; // Guard clause
|
|
2758
|
+
const arrayKeys =
|
|
2759
|
+
meta?.validIds ??
|
|
2760
|
+
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
2761
|
+
?.arrayKeys;
|
|
2762
|
+
if (!arrayKeys) {
|
|
2763
|
+
throw new Error('No array keys found for sorting');
|
|
2764
|
+
}
|
|
2765
|
+
const itemsWithIds = currentState.map((item, index) => ({
|
|
2766
|
+
item,
|
|
2767
|
+
key: arrayKeys[index],
|
|
2768
|
+
}));
|
|
2769
|
+
|
|
2770
|
+
itemsWithIds
|
|
2771
|
+
.sort((a, b) => compareFn(a.item, b.item))
|
|
2772
|
+
.filter(Boolean);
|
|
2773
|
+
|
|
2774
|
+
return rebuildStateShape({
|
|
2775
|
+
path,
|
|
2776
|
+
componentId: componentId!,
|
|
2777
|
+
meta: {
|
|
2778
|
+
validIds: itemsWithIds.map((i) => i.key) as string[],
|
|
2779
|
+
transforms: [
|
|
2780
|
+
...(meta?.transforms || []),
|
|
2781
|
+
{ type: 'sort', fn: compareFn },
|
|
2782
|
+
],
|
|
2783
|
+
},
|
|
2784
|
+
});
|
|
2785
|
+
};
|
|
2786
|
+
}
|
|
2787
|
+
// In createProxyHandler, inside the get trap where you have other array methods:
|
|
2788
|
+
if (prop === 'stream') {
|
|
2789
|
+
return function <U = InferArrayElement<T>, R = U>(
|
|
2790
|
+
options: StreamOptions<U, R> = {}
|
|
2791
|
+
): StreamHandle<U> {
|
|
2792
|
+
const {
|
|
2793
|
+
bufferSize = 100,
|
|
2794
|
+
flushInterval = 100,
|
|
2795
|
+
bufferStrategy = 'accumulate',
|
|
2796
|
+
store,
|
|
2797
|
+
onFlush,
|
|
2798
|
+
} = options;
|
|
2799
|
+
|
|
2800
|
+
let buffer: U[] = [];
|
|
2801
|
+
let isPaused = false;
|
|
2802
|
+
let flushTimer: NodeJS.Timeout | null = null;
|
|
2803
|
+
|
|
2804
|
+
const addToBuffer = (item: U) => {
|
|
2805
|
+
if (isPaused) return;
|
|
2806
|
+
|
|
2807
|
+
if (bufferStrategy === 'sliding' && buffer.length >= bufferSize) {
|
|
2808
|
+
buffer.shift();
|
|
2809
|
+
} else if (
|
|
2810
|
+
bufferStrategy === 'dropping' &&
|
|
2811
|
+
buffer.length >= bufferSize
|
|
2812
|
+
) {
|
|
2813
|
+
return;
|
|
2811
2814
|
}
|
|
2812
|
-
const itemsWithIds = currentState.map((item, index) => ({
|
|
2813
|
-
item,
|
|
2814
|
-
key: arrayKeys[index],
|
|
2815
|
-
}));
|
|
2816
2815
|
|
|
2817
|
-
|
|
2818
|
-
.sort((a, b) => compareFn(a.item, b.item))
|
|
2819
|
-
.filter(Boolean);
|
|
2816
|
+
buffer.push(item);
|
|
2820
2817
|
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
componentId: componentId!,
|
|
2825
|
-
meta: {
|
|
2826
|
-
validIds: itemsWithIds.map((i) => i.key) as string[],
|
|
2827
|
-
transforms: [
|
|
2828
|
-
...(meta?.transforms || []),
|
|
2829
|
-
{ type: 'sort', fn: compareFn },
|
|
2830
|
-
],
|
|
2831
|
-
},
|
|
2832
|
-
});
|
|
2818
|
+
if (buffer.length >= bufferSize) {
|
|
2819
|
+
flushBuffer();
|
|
2820
|
+
}
|
|
2833
2821
|
};
|
|
2834
|
-
}
|
|
2835
|
-
// In createProxyHandler, inside the get trap where you have other array methods:
|
|
2836
|
-
if (prop === 'stream') {
|
|
2837
|
-
return function <U = InferArrayElement<T>, R = U>(
|
|
2838
|
-
options: StreamOptions<U, R> = {}
|
|
2839
|
-
): StreamHandle<U> {
|
|
2840
|
-
const {
|
|
2841
|
-
bufferSize = 100,
|
|
2842
|
-
flushInterval = 100,
|
|
2843
|
-
bufferStrategy = 'accumulate',
|
|
2844
|
-
store,
|
|
2845
|
-
onFlush,
|
|
2846
|
-
} = options;
|
|
2847
|
-
|
|
2848
|
-
let buffer: U[] = [];
|
|
2849
|
-
let isPaused = false;
|
|
2850
|
-
let flushTimer: NodeJS.Timeout | null = null;
|
|
2851
|
-
|
|
2852
|
-
const addToBuffer = (item: U) => {
|
|
2853
|
-
if (isPaused) return;
|
|
2854
2822
|
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
buffer.length >= bufferSize
|
|
2858
|
-
) {
|
|
2859
|
-
buffer.shift();
|
|
2860
|
-
} else if (
|
|
2861
|
-
bufferStrategy === 'dropping' &&
|
|
2862
|
-
buffer.length >= bufferSize
|
|
2863
|
-
) {
|
|
2864
|
-
return;
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
buffer.push(item);
|
|
2823
|
+
const flushBuffer = () => {
|
|
2824
|
+
if (buffer.length === 0) return;
|
|
2868
2825
|
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
}
|
|
2872
|
-
};
|
|
2826
|
+
const toFlush = [...buffer];
|
|
2827
|
+
buffer = [];
|
|
2873
2828
|
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
if (store) {
|
|
2881
|
-
const result = store(toFlush);
|
|
2882
|
-
if (result !== undefined) {
|
|
2883
|
-
const items = Array.isArray(result) ? result : [result];
|
|
2884
|
-
items.forEach((item) => {
|
|
2885
|
-
effectiveSetState(item as any, path, {
|
|
2886
|
-
updateType: 'insert',
|
|
2887
|
-
});
|
|
2888
|
-
});
|
|
2889
|
-
}
|
|
2890
|
-
} else {
|
|
2891
|
-
toFlush.forEach((item) => {
|
|
2829
|
+
if (store) {
|
|
2830
|
+
const result = store(toFlush);
|
|
2831
|
+
if (result !== undefined) {
|
|
2832
|
+
const items = Array.isArray(result) ? result : [result];
|
|
2833
|
+
items.forEach((item) => {
|
|
2892
2834
|
effectiveSetState(item as any, path, {
|
|
2893
2835
|
updateType: 'insert',
|
|
2894
2836
|
});
|
|
2895
2837
|
});
|
|
2896
2838
|
}
|
|
2839
|
+
} else {
|
|
2840
|
+
toFlush.forEach((item) => {
|
|
2841
|
+
effectiveSetState(item as any, path, {
|
|
2842
|
+
updateType: 'insert',
|
|
2843
|
+
});
|
|
2844
|
+
});
|
|
2845
|
+
}
|
|
2897
2846
|
|
|
2898
|
-
|
|
2899
|
-
|
|
2847
|
+
onFlush?.(toFlush);
|
|
2848
|
+
};
|
|
2900
2849
|
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2850
|
+
if (flushInterval > 0) {
|
|
2851
|
+
flushTimer = setInterval(flushBuffer, flushInterval);
|
|
2852
|
+
}
|
|
2904
2853
|
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
streams.set(streamId, { buffer, flushTimer });
|
|
2854
|
+
const streamId = uuidv4();
|
|
2855
|
+
const currentMeta =
|
|
2856
|
+
getGlobalStore.getState().getShadowMetadata(stateKey, path) || {};
|
|
2857
|
+
const streams = currentMeta.streams || new Map();
|
|
2858
|
+
streams.set(streamId, { buffer, flushTimer });
|
|
2911
2859
|
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2860
|
+
getGlobalStore.getState().setShadowMetadata(stateKey, path, {
|
|
2861
|
+
...currentMeta,
|
|
2862
|
+
streams,
|
|
2863
|
+
});
|
|
2916
2864
|
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2865
|
+
return {
|
|
2866
|
+
write: (data: U) => addToBuffer(data),
|
|
2867
|
+
writeMany: (data: U[]) => data.forEach(addToBuffer),
|
|
2868
|
+
flush: () => flushBuffer(),
|
|
2869
|
+
pause: () => {
|
|
2870
|
+
isPaused = true;
|
|
2871
|
+
},
|
|
2872
|
+
resume: () => {
|
|
2873
|
+
isPaused = false;
|
|
2874
|
+
if (buffer.length > 0) flushBuffer();
|
|
2875
|
+
},
|
|
2876
|
+
close: () => {
|
|
2877
|
+
flushBuffer();
|
|
2878
|
+
if (flushTimer) clearInterval(flushTimer);
|
|
2931
2879
|
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
};
|
|
2880
|
+
const meta = getGlobalStore
|
|
2881
|
+
.getState()
|
|
2882
|
+
.getShadowMetadata(stateKey, path);
|
|
2883
|
+
if (meta?.streams) {
|
|
2884
|
+
meta.streams.delete(streamId);
|
|
2885
|
+
}
|
|
2886
|
+
},
|
|
2940
2887
|
};
|
|
2941
|
-
}
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2942
2890
|
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2891
|
+
if (prop === 'stateList') {
|
|
2892
|
+
return (
|
|
2893
|
+
callbackfn: (
|
|
2894
|
+
setter: any,
|
|
2895
|
+
index: number,
|
|
2896
|
+
arraySetter: any
|
|
2897
|
+
) => ReactNode
|
|
2898
|
+
) => {
|
|
2899
|
+
const StateListWrapper = () => {
|
|
2900
|
+
const componentIdsRef = useRef<Map<string, string>>(new Map());
|
|
2901
|
+
|
|
2902
|
+
const cacheKey =
|
|
2903
|
+
meta?.transforms && meta.transforms.length > 0
|
|
2904
|
+
? `${componentId}-${hashTransforms(meta.transforms)}`
|
|
2905
|
+
: `${componentId}-base`;
|
|
2906
|
+
|
|
2907
|
+
const [updateTrigger, forceUpdate] = useState({});
|
|
2908
|
+
|
|
2909
|
+
const { validIds, arrayValues } = useMemo(() => {
|
|
2910
|
+
const cached = getGlobalStore
|
|
2911
|
+
.getState()
|
|
2912
|
+
.getShadowMetadata(stateKey, path)
|
|
2913
|
+
?.transformCaches?.get(cacheKey);
|
|
2953
2914
|
|
|
2954
|
-
|
|
2955
|
-
meta?.transforms && meta.transforms.length > 0
|
|
2956
|
-
? `${componentId}-${hashTransforms(meta.transforms)}`
|
|
2957
|
-
: `${componentId}-base`;
|
|
2915
|
+
let freshValidIds: string[];
|
|
2958
2916
|
|
|
2959
|
-
|
|
2917
|
+
if (cached && cached.validIds) {
|
|
2918
|
+
freshValidIds = cached.validIds;
|
|
2919
|
+
} else {
|
|
2920
|
+
freshValidIds = applyTransforms(
|
|
2921
|
+
stateKey,
|
|
2922
|
+
path,
|
|
2923
|
+
meta?.transforms
|
|
2924
|
+
);
|
|
2960
2925
|
|
|
2961
|
-
|
|
2962
|
-
const cached = getGlobalStore
|
|
2926
|
+
getGlobalStore
|
|
2963
2927
|
.getState()
|
|
2964
|
-
.
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
freshValidIds = cached.validIds;
|
|
2971
|
-
} else {
|
|
2972
|
-
freshValidIds = applyTransforms(
|
|
2973
|
-
stateKey,
|
|
2974
|
-
path,
|
|
2975
|
-
meta?.transforms
|
|
2976
|
-
);
|
|
2977
|
-
|
|
2978
|
-
getGlobalStore
|
|
2979
|
-
.getState()
|
|
2980
|
-
.setTransformCache(stateKey, path, cacheKey, {
|
|
2981
|
-
validIds: freshValidIds,
|
|
2982
|
-
computedAt: Date.now(),
|
|
2983
|
-
transforms: meta?.transforms || [],
|
|
2984
|
-
});
|
|
2985
|
-
}
|
|
2928
|
+
.setTransformCache(stateKey, path, cacheKey, {
|
|
2929
|
+
validIds: freshValidIds,
|
|
2930
|
+
computedAt: Date.now(),
|
|
2931
|
+
transforms: meta?.transforms || [],
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2986
2934
|
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2935
|
+
const freshValues = getGlobalStore
|
|
2936
|
+
.getState()
|
|
2937
|
+
.getShadowValue(stateKeyPathKey, freshValidIds);
|
|
2990
2938
|
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2939
|
+
return {
|
|
2940
|
+
validIds: freshValidIds,
|
|
2941
|
+
arrayValues: freshValues || [],
|
|
2942
|
+
};
|
|
2943
|
+
}, [cacheKey, updateTrigger]);
|
|
2996
2944
|
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
2945
|
+
useEffect(() => {
|
|
2946
|
+
const unsubscribe = getGlobalStore
|
|
2947
|
+
.getState()
|
|
2948
|
+
.subscribeToPath(stateKeyPathKey, (e) => {
|
|
2949
|
+
// A data change has occurred for the source array.
|
|
3002
2950
|
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
}
|
|
2951
|
+
if (e.type === 'GET_SELECTED') {
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
const shadowMeta = getGlobalStore
|
|
2955
|
+
.getState()
|
|
2956
|
+
.getShadowMetadata(stateKey, path);
|
|
2957
|
+
|
|
2958
|
+
const caches = shadowMeta?.transformCaches;
|
|
2959
|
+
if (caches) {
|
|
2960
|
+
// Iterate over ALL keys in the cache map.
|
|
2961
|
+
for (const key of caches.keys()) {
|
|
2962
|
+
// If the key belongs to this component instance, delete it.
|
|
2963
|
+
// This purges caches for 'sort by name', 'sort by score', etc.
|
|
2964
|
+
if (key.startsWith(componentId)) {
|
|
2965
|
+
caches.delete(key);
|
|
3019
2966
|
}
|
|
3020
2967
|
}
|
|
2968
|
+
}
|
|
3021
2969
|
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
return () => {
|
|
3032
|
-
unsubscribe();
|
|
3033
|
-
};
|
|
2970
|
+
if (
|
|
2971
|
+
e.type === 'INSERT' ||
|
|
2972
|
+
e.type === 'REMOVE' ||
|
|
2973
|
+
e.type === 'CLEAR_SELECTION'
|
|
2974
|
+
) {
|
|
2975
|
+
forceUpdate({});
|
|
2976
|
+
}
|
|
2977
|
+
});
|
|
3034
2978
|
|
|
3035
|
-
|
|
3036
|
-
|
|
2979
|
+
return () => {
|
|
2980
|
+
unsubscribe();
|
|
2981
|
+
};
|
|
3037
2982
|
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
}
|
|
2983
|
+
// This effect's logic now depends on the componentId to perform the purge.
|
|
2984
|
+
}, [componentId, stateKeyPathKey]);
|
|
3041
2985
|
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
componentId: componentId!,
|
|
3046
|
-
meta: {
|
|
3047
|
-
...meta,
|
|
3048
|
-
validIds: validIds,
|
|
3049
|
-
},
|
|
3050
|
-
});
|
|
2986
|
+
if (!Array.isArray(arrayValues)) {
|
|
2987
|
+
return null;
|
|
2988
|
+
}
|
|
3051
2989
|
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
2990
|
+
const arraySetter = rebuildStateShape({
|
|
2991
|
+
path,
|
|
2992
|
+
componentId: componentId!,
|
|
2993
|
+
meta: {
|
|
2994
|
+
...meta,
|
|
2995
|
+
validIds: validIds,
|
|
2996
|
+
},
|
|
2997
|
+
});
|
|
3056
2998
|
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
2999
|
+
return (
|
|
3000
|
+
<>
|
|
3001
|
+
{arrayValues.map((item, localIndex) => {
|
|
3002
|
+
const itemKey = validIds[localIndex];
|
|
3060
3003
|
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
itemComponentId = uuidv4();
|
|
3065
|
-
componentIdsRef.current.set(itemKey, itemComponentId);
|
|
3066
|
-
}
|
|
3004
|
+
if (!itemKey) {
|
|
3005
|
+
return null;
|
|
3006
|
+
}
|
|
3067
3007
|
|
|
3068
|
-
|
|
3008
|
+
let itemComponentId = componentIdsRef.current.get(itemKey);
|
|
3009
|
+
if (!itemComponentId) {
|
|
3010
|
+
itemComponentId = uuidv4();
|
|
3011
|
+
componentIdsRef.current.set(itemKey, itemComponentId);
|
|
3012
|
+
}
|
|
3069
3013
|
|
|
3070
|
-
|
|
3071
|
-
key: itemKey,
|
|
3072
|
-
stateKey,
|
|
3073
|
-
itemComponentId,
|
|
3074
|
-
itemPath,
|
|
3075
|
-
localIndex,
|
|
3076
|
-
arraySetter,
|
|
3077
|
-
rebuildStateShape,
|
|
3078
|
-
renderFn: callbackfn,
|
|
3079
|
-
});
|
|
3080
|
-
})}
|
|
3081
|
-
</>
|
|
3082
|
-
);
|
|
3083
|
-
};
|
|
3014
|
+
const itemPath = itemKey.split('.').slice(1);
|
|
3084
3015
|
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3016
|
+
return createElement(MemoizedCogsItemWrapper, {
|
|
3017
|
+
key: itemKey,
|
|
3018
|
+
stateKey,
|
|
3019
|
+
itemComponentId,
|
|
3020
|
+
itemPath,
|
|
3021
|
+
localIndex,
|
|
3022
|
+
arraySetter,
|
|
3023
|
+
rebuildStateShape,
|
|
3024
|
+
renderFn: callbackfn,
|
|
3025
|
+
});
|
|
3026
|
+
})}
|
|
3027
|
+
</>
|
|
3095
3028
|
);
|
|
3096
|
-
return rebuildStateShape({
|
|
3097
|
-
currentState: flattenedResults as any,
|
|
3098
|
-
path: [...path, '[*]', fieldName],
|
|
3099
|
-
componentId: componentId!,
|
|
3100
|
-
meta,
|
|
3101
|
-
});
|
|
3102
|
-
};
|
|
3103
|
-
}
|
|
3104
|
-
if (prop === 'index') {
|
|
3105
|
-
return (index: number) => {
|
|
3106
|
-
const arrayKeys = getGlobalStore
|
|
3107
|
-
.getState()
|
|
3108
|
-
.getShadowMetadata(stateKey, path)
|
|
3109
|
-
?.arrayKeys?.filter(
|
|
3110
|
-
(key) =>
|
|
3111
|
-
!meta?.validIds ||
|
|
3112
|
-
(meta?.validIds && meta?.validIds?.includes(key))
|
|
3113
|
-
);
|
|
3114
|
-
const itemId = arrayKeys?.[index];
|
|
3115
|
-
if (!itemId) return undefined;
|
|
3116
|
-
const value = getGlobalStore
|
|
3117
|
-
.getState()
|
|
3118
|
-
.getShadowValue(itemId, meta?.validIds);
|
|
3119
|
-
const state = rebuildStateShape({
|
|
3120
|
-
currentState: value,
|
|
3121
|
-
path: itemId.split('.').slice(1) as string[],
|
|
3122
|
-
componentId: componentId!,
|
|
3123
|
-
meta,
|
|
3124
|
-
});
|
|
3125
|
-
return state;
|
|
3126
|
-
};
|
|
3127
|
-
}
|
|
3128
|
-
if (prop === 'last') {
|
|
3129
|
-
return () => {
|
|
3130
|
-
const currentArray = getGlobalStore
|
|
3131
|
-
.getState()
|
|
3132
|
-
.getShadowValue(stateKey, path) as any[];
|
|
3133
|
-
if (currentArray.length === 0) return undefined;
|
|
3134
|
-
const lastIndex = currentArray.length - 1;
|
|
3135
|
-
const lastValue = currentArray[lastIndex];
|
|
3136
|
-
const newPath = [...path, lastIndex.toString()];
|
|
3137
|
-
return rebuildStateShape({
|
|
3138
|
-
currentState: lastValue,
|
|
3139
|
-
path: newPath,
|
|
3140
|
-
componentId: componentId!,
|
|
3141
|
-
meta,
|
|
3142
|
-
});
|
|
3143
|
-
};
|
|
3144
|
-
}
|
|
3145
|
-
if (prop === 'insert') {
|
|
3146
|
-
return (
|
|
3147
|
-
payload: InsertParams<InferArrayElement<T>>,
|
|
3148
|
-
index?: number
|
|
3149
|
-
) => {
|
|
3150
|
-
effectiveSetState(payload as any, path, { updateType: 'insert' });
|
|
3151
|
-
return rebuildStateShape({
|
|
3152
|
-
currentState: getGlobalStore
|
|
3153
|
-
.getState()
|
|
3154
|
-
.getShadowValue(stateKey, path),
|
|
3155
|
-
path,
|
|
3156
|
-
componentId: componentId!,
|
|
3157
|
-
meta,
|
|
3158
|
-
});
|
|
3159
|
-
};
|
|
3160
|
-
}
|
|
3161
|
-
if (prop === 'uniqueInsert') {
|
|
3162
|
-
return (
|
|
3163
|
-
payload: UpdateArg<T>,
|
|
3164
|
-
fields?: (keyof InferArrayElement<T>)[],
|
|
3165
|
-
onMatch?: (existingItem: any) => any
|
|
3166
|
-
) => {
|
|
3167
|
-
const currentArray = getGlobalStore
|
|
3168
|
-
.getState()
|
|
3169
|
-
.getShadowValue(stateKey, path) as any[];
|
|
3170
|
-
const newValue = isFunction<T>(payload)
|
|
3171
|
-
? payload(currentArray as any)
|
|
3172
|
-
: (payload as any);
|
|
3173
|
-
|
|
3174
|
-
let matchedItem: any = null;
|
|
3175
|
-
const isUnique = !currentArray.some((item) => {
|
|
3176
|
-
const isMatch = fields
|
|
3177
|
-
? fields.every((field) =>
|
|
3178
|
-
isDeepEqual(item[field], newValue[field])
|
|
3179
|
-
)
|
|
3180
|
-
: isDeepEqual(item, newValue);
|
|
3181
|
-
if (isMatch) matchedItem = item;
|
|
3182
|
-
return isMatch;
|
|
3183
|
-
});
|
|
3184
|
-
|
|
3185
|
-
if (isUnique) {
|
|
3186
|
-
invalidateCachePath(path);
|
|
3187
|
-
effectiveSetState(newValue, path, { updateType: 'insert' });
|
|
3188
|
-
} else if (onMatch && matchedItem) {
|
|
3189
|
-
const updatedItem = onMatch(matchedItem);
|
|
3190
|
-
const updatedArray = currentArray.map((item) =>
|
|
3191
|
-
isDeepEqual(item, matchedItem) ? updatedItem : item
|
|
3192
|
-
);
|
|
3193
|
-
invalidateCachePath(path);
|
|
3194
|
-
effectiveSetState(updatedArray as any, path, {
|
|
3195
|
-
updateType: 'update',
|
|
3196
|
-
});
|
|
3197
|
-
}
|
|
3198
3029
|
};
|
|
3199
|
-
}
|
|
3200
3030
|
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3031
|
+
return <StateListWrapper />;
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
if (prop === 'stateFlattenOn') {
|
|
3035
|
+
return (fieldName: string) => {
|
|
3036
|
+
const currentState = getGlobalStore
|
|
3037
|
+
.getState()
|
|
3038
|
+
.getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
|
|
3039
|
+
if (!Array.isArray(currentState)) return []; // Guard clause
|
|
3040
|
+
const arrayToMap = currentState as any[];
|
|
3209
3041
|
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3042
|
+
stateVersion++;
|
|
3043
|
+
const flattenedResults = arrayToMap.flatMap(
|
|
3044
|
+
(val: any) => val[fieldName] ?? []
|
|
3045
|
+
);
|
|
3046
|
+
return rebuildStateShape({
|
|
3047
|
+
path: [...path, '[*]', fieldName],
|
|
3048
|
+
componentId: componentId!,
|
|
3049
|
+
meta,
|
|
3050
|
+
});
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
if (prop === 'index') {
|
|
3054
|
+
return (index: number) => {
|
|
3055
|
+
const arrayKeys = getGlobalStore
|
|
3056
|
+
.getState()
|
|
3057
|
+
.getShadowMetadata(stateKey, path)
|
|
3058
|
+
?.arrayKeys?.filter(
|
|
3059
|
+
(key) =>
|
|
3060
|
+
!meta?.validIds ||
|
|
3061
|
+
(meta?.validIds && meta?.validIds?.includes(key))
|
|
3062
|
+
);
|
|
3063
|
+
const itemId = arrayKeys?.[index];
|
|
3064
|
+
if (!itemId) return undefined;
|
|
3065
|
+
const value = getGlobalStore
|
|
3066
|
+
.getState()
|
|
3067
|
+
.getShadowValue(itemId, meta?.validIds);
|
|
3068
|
+
const state = rebuildStateShape({
|
|
3069
|
+
path: itemId.split('.').slice(1) as string[],
|
|
3070
|
+
componentId: componentId!,
|
|
3071
|
+
meta,
|
|
3072
|
+
});
|
|
3073
|
+
return state;
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
if (prop === 'last') {
|
|
3077
|
+
return () => {
|
|
3078
|
+
const currentArray = getGlobalStore
|
|
3079
|
+
.getState()
|
|
3080
|
+
.getShadowValue(stateKey, path) as any[];
|
|
3081
|
+
if (currentArray.length === 0) return undefined;
|
|
3082
|
+
const lastIndex = currentArray.length - 1;
|
|
3083
|
+
const lastValue = currentArray[lastIndex];
|
|
3084
|
+
const newPath = [...path, lastIndex.toString()];
|
|
3085
|
+
return rebuildStateShape({
|
|
3086
|
+
path: newPath,
|
|
3087
|
+
componentId: componentId!,
|
|
3088
|
+
meta,
|
|
3089
|
+
});
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
if (prop === 'insert') {
|
|
3093
|
+
return (
|
|
3094
|
+
payload: InsertParams<InferArrayElement<T>>,
|
|
3095
|
+
index?: number
|
|
3096
|
+
) => {
|
|
3097
|
+
effectiveSetState(payload as any, path, { updateType: 'insert' });
|
|
3098
|
+
return rebuildStateShape({
|
|
3099
|
+
path,
|
|
3100
|
+
componentId: componentId!,
|
|
3101
|
+
meta,
|
|
3102
|
+
});
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
3105
|
+
if (prop === 'uniqueInsert') {
|
|
3106
|
+
return (
|
|
3107
|
+
payload: UpdateArg<T>,
|
|
3108
|
+
fields?: (keyof InferArrayElement<T>)[],
|
|
3109
|
+
onMatch?: (existingItem: any) => any
|
|
3110
|
+
) => {
|
|
3111
|
+
const currentArray = getGlobalStore
|
|
3112
|
+
.getState()
|
|
3113
|
+
.getShadowValue(stateKey, path) as any[];
|
|
3114
|
+
const newValue = isFunction<T>(payload)
|
|
3115
|
+
? payload(currentArray as any)
|
|
3116
|
+
: (payload as any);
|
|
3117
|
+
|
|
3118
|
+
let matchedItem: any = null;
|
|
3119
|
+
const isUnique = !currentArray.some((item) => {
|
|
3120
|
+
const isMatch = fields
|
|
3121
|
+
? fields.every((field) =>
|
|
3122
|
+
isDeepEqual(item[field], newValue[field])
|
|
3123
|
+
)
|
|
3124
|
+
: isDeepEqual(item, newValue);
|
|
3125
|
+
if (isMatch) matchedItem = item;
|
|
3126
|
+
return isMatch;
|
|
3127
|
+
});
|
|
3216
3128
|
|
|
3217
|
-
|
|
3218
|
-
|
|
3129
|
+
if (isUnique) {
|
|
3130
|
+
effectiveSetState(newValue, path, { updateType: 'insert' });
|
|
3131
|
+
} else if (onMatch && matchedItem) {
|
|
3132
|
+
const updatedItem = onMatch(matchedItem);
|
|
3133
|
+
const updatedArray = currentArray.map((item) =>
|
|
3134
|
+
isDeepEqual(item, matchedItem) ? updatedItem : item
|
|
3135
|
+
);
|
|
3219
3136
|
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
updateType: 'cut',
|
|
3137
|
+
effectiveSetState(updatedArray as any, path, {
|
|
3138
|
+
updateType: 'update',
|
|
3223
3139
|
});
|
|
3224
|
-
}
|
|
3225
|
-
}
|
|
3226
|
-
|
|
3227
|
-
return () => {
|
|
3228
|
-
const validKeys = applyTransforms(
|
|
3229
|
-
stateKey,
|
|
3230
|
-
path,
|
|
3231
|
-
meta?.transforms
|
|
3232
|
-
);
|
|
3140
|
+
}
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
3233
3143
|
|
|
3234
|
-
|
|
3144
|
+
if (prop === 'cut') {
|
|
3145
|
+
return (index?: number, options?: { waitForSync?: boolean }) => {
|
|
3146
|
+
const currentState = getGlobalStore
|
|
3147
|
+
.getState()
|
|
3148
|
+
.getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
|
|
3149
|
+
const validKeys =
|
|
3150
|
+
meta?.validIds ??
|
|
3151
|
+
getGlobalStore.getState().getShadowMetadata(stateKey, path)
|
|
3152
|
+
?.arrayKeys;
|
|
3153
|
+
|
|
3154
|
+
if (!validKeys || validKeys.length === 0) return;
|
|
3155
|
+
|
|
3156
|
+
const indexToCut =
|
|
3157
|
+
index == -1
|
|
3158
|
+
? validKeys.length - 1
|
|
3159
|
+
: index !== undefined
|
|
3160
|
+
? index
|
|
3161
|
+
: validKeys.length - 1;
|
|
3162
|
+
|
|
3163
|
+
const fullIdToCut = validKeys[indexToCut];
|
|
3164
|
+
if (!fullIdToCut) return; // Index out of bounds
|
|
3165
|
+
|
|
3166
|
+
const pathForCut = fullIdToCut.split('.').slice(1);
|
|
3167
|
+
effectiveSetState(currentState, pathForCut, {
|
|
3168
|
+
updateType: 'cut',
|
|
3169
|
+
});
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
if (prop === 'cutSelected') {
|
|
3173
|
+
return () => {
|
|
3174
|
+
const validKeys = applyTransforms(stateKey, path, meta?.transforms);
|
|
3175
|
+
const currentState = getGlobalStore
|
|
3176
|
+
.getState()
|
|
3177
|
+
.getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
|
|
3178
|
+
if (!validKeys || validKeys.length === 0) return;
|
|
3235
3179
|
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3180
|
+
const indexKeyToCut = getGlobalStore
|
|
3181
|
+
.getState()
|
|
3182
|
+
.selectedIndicesMap.get(stateKeyPathKey);
|
|
3239
3183
|
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3184
|
+
let indexToCut = validKeys.findIndex(
|
|
3185
|
+
(key) => key === indexKeyToCut
|
|
3186
|
+
);
|
|
3243
3187
|
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3188
|
+
const pathForCut = validKeys[
|
|
3189
|
+
indexToCut == -1 ? validKeys.length - 1 : indexToCut
|
|
3190
|
+
]
|
|
3191
|
+
?.split('.')
|
|
3192
|
+
.slice(1);
|
|
3193
|
+
getGlobalStore
|
|
3194
|
+
.getState()
|
|
3195
|
+
.clearSelectedIndex({ arrayKey: stateKeyPathKey });
|
|
3196
|
+
const parentPath = pathForCut?.slice(0, -1)!;
|
|
3197
|
+
notifySelectionComponents(stateKey, parentPath);
|
|
3198
|
+
effectiveSetState(currentState, pathForCut!, {
|
|
3199
|
+
updateType: 'cut',
|
|
3200
|
+
});
|
|
3201
|
+
};
|
|
3202
|
+
}
|
|
3203
|
+
if (prop === 'cutByValue') {
|
|
3204
|
+
return (value: string | number | boolean) => {
|
|
3205
|
+
// Step 1: Get the list of all unique keys for the current view.
|
|
3206
|
+
const arrayMeta = getGlobalStore
|
|
3207
|
+
.getState()
|
|
3208
|
+
.getShadowMetadata(stateKey, path);
|
|
3209
|
+
const relevantKeys = meta?.validIds ?? arrayMeta?.arrayKeys;
|
|
3266
3210
|
|
|
3267
|
-
|
|
3211
|
+
if (!relevantKeys) return;
|
|
3268
3212
|
|
|
3269
|
-
|
|
3213
|
+
let keyToCut: string | null = null;
|
|
3270
3214
|
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
}
|
|
3215
|
+
// Step 2: Iterate through the KEYS, get the value for each, and find the match.
|
|
3216
|
+
for (const key of relevantKeys) {
|
|
3217
|
+
const itemValue = getGlobalStore.getState().getShadowValue(key);
|
|
3218
|
+
if (itemValue === value) {
|
|
3219
|
+
keyToCut = key;
|
|
3220
|
+
break; // We found the key, no need to search further.
|
|
3278
3221
|
}
|
|
3222
|
+
}
|
|
3279
3223
|
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3224
|
+
// Step 3: If we found a matching key, use it to perform the cut.
|
|
3225
|
+
if (keyToCut) {
|
|
3226
|
+
const itemPath = keyToCut.split('.').slice(1);
|
|
3227
|
+
effectiveSetState(null as any, itemPath, { updateType: 'cut' });
|
|
3228
|
+
}
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3287
3231
|
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3232
|
+
if (prop === 'toggleByValue') {
|
|
3233
|
+
return (value: string | number | boolean) => {
|
|
3234
|
+
// Step 1: Get the list of all unique keys for the current view.
|
|
3235
|
+
const arrayMeta = getGlobalStore
|
|
3236
|
+
.getState()
|
|
3237
|
+
.getShadowMetadata(stateKey, path);
|
|
3238
|
+
const relevantKeys = meta?.validIds ?? arrayMeta?.arrayKeys;
|
|
3295
3239
|
|
|
3296
|
-
|
|
3240
|
+
if (!relevantKeys) return;
|
|
3297
3241
|
|
|
3298
|
-
|
|
3242
|
+
let keyToCut: string | null = null;
|
|
3299
3243
|
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
}
|
|
3244
|
+
// Step 2: Iterate through the KEYS to find the one matching the value. This is the robust way.
|
|
3245
|
+
for (const key of relevantKeys) {
|
|
3246
|
+
const itemValue = getGlobalStore.getState().getShadowValue(key);
|
|
3247
|
+
console.log('itemValue sdasdasdasd', itemValue);
|
|
3248
|
+
if (itemValue === value) {
|
|
3249
|
+
keyToCut = key;
|
|
3250
|
+
break; // Found it!
|
|
3308
3251
|
}
|
|
3252
|
+
}
|
|
3253
|
+
console.log('itemValue keyToCut', keyToCut);
|
|
3254
|
+
// Step 3: Act based on whether the key was found.
|
|
3255
|
+
if (keyToCut) {
|
|
3256
|
+
// Item exists, so we CUT it using its *actual* key.
|
|
3257
|
+
const itemPath = keyToCut.split('.').slice(1);
|
|
3309
3258
|
console.log('itemValue keyToCut', keyToCut);
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
if (prop === 'findWith') {
|
|
3325
|
-
return (
|
|
3326
|
-
searchKey: keyof InferArrayElement<T>,
|
|
3327
|
-
searchValue: any
|
|
3328
|
-
) => {
|
|
3329
|
-
const arrayKeys = getGlobalStore
|
|
3330
|
-
.getState()
|
|
3331
|
-
.getShadowMetadata(stateKey, path)?.arrayKeys;
|
|
3259
|
+
effectiveSetState(value as any, itemPath, {
|
|
3260
|
+
updateType: 'cut',
|
|
3261
|
+
});
|
|
3262
|
+
} else {
|
|
3263
|
+
// Item does not exist, so we INSERT it.
|
|
3264
|
+
effectiveSetState(value as any, path, { updateType: 'insert' });
|
|
3265
|
+
}
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
if (prop === 'findWith') {
|
|
3269
|
+
return (searchKey: keyof InferArrayElement<T>, searchValue: any) => {
|
|
3270
|
+
const arrayKeys = getGlobalStore
|
|
3271
|
+
.getState()
|
|
3272
|
+
.getShadowMetadata(stateKey, path)?.arrayKeys;
|
|
3332
3273
|
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3274
|
+
if (!arrayKeys) {
|
|
3275
|
+
throw new Error('No array keys found for sorting');
|
|
3276
|
+
}
|
|
3336
3277
|
|
|
3337
|
-
|
|
3338
|
-
|
|
3278
|
+
let value = null;
|
|
3279
|
+
let foundPath: string[] = [];
|
|
3339
3280
|
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
}
|
|
3281
|
+
for (const fullPath of arrayKeys) {
|
|
3282
|
+
let shadowValue = getGlobalStore
|
|
3283
|
+
.getState()
|
|
3284
|
+
.getShadowValue(fullPath, meta?.validIds);
|
|
3285
|
+
if (shadowValue && shadowValue[searchKey] === searchValue) {
|
|
3286
|
+
value = shadowValue;
|
|
3287
|
+
foundPath = fullPath.split('.').slice(1);
|
|
3288
|
+
break;
|
|
3349
3289
|
}
|
|
3290
|
+
}
|
|
3350
3291
|
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
};
|
|
3358
|
-
}
|
|
3292
|
+
return rebuildStateShape({
|
|
3293
|
+
path: foundPath,
|
|
3294
|
+
componentId: componentId!,
|
|
3295
|
+
meta,
|
|
3296
|
+
});
|
|
3297
|
+
};
|
|
3359
3298
|
}
|
|
3360
3299
|
|
|
3361
|
-
if (prop === '
|
|
3300
|
+
if (prop === 'cutThis') {
|
|
3362
3301
|
let shadowValue = getGlobalStore
|
|
3363
3302
|
.getState()
|
|
3364
3303
|
.getShadowValue(path.join('.'));
|
|
@@ -3483,7 +3422,6 @@ function createProxyHandler<T>(
|
|
|
3483
3422
|
const init = getGlobalStore
|
|
3484
3423
|
.getState()
|
|
3485
3424
|
.getInitialOptions(stateKey)?.validation;
|
|
3486
|
-
if (!init?.key) throw new Error('Validation key not found');
|
|
3487
3425
|
|
|
3488
3426
|
// For each error, set shadow metadata
|
|
3489
3427
|
zodErrors.forEach((error) => {
|
|
@@ -3697,7 +3635,9 @@ function createProxyHandler<T>(
|
|
|
3697
3635
|
const currentValueAtPath = getGlobalStore
|
|
3698
3636
|
.getState()
|
|
3699
3637
|
.getShadowValue([stateKey, ...path].join('.'));
|
|
3700
|
-
|
|
3638
|
+
const currentState = getGlobalStore
|
|
3639
|
+
.getState()
|
|
3640
|
+
.getShadowValue([stateKey, ...path].join('.'), meta?.validIds);
|
|
3701
3641
|
console.log('currentValueAtPath', currentValueAtPath);
|
|
3702
3642
|
if (typeof currentState != 'boolean') {
|
|
3703
3643
|
throw new Error('toggle() can only be used on boolean values');
|
|
@@ -3727,7 +3667,6 @@ function createProxyHandler<T>(
|
|
|
3727
3667
|
.getState()
|
|
3728
3668
|
.getShadowValue(stateKey, nextPath);
|
|
3729
3669
|
return rebuildStateShape({
|
|
3730
|
-
currentState: nextValue,
|
|
3731
3670
|
path: nextPath,
|
|
3732
3671
|
componentId: componentId!,
|
|
3733
3672
|
meta,
|
|
@@ -3736,10 +3675,7 @@ function createProxyHandler<T>(
|
|
|
3736
3675
|
};
|
|
3737
3676
|
|
|
3738
3677
|
const proxyInstance = new Proxy(baseFunction, handler);
|
|
3739
|
-
|
|
3740
|
-
proxy: proxyInstance,
|
|
3741
|
-
stateVersion: stateVersion,
|
|
3742
|
-
});
|
|
3678
|
+
proxyCache.set(cacheKey, proxyInstance);
|
|
3743
3679
|
return proxyInstance;
|
|
3744
3680
|
}
|
|
3745
3681
|
|
|
@@ -3765,11 +3701,10 @@ function createProxyHandler<T>(
|
|
|
3765
3701
|
getGlobalStore.getState().initialStateGlobal[stateKey];
|
|
3766
3702
|
|
|
3767
3703
|
getGlobalStore.getState().clearSelectedIndexesForState(stateKey);
|
|
3768
|
-
|
|
3704
|
+
|
|
3769
3705
|
stateVersion++;
|
|
3770
3706
|
getGlobalStore.getState().initializeShadowState(stateKey, initialState);
|
|
3771
|
-
|
|
3772
|
-
currentState: initialState,
|
|
3707
|
+
rebuildStateShape({
|
|
3773
3708
|
path: [],
|
|
3774
3709
|
componentId: componentId!,
|
|
3775
3710
|
});
|
|
@@ -3796,7 +3731,6 @@ function createProxyHandler<T>(
|
|
|
3796
3731
|
return initialState;
|
|
3797
3732
|
},
|
|
3798
3733
|
updateInitialState: (newState: T) => {
|
|
3799
|
-
shapeCache.clear();
|
|
3800
3734
|
stateVersion++;
|
|
3801
3735
|
|
|
3802
3736
|
const newUpdaterState = createProxyHandler(
|
|
@@ -3838,7 +3772,6 @@ function createProxyHandler<T>(
|
|
|
3838
3772
|
},
|
|
3839
3773
|
};
|
|
3840
3774
|
const returnShape = rebuildStateShape({
|
|
3841
|
-
currentState: getGlobalStore.getState().getShadowValue(stateKey, []),
|
|
3842
3775
|
componentId,
|
|
3843
3776
|
path: [],
|
|
3844
3777
|
});
|