cogsbox-state 0.5.329 → 0.5.331
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.jsx +696 -720
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +65 -133
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1813,15 +1813,9 @@ function createProxyHandler<T>(
|
|
|
1813
1813
|
startIndex: 0,
|
|
1814
1814
|
endIndex: 10,
|
|
1815
1815
|
});
|
|
1816
|
-
|
|
1817
|
-
// This ref tracks if the user is locked to the bottom.
|
|
1818
|
-
const isLockedToBottomRef = useRef(stickToBottom);
|
|
1819
|
-
|
|
1820
|
-
// This state triggers a re-render when item heights change.
|
|
1821
1816
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1822
|
-
const hasInitiallyLoadedRef = useRef(false);
|
|
1823
|
-
const prevTotalCountRef = useRef(0);
|
|
1824
1817
|
|
|
1818
|
+
// Subscribe to shadow updates
|
|
1825
1819
|
useEffect(() => {
|
|
1826
1820
|
const unsubscribe = getGlobalStore
|
|
1827
1821
|
.getState()
|
|
@@ -1837,20 +1831,18 @@ function createProxyHandler<T>(
|
|
|
1837
1831
|
) as any[];
|
|
1838
1832
|
const totalCount = sourceArray.length;
|
|
1839
1833
|
|
|
1840
|
-
// Calculate
|
|
1841
|
-
const
|
|
1834
|
+
// Calculate total height
|
|
1835
|
+
const totalHeight = useMemo(() => {
|
|
1842
1836
|
const shadowArray =
|
|
1843
1837
|
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
1844
1838
|
[];
|
|
1845
1839
|
let height = 0;
|
|
1846
|
-
const pos: number[] = [];
|
|
1847
1840
|
for (let i = 0; i < totalCount; i++) {
|
|
1848
|
-
pos[i] = height;
|
|
1849
1841
|
const measuredHeight =
|
|
1850
1842
|
shadowArray[i]?.virtualizer?.itemHeight;
|
|
1851
1843
|
height += measuredHeight || itemHeight;
|
|
1852
1844
|
}
|
|
1853
|
-
return
|
|
1845
|
+
return height;
|
|
1854
1846
|
}, [
|
|
1855
1847
|
totalCount,
|
|
1856
1848
|
stateKey,
|
|
@@ -1859,7 +1851,7 @@ function createProxyHandler<T>(
|
|
|
1859
1851
|
shadowUpdateTrigger,
|
|
1860
1852
|
]);
|
|
1861
1853
|
|
|
1862
|
-
//
|
|
1854
|
+
// Create virtual slice
|
|
1863
1855
|
const virtualState = useMemo(() => {
|
|
1864
1856
|
const start = Math.max(0, range.startIndex);
|
|
1865
1857
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1873,135 +1865,47 @@ function createProxyHandler<T>(
|
|
|
1873
1865
|
validIndices,
|
|
1874
1866
|
});
|
|
1875
1867
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1876
|
-
useEffect(() => {
|
|
1877
|
-
if (stickToBottom && totalCount > 0 && containerRef.current) {
|
|
1878
|
-
// When count increases, immediately adjust range to show bottom
|
|
1879
|
-
const container = containerRef.current;
|
|
1880
|
-
const visibleCount = Math.ceil(
|
|
1881
|
-
container.clientHeight / itemHeight
|
|
1882
|
-
);
|
|
1883
|
-
|
|
1884
|
-
// Set range to show the last items including the new one
|
|
1885
|
-
setRange({
|
|
1886
|
-
startIndex: Math.max(
|
|
1887
|
-
0,
|
|
1888
|
-
totalCount - visibleCount - overscan
|
|
1889
|
-
),
|
|
1890
|
-
endIndex: totalCount,
|
|
1891
|
-
});
|
|
1892
1868
|
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
container.scrollTop = container.scrollHeight;
|
|
1896
|
-
}, 100);
|
|
1897
|
-
}
|
|
1898
|
-
}, [totalCount]);
|
|
1899
|
-
// This is the main effect that handles all scrolling and updates.
|
|
1900
|
-
useLayoutEffect(() => {
|
|
1869
|
+
// Handle scroll
|
|
1870
|
+
useEffect(() => {
|
|
1901
1871
|
const container = containerRef.current;
|
|
1902
1872
|
if (!container) return;
|
|
1903
1873
|
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
if (!container) return;
|
|
1909
|
-
const { scrollTop } = container;
|
|
1910
|
-
let low = 0,
|
|
1911
|
-
high = totalCount - 1;
|
|
1912
|
-
while (low <= high) {
|
|
1913
|
-
const mid = Math.floor((low + high) / 2);
|
|
1914
|
-
if (positions[mid]! < scrollTop) low = mid + 1;
|
|
1915
|
-
else high = mid - 1;
|
|
1916
|
-
}
|
|
1917
|
-
const startIndex = Math.max(0, high - overscan);
|
|
1918
|
-
let endIndex = startIndex;
|
|
1919
|
-
const visibleEnd = scrollTop + container.clientHeight;
|
|
1920
|
-
while (
|
|
1921
|
-
endIndex < totalCount &&
|
|
1922
|
-
positions[endIndex]! < visibleEnd
|
|
1923
|
-
) {
|
|
1924
|
-
endIndex++;
|
|
1925
|
-
}
|
|
1926
|
-
endIndex = Math.min(totalCount, endIndex + overscan);
|
|
1927
|
-
setRange({ startIndex, endIndex });
|
|
1928
|
-
};
|
|
1874
|
+
const handleScroll = () => {
|
|
1875
|
+
const { scrollTop, clientHeight } = container;
|
|
1876
|
+
const startIndex = Math.floor(scrollTop / itemHeight);
|
|
1877
|
+
const visibleCount = Math.ceil(clientHeight / itemHeight);
|
|
1929
1878
|
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
updateVirtualRange();
|
|
1879
|
+
setRange({
|
|
1880
|
+
startIndex: Math.max(0, startIndex - overscan),
|
|
1881
|
+
endIndex: Math.min(
|
|
1882
|
+
totalCount,
|
|
1883
|
+
startIndex + visibleCount + overscan
|
|
1884
|
+
),
|
|
1885
|
+
});
|
|
1938
1886
|
};
|
|
1939
1887
|
|
|
1940
|
-
container.addEventListener("scroll",
|
|
1941
|
-
|
|
1942
|
-
});
|
|
1888
|
+
container.addEventListener("scroll", handleScroll);
|
|
1889
|
+
handleScroll(); // Initial calculation
|
|
1943
1890
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
const isInitialLoad =
|
|
1948
|
-
!hasInitiallyLoadedRef.current && totalCount > 0;
|
|
1949
|
-
const isNewItem =
|
|
1950
|
-
hasInitiallyLoadedRef.current &&
|
|
1951
|
-
totalCount > prevTotalCountRef.current;
|
|
1952
|
-
|
|
1953
|
-
scrollTimeoutId = setTimeout(() => {
|
|
1954
|
-
console.log("totalHeight", totalHeight);
|
|
1955
|
-
if (isLockedToBottomRef.current) {
|
|
1956
|
-
container.scrollTo({
|
|
1957
|
-
top: 999999999,
|
|
1958
|
-
behavior: isNewItem ? "smooth" : "auto", // Only smooth for NEW items after initial load
|
|
1959
|
-
});
|
|
1960
|
-
}
|
|
1961
|
-
}, 200);
|
|
1891
|
+
return () =>
|
|
1892
|
+
container.removeEventListener("scroll", handleScroll);
|
|
1893
|
+
}, [totalCount, itemHeight, overscan]);
|
|
1962
1894
|
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1895
|
+
const scrollToBottom = useCallback(() => {
|
|
1896
|
+
if (containerRef.current) {
|
|
1897
|
+
containerRef.current.scrollTop =
|
|
1898
|
+
containerRef.current.scrollHeight;
|
|
1967
1899
|
}
|
|
1968
|
-
|
|
1969
|
-
// Update ref at the end
|
|
1970
|
-
prevTotalCountRef.current = totalCount;
|
|
1971
|
-
updateVirtualRange();
|
|
1972
|
-
|
|
1973
|
-
// Cleanup function is vital to prevent memory leaks.
|
|
1974
|
-
return () => {
|
|
1975
|
-
clearTimeout(scrollTimeoutId);
|
|
1976
|
-
container.removeEventListener("scroll", handleUserScroll);
|
|
1977
|
-
};
|
|
1978
|
-
// This effect re-runs whenever the list size or item heights change.
|
|
1979
|
-
}, [totalCount, positions, totalHeight, stickToBottom]);
|
|
1980
|
-
|
|
1981
|
-
const scrollToBottom = useCallback(
|
|
1982
|
-
(behavior: ScrollBehavior = "smooth") => {
|
|
1983
|
-
if (containerRef.current) {
|
|
1984
|
-
isLockedToBottomRef.current = true;
|
|
1985
|
-
containerRef.current.scrollTo({
|
|
1986
|
-
top: containerRef.current.scrollHeight,
|
|
1987
|
-
behavior,
|
|
1988
|
-
});
|
|
1989
|
-
}
|
|
1990
|
-
},
|
|
1991
|
-
[]
|
|
1992
|
-
);
|
|
1900
|
+
}, []);
|
|
1993
1901
|
|
|
1994
1902
|
const scrollToIndex = useCallback(
|
|
1995
|
-
(index: number
|
|
1996
|
-
if (containerRef.current
|
|
1997
|
-
|
|
1998
|
-
containerRef.current.scrollTo({
|
|
1999
|
-
top: positions[index],
|
|
2000
|
-
behavior,
|
|
2001
|
-
});
|
|
1903
|
+
(index: number) => {
|
|
1904
|
+
if (containerRef.current) {
|
|
1905
|
+
containerRef.current.scrollTop = index * itemHeight;
|
|
2002
1906
|
}
|
|
2003
1907
|
},
|
|
2004
|
-
[
|
|
1908
|
+
[itemHeight]
|
|
2005
1909
|
);
|
|
2006
1910
|
|
|
2007
1911
|
const virtualizerProps = {
|
|
@@ -2017,7 +1921,10 @@ function createProxyHandler<T>(
|
|
|
2017
1921
|
},
|
|
2018
1922
|
list: {
|
|
2019
1923
|
style: {
|
|
2020
|
-
|
|
1924
|
+
position: "absolute" as const,
|
|
1925
|
+
top: `${range.startIndex * itemHeight}px`,
|
|
1926
|
+
left: 0,
|
|
1927
|
+
right: 0,
|
|
2021
1928
|
},
|
|
2022
1929
|
},
|
|
2023
1930
|
};
|
|
@@ -2229,21 +2136,24 @@ function createProxyHandler<T>(
|
|
|
2229
2136
|
return null;
|
|
2230
2137
|
}
|
|
2231
2138
|
|
|
2139
|
+
const arrayLength = arrayToMap.length;
|
|
2232
2140
|
const indicesToMap =
|
|
2233
2141
|
meta?.validIndices ||
|
|
2234
|
-
Array.from({ length:
|
|
2142
|
+
Array.from({ length: arrayLength }, (_, i) => i);
|
|
2235
2143
|
|
|
2236
2144
|
return indicesToMap.map((originalIndex, localIndex) => {
|
|
2237
2145
|
const item = arrayToMap[originalIndex];
|
|
2238
2146
|
const finalPath = [...path, originalIndex.toString()];
|
|
2239
2147
|
const setter = rebuildStateShape(item, finalPath, meta);
|
|
2240
2148
|
const itemComponentId = `${componentId}-${path.join(".")}-${originalIndex}`;
|
|
2149
|
+
const isLastItem = originalIndex === arrayLength - 1;
|
|
2241
2150
|
|
|
2242
2151
|
return createElement(CogsItemWrapper, {
|
|
2243
2152
|
key: originalIndex,
|
|
2244
2153
|
stateKey,
|
|
2245
2154
|
itemComponentId,
|
|
2246
2155
|
itemPath: finalPath,
|
|
2156
|
+
isLastItem, // Pass it here!
|
|
2247
2157
|
children: callbackfn(
|
|
2248
2158
|
item,
|
|
2249
2159
|
setter,
|
|
@@ -2973,21 +2883,25 @@ export function $cogsSignalStore(proxy: {
|
|
|
2973
2883
|
);
|
|
2974
2884
|
return createElement("text", {}, String(value));
|
|
2975
2885
|
}
|
|
2886
|
+
|
|
2976
2887
|
function CogsItemWrapper({
|
|
2977
2888
|
stateKey,
|
|
2978
2889
|
itemComponentId,
|
|
2979
2890
|
itemPath,
|
|
2891
|
+
isLastItem,
|
|
2980
2892
|
children,
|
|
2981
2893
|
}: {
|
|
2982
2894
|
stateKey: string;
|
|
2983
2895
|
itemComponentId: string;
|
|
2984
2896
|
itemPath: string[];
|
|
2897
|
+
isLastItem: boolean;
|
|
2985
2898
|
children: React.ReactNode;
|
|
2986
2899
|
}) {
|
|
2987
2900
|
// This hook handles the re-rendering when the item's own data changes.
|
|
2988
2901
|
const [, forceUpdate] = useState({});
|
|
2989
2902
|
// This hook measures the element.
|
|
2990
|
-
const [
|
|
2903
|
+
const [measureRef, bounds] = useMeasure();
|
|
2904
|
+
const scrollRef = useRef<HTMLDivElement | null>(null);
|
|
2991
2905
|
// This ref prevents sending the same height update repeatedly.
|
|
2992
2906
|
const lastReportedHeight = useRef<number | null>(null);
|
|
2993
2907
|
|
|
@@ -3033,7 +2947,25 @@ function CogsItemWrapper({
|
|
|
3033
2947
|
}
|
|
3034
2948
|
};
|
|
3035
2949
|
}, [stateKey, itemComponentId, itemPath.join(".")]);
|
|
3036
|
-
|
|
2950
|
+
useEffect(() => {
|
|
2951
|
+
if (isLastItem && scrollRef.current) {
|
|
2952
|
+
setTimeout(() => {
|
|
2953
|
+
scrollRef.current?.scrollIntoView({
|
|
2954
|
+
behavior: "smooth",
|
|
2955
|
+
block: "end",
|
|
2956
|
+
});
|
|
2957
|
+
}, 50);
|
|
2958
|
+
}
|
|
2959
|
+
}, [isLastItem]);
|
|
3037
2960
|
// The rendered output is a simple div that gets measured.
|
|
3038
|
-
return
|
|
2961
|
+
return (
|
|
2962
|
+
<div
|
|
2963
|
+
ref={(el) => {
|
|
2964
|
+
measureRef(el);
|
|
2965
|
+
scrollRef.current = el;
|
|
2966
|
+
}}
|
|
2967
|
+
>
|
|
2968
|
+
{children}
|
|
2969
|
+
</div>
|
|
2970
|
+
);
|
|
3039
2971
|
}
|