cogsbox-state 0.5.382 → 0.5.383
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 +372 -370
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +53 -29
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1828,8 +1828,14 @@ function createProxyHandler<T>(
|
|
|
1828
1828
|
const prevTotalCountRef = useRef(0);
|
|
1829
1829
|
const prevDepsRef = useRef(dependencies);
|
|
1830
1830
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1831
|
-
|
|
1832
|
-
//
|
|
1831
|
+
// CHANGE: Add a ref to store the scroll position BEFORE new items are added.
|
|
1832
|
+
// This is the key to preventing the jump when scrolled up.
|
|
1833
|
+
const scrollAnchorRef = useRef<{
|
|
1834
|
+
scrollTop: number;
|
|
1835
|
+
scrollHeight: number;
|
|
1836
|
+
} | null>(null);
|
|
1837
|
+
|
|
1838
|
+
// ... (Your existing useEffect for shadow state and useMemo for data are fine) ...
|
|
1833
1839
|
useEffect(() => {
|
|
1834
1840
|
const unsubscribe = getGlobalStore
|
|
1835
1841
|
.getState()
|
|
@@ -1845,7 +1851,6 @@ function createProxyHandler<T>(
|
|
|
1845
1851
|
) as any[];
|
|
1846
1852
|
const totalCount = sourceArray.length;
|
|
1847
1853
|
|
|
1848
|
-
// Memoize positions and total height based on measured items
|
|
1849
1854
|
const { totalHeight, positions } = useMemo(() => {
|
|
1850
1855
|
const shadowArray =
|
|
1851
1856
|
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
@@ -1867,7 +1872,6 @@ function createProxyHandler<T>(
|
|
|
1867
1872
|
shadowUpdateTrigger,
|
|
1868
1873
|
]);
|
|
1869
1874
|
|
|
1870
|
-
// Memoize the virtualized data slice
|
|
1871
1875
|
const virtualState = useMemo(() => {
|
|
1872
1876
|
const start = Math.max(0, range.startIndex);
|
|
1873
1877
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1882,23 +1886,28 @@ function createProxyHandler<T>(
|
|
|
1882
1886
|
});
|
|
1883
1887
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1884
1888
|
|
|
1885
|
-
// --- 1. STATE CONTROLLER ---
|
|
1886
|
-
// Decides which state to transition TO based on data changes.
|
|
1889
|
+
// --- 1. STATE CONTROLLER (REVISED LOGIC) ---
|
|
1887
1890
|
useLayoutEffect(() => {
|
|
1891
|
+
const container = containerRef.current;
|
|
1892
|
+
if (!container) return;
|
|
1893
|
+
|
|
1894
|
+
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1888
1895
|
const depsChanged = !isDeepEqual(
|
|
1889
1896
|
dependencies,
|
|
1890
1897
|
prevDepsRef.current
|
|
1891
1898
|
);
|
|
1892
|
-
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1893
1899
|
|
|
1900
|
+
// Condition 1: Hard Reset.
|
|
1901
|
+
// This happens when you load a completely new list (e.g., switch chats).
|
|
1894
1902
|
if (depsChanged) {
|
|
1895
|
-
console.log(
|
|
1903
|
+
console.log(
|
|
1904
|
+
"TRANSITION (Hard Reset): Deps changed -> IDLE_AT_TOP"
|
|
1905
|
+
);
|
|
1896
1906
|
setStatus("IDLE_AT_TOP");
|
|
1897
|
-
|
|
1907
|
+
container.scrollTo({ top: 0 }); // Reset scroll position
|
|
1898
1908
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
if (
|
|
1909
|
+
// Condition 2: New items arrive while we're locked to the bottom.
|
|
1910
|
+
else if (
|
|
1902
1911
|
hasNewItems &&
|
|
1903
1912
|
status === "LOCKED_AT_BOTTOM" &&
|
|
1904
1913
|
stickToBottom
|
|
@@ -1908,14 +1917,29 @@ function createProxyHandler<T>(
|
|
|
1908
1917
|
);
|
|
1909
1918
|
setStatus("GETTING_HEIGHTS");
|
|
1910
1919
|
}
|
|
1920
|
+
// CHANGE: Condition 3: New items arrive while we are scrolled up.
|
|
1921
|
+
// This is the "scroll anchoring" logic that prevents the jump.
|
|
1922
|
+
else if (hasNewItems && scrollAnchorRef.current) {
|
|
1923
|
+
console.log(
|
|
1924
|
+
"ACTION: Maintaining scroll position after new items added."
|
|
1925
|
+
);
|
|
1926
|
+
// We adjust the scroll position by the amount of height that was just added.
|
|
1927
|
+
container.scrollTop =
|
|
1928
|
+
scrollAnchorRef.current.scrollTop +
|
|
1929
|
+
(container.scrollHeight -
|
|
1930
|
+
scrollAnchorRef.current.scrollHeight);
|
|
1931
|
+
}
|
|
1911
1932
|
|
|
1933
|
+
// Finally, update the refs for the next render.
|
|
1912
1934
|
prevTotalCountRef.current = totalCount;
|
|
1913
1935
|
prevDepsRef.current = dependencies;
|
|
1914
|
-
|
|
1936
|
+
// Clear the anchor after using it. It will be re-set by the user scroll handler.
|
|
1937
|
+
scrollAnchorRef.current = null;
|
|
1938
|
+
}, [totalCount, ...dependencies]); // This dependency array is correct.
|
|
1915
1939
|
|
|
1916
|
-
// --- 2. STATE ACTION HANDLER ---
|
|
1917
|
-
// Performs the ACTION for the current state (e.g., scrolling).
|
|
1940
|
+
// --- 2. STATE ACTION HANDLER (Mostly Unchanged) ---
|
|
1918
1941
|
useLayoutEffect(() => {
|
|
1942
|
+
// ... (This effect's logic for GETTING_HEIGHTS and SCROLLING_TO_BOTTOM is correct and can remain the same)
|
|
1919
1943
|
const container = containerRef.current;
|
|
1920
1944
|
if (!container) return;
|
|
1921
1945
|
|
|
@@ -1982,11 +2006,14 @@ function createProxyHandler<T>(
|
|
|
1982
2006
|
};
|
|
1983
2007
|
}, [status, totalCount, positions]);
|
|
1984
2008
|
|
|
1985
|
-
// --- 3. USER INTERACTION & RANGE UPDATER (
|
|
2009
|
+
// --- 3. USER INTERACTION & RANGE UPDATER (REVISED) ---
|
|
1986
2010
|
useEffect(() => {
|
|
1987
2011
|
const container = containerRef.current;
|
|
1988
2012
|
if (!container) return;
|
|
1989
2013
|
|
|
2014
|
+
// CHANGE: Add a buffer to prevent jittering at the bottom.
|
|
2015
|
+
const bottomLockThreshold = 10; // 10px buffer
|
|
2016
|
+
|
|
1990
2017
|
const handleUserScroll = () => {
|
|
1991
2018
|
if (isProgrammaticScroll.current) {
|
|
1992
2019
|
return;
|
|
@@ -1994,10 +2021,14 @@ function createProxyHandler<T>(
|
|
|
1994
2021
|
|
|
1995
2022
|
const { scrollTop, clientHeight, scrollHeight } = container;
|
|
1996
2023
|
|
|
1997
|
-
//
|
|
1998
|
-
// This
|
|
2024
|
+
// CHANGE: Before the next state update, save the current scroll positions.
|
|
2025
|
+
// This 'anchors' our view, so the State Controller knows where we were.
|
|
2026
|
+
scrollAnchorRef.current = { scrollTop, scrollHeight };
|
|
2027
|
+
|
|
2028
|
+
// Part 1: Update the state machine with a tolerance
|
|
1999
2029
|
const isAtBottom =
|
|
2000
|
-
scrollHeight - scrollTop - clientHeight <
|
|
2030
|
+
scrollHeight - scrollTop - clientHeight <
|
|
2031
|
+
bottomLockThreshold;
|
|
2001
2032
|
|
|
2002
2033
|
if (isAtBottom) {
|
|
2003
2034
|
if (status !== "LOCKED_AT_BOTTOM") {
|
|
@@ -2015,9 +2046,7 @@ function createProxyHandler<T>(
|
|
|
2015
2046
|
}
|
|
2016
2047
|
}
|
|
2017
2048
|
|
|
2018
|
-
// Part 2:
|
|
2019
|
-
// This is the superior optimization from your first version. It only
|
|
2020
|
-
// re-renders if the actual items in view have changed.
|
|
2049
|
+
// Part 2: Efficiently update the rendered range (this logic is good)
|
|
2021
2050
|
let high = totalCount - 1;
|
|
2022
2051
|
let low = 0;
|
|
2023
2052
|
let potentialTopIndex = 0;
|
|
@@ -2036,12 +2065,10 @@ function createProxyHandler<T>(
|
|
|
2036
2065
|
potentialTopIndex - overscan
|
|
2037
2066
|
);
|
|
2038
2067
|
|
|
2039
|
-
// Only update state if the visible start index has actually changed.
|
|
2040
2068
|
if (potentialStartIndex === range.startIndex) {
|
|
2041
2069
|
return;
|
|
2042
2070
|
}
|
|
2043
2071
|
|
|
2044
|
-
// If we must update, calculate the new end index.
|
|
2045
2072
|
let endIndex = potentialStartIndex;
|
|
2046
2073
|
const visibleEnd = scrollTop + clientHeight;
|
|
2047
2074
|
while (
|
|
@@ -2065,14 +2092,13 @@ function createProxyHandler<T>(
|
|
|
2065
2092
|
});
|
|
2066
2093
|
return () =>
|
|
2067
2094
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2068
|
-
}, [totalCount, positions, status, range.startIndex]);
|
|
2095
|
+
}, [totalCount, positions, status, range.startIndex]);
|
|
2069
2096
|
|
|
2070
|
-
// ---
|
|
2097
|
+
// --- (The rest of your code: scrollToBottom, scrollToIndex, virtualizerProps is fine) ---
|
|
2071
2098
|
const scrollToBottom = useCallback(() => {
|
|
2072
2099
|
console.log(
|
|
2073
2100
|
"USER ACTION: Clicked scroll button -> SCROLLING_TO_BOTTOM"
|
|
2074
2101
|
);
|
|
2075
|
-
// Don't scroll if there's nothing to scroll to.
|
|
2076
2102
|
if (totalCount === 0) return;
|
|
2077
2103
|
setStatus("SCROLLING_TO_BOTTOM");
|
|
2078
2104
|
}, [totalCount]);
|
|
@@ -2080,7 +2106,6 @@ function createProxyHandler<T>(
|
|
|
2080
2106
|
const scrollToIndex = useCallback(
|
|
2081
2107
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2082
2108
|
if (containerRef.current && positions[index] !== undefined) {
|
|
2083
|
-
// Manually scrolling to an index means we are no longer at the bottom.
|
|
2084
2109
|
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2085
2110
|
containerRef.current.scrollTo({
|
|
2086
2111
|
top: positions[index],
|
|
@@ -2091,7 +2116,6 @@ function createProxyHandler<T>(
|
|
|
2091
2116
|
[positions]
|
|
2092
2117
|
);
|
|
2093
2118
|
|
|
2094
|
-
// --- 5. RENDER PROPS ---
|
|
2095
2119
|
const virtualizerProps = {
|
|
2096
2120
|
outer: {
|
|
2097
2121
|
ref: containerRef,
|