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