cogsbox-state 0.5.364 → 0.5.366
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 +601 -589
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +121 -95
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1809,13 +1809,19 @@ function createProxyHandler<T>(
|
|
|
1809
1809
|
dependencies = [],
|
|
1810
1810
|
} = options;
|
|
1811
1811
|
|
|
1812
|
+
type Status =
|
|
1813
|
+
| "WAITING_FOR_ARRAY"
|
|
1814
|
+
| "GETTING_ARRAY_HEIGHTS"
|
|
1815
|
+
| "MOVING_TO_BOTTOM"
|
|
1816
|
+
| "LOCKED_AT_BOTTOM"
|
|
1817
|
+
| "IDLE_NOT_AT_BOTTOM";
|
|
1818
|
+
|
|
1812
1819
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
1813
1820
|
const [range, setRange] = useState({
|
|
1814
1821
|
startIndex: 0,
|
|
1815
1822
|
endIndex: 10,
|
|
1816
1823
|
});
|
|
1817
|
-
const
|
|
1818
|
-
const isAutoScrolling = useRef(false);
|
|
1824
|
+
const [status, setStatus] = useState<Status>("WAITING_FOR_ARRAY");
|
|
1819
1825
|
const prevTotalCountRef = useRef(0);
|
|
1820
1826
|
const prevDepsRef = useRef(dependencies);
|
|
1821
1827
|
|
|
@@ -1871,103 +1877,141 @@ function createProxyHandler<T>(
|
|
|
1871
1877
|
});
|
|
1872
1878
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1873
1879
|
|
|
1874
|
-
// ---
|
|
1875
|
-
// This effect
|
|
1880
|
+
// --- STATE MACHINE CONTROLLER ---
|
|
1881
|
+
// This effect decides which state to transition to based on external changes.
|
|
1876
1882
|
useLayoutEffect(() => {
|
|
1877
|
-
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1878
1883
|
const depsChanged = !isDeepEqual(
|
|
1879
1884
|
dependencies,
|
|
1880
1885
|
prevDepsRef.current
|
|
1881
1886
|
);
|
|
1887
|
+
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1882
1888
|
|
|
1883
1889
|
if (depsChanged) {
|
|
1884
|
-
|
|
1890
|
+
console.log(
|
|
1891
|
+
"STATE_TRANSITION: Deps changed. -> WAITING_FOR_ARRAY"
|
|
1892
|
+
);
|
|
1893
|
+
setStatus("WAITING_FOR_ARRAY");
|
|
1894
|
+
return;
|
|
1885
1895
|
}
|
|
1886
1896
|
|
|
1887
1897
|
if (
|
|
1888
|
-
|
|
1889
|
-
|
|
1898
|
+
hasNewItems &&
|
|
1899
|
+
status === "LOCKED_AT_BOTTOM" &&
|
|
1900
|
+
stickToBottom
|
|
1890
1901
|
) {
|
|
1891
1902
|
console.log(
|
|
1892
|
-
"
|
|
1903
|
+
"STATE_TRANSITION: New items arrived while locked. -> GETTING_ARRAY_HEIGHTS"
|
|
1893
1904
|
);
|
|
1894
|
-
|
|
1895
|
-
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
1896
|
-
endIndex: totalCount,
|
|
1897
|
-
});
|
|
1905
|
+
setStatus("GETTING_ARRAY_HEIGHTS");
|
|
1898
1906
|
}
|
|
1899
1907
|
|
|
1900
1908
|
prevTotalCountRef.current = totalCount;
|
|
1901
1909
|
prevDepsRef.current = dependencies;
|
|
1902
1910
|
}, [totalCount, ...dependencies]);
|
|
1903
1911
|
|
|
1904
|
-
// ---
|
|
1905
|
-
// This effect
|
|
1912
|
+
// --- STATE MACHINE ACTIONS ---
|
|
1913
|
+
// This effect handles the actions for each state.
|
|
1906
1914
|
useLayoutEffect(() => {
|
|
1907
1915
|
const container = containerRef.current;
|
|
1908
|
-
|
|
1909
|
-
|
|
1916
|
+
if (!container) return;
|
|
1917
|
+
|
|
1918
|
+
let intervalId: NodeJS.Timeout | undefined;
|
|
1919
|
+
let scrollTimeoutId: NodeJS.Timeout | undefined;
|
|
1910
1920
|
|
|
1911
|
-
// We only start the loop if the range is correctly set to the end and we are locked.
|
|
1912
1921
|
if (
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1922
|
+
status === "WAITING_FOR_ARRAY" &&
|
|
1923
|
+
totalCount > 0 &&
|
|
1924
|
+
stickToBottom
|
|
1916
1925
|
) {
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1926
|
+
console.log(
|
|
1927
|
+
"ACTION: WAITING_FOR_ARRAY -> GETTING_ARRAY_HEIGHTS"
|
|
1928
|
+
);
|
|
1929
|
+
setStatus("GETTING_ARRAY_HEIGHTS");
|
|
1930
|
+
} else if (status === "GETTING_ARRAY_HEIGHTS") {
|
|
1931
|
+
console.log(
|
|
1932
|
+
"ACTION: GETTING_ARRAY_HEIGHTS. Setting range and starting loop."
|
|
1933
|
+
);
|
|
1934
|
+
setRange({
|
|
1935
|
+
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
1936
|
+
endIndex: totalCount,
|
|
1937
|
+
});
|
|
1927
1938
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1939
|
+
intervalId = setInterval(() => {
|
|
1940
|
+
const lastItemIndex = totalCount - 1;
|
|
1941
|
+
const shadowArray =
|
|
1942
|
+
getGlobalStore
|
|
1943
|
+
.getState()
|
|
1944
|
+
.getShadowMetadata(stateKey, path) || [];
|
|
1945
|
+
const lastItemHeight =
|
|
1946
|
+
shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
|
|
1947
|
+
|
|
1948
|
+
if (lastItemHeight > 0) {
|
|
1949
|
+
clearInterval(intervalId);
|
|
1950
|
+
console.log(
|
|
1951
|
+
"ACTION: Measurement success. -> MOVING_TO_BOTTOM"
|
|
1952
|
+
);
|
|
1953
|
+
setStatus("MOVING_TO_BOTTOM");
|
|
1954
|
+
}
|
|
1955
|
+
}, 100);
|
|
1956
|
+
} else if (status === "MOVING_TO_BOTTOM") {
|
|
1957
|
+
console.log("ACTION: MOVING_TO_BOTTOM. Executing scroll.");
|
|
1958
|
+
const scrollBehavior =
|
|
1959
|
+
prevTotalCountRef.current === 0 ? "auto" : "smooth";
|
|
1960
|
+
|
|
1961
|
+
container.scrollTo({
|
|
1962
|
+
top: container.scrollHeight,
|
|
1963
|
+
behavior: scrollBehavior,
|
|
1964
|
+
});
|
|
1935
1965
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1966
|
+
scrollTimeoutId = setTimeout(
|
|
1967
|
+
() => {
|
|
1968
|
+
console.log(
|
|
1969
|
+
"ACTION: Scroll finished. -> LOCKED_AT_BOTTOM"
|
|
1970
|
+
);
|
|
1971
|
+
setStatus("LOCKED_AT_BOTTOM");
|
|
1972
|
+
},
|
|
1973
|
+
scrollBehavior === "smooth" ? 500 : 50
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1942
1976
|
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
});
|
|
1948
|
-
// Give the animation time to finish before unsetting the flag
|
|
1949
|
-
setTimeout(() => {
|
|
1950
|
-
isAutoScrolling.current = false;
|
|
1951
|
-
}, 1000);
|
|
1952
|
-
} else if (loopCount > 30) {
|
|
1953
|
-
console.error(
|
|
1954
|
-
"LOOP TIMEOUT: Last item was never measured. Stopping loop."
|
|
1955
|
-
);
|
|
1977
|
+
// THE FIX: This cleanup runs whenever the state changes, killing any active timers.
|
|
1978
|
+
return () => {
|
|
1979
|
+
if (intervalId) {
|
|
1980
|
+
console.log("CLEANUP: Clearing measurement loop timer.");
|
|
1956
1981
|
clearInterval(intervalId);
|
|
1957
|
-
} else {
|
|
1958
|
-
console.log("...WAITING. Height is not ready.");
|
|
1959
1982
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1983
|
+
if (scrollTimeoutId) {
|
|
1984
|
+
console.log("CLEANUP: Clearing scroll-end timer.");
|
|
1985
|
+
clearTimeout(scrollTimeoutId);
|
|
1986
|
+
}
|
|
1987
|
+
};
|
|
1988
|
+
}, [status, totalCount, positions]);
|
|
1964
1989
|
|
|
1965
|
-
// ---
|
|
1990
|
+
// --- USER INTERACTION ---
|
|
1991
|
+
// This effect only handles user scrolls.
|
|
1966
1992
|
useEffect(() => {
|
|
1967
1993
|
const container = containerRef.current;
|
|
1968
1994
|
if (!container) return;
|
|
1969
1995
|
|
|
1970
|
-
const
|
|
1996
|
+
const handleUserScroll = () => {
|
|
1997
|
+
const isAtBottom =
|
|
1998
|
+
container.scrollHeight -
|
|
1999
|
+
container.scrollTop -
|
|
2000
|
+
container.clientHeight <
|
|
2001
|
+
1;
|
|
2002
|
+
// If the user scrolls up from a locked or scrolling state, move to idle.
|
|
2003
|
+
if (
|
|
2004
|
+
!isAtBottom &&
|
|
2005
|
+
(status === "LOCKED_AT_BOTTOM" ||
|
|
2006
|
+
status === "MOVING_TO_BOTTOM")
|
|
2007
|
+
) {
|
|
2008
|
+
console.log(
|
|
2009
|
+
"USER_ACTION: Scrolled up. -> IDLE_NOT_AT_BOTTOM"
|
|
2010
|
+
);
|
|
2011
|
+
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// Update the rendered range based on scroll position.
|
|
1971
2015
|
const { scrollTop, clientHeight } = container;
|
|
1972
2016
|
let low = 0,
|
|
1973
2017
|
high = totalCount - 1;
|
|
@@ -1991,50 +2035,32 @@ function createProxyHandler<T>(
|
|
|
1991
2035
|
});
|
|
1992
2036
|
};
|
|
1993
2037
|
|
|
1994
|
-
const handleUserScroll = () => {
|
|
1995
|
-
if (isAutoScrolling.current) return;
|
|
1996
|
-
const isAtBottom =
|
|
1997
|
-
container.scrollHeight -
|
|
1998
|
-
container.scrollTop -
|
|
1999
|
-
container.clientHeight <
|
|
2000
|
-
1;
|
|
2001
|
-
if (!isAtBottom) {
|
|
2002
|
-
isLockedToBottomRef.current = false;
|
|
2003
|
-
}
|
|
2004
|
-
updateVirtualRange();
|
|
2005
|
-
};
|
|
2006
|
-
|
|
2007
2038
|
container.addEventListener("scroll", handleUserScroll, {
|
|
2008
2039
|
passive: true,
|
|
2009
2040
|
});
|
|
2010
|
-
updateVirtualRange(); // Always run to set the initial view
|
|
2011
|
-
|
|
2012
2041
|
return () =>
|
|
2013
2042
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2014
|
-
}, [totalCount, positions,
|
|
2043
|
+
}, [totalCount, positions, status]);
|
|
2015
2044
|
|
|
2016
2045
|
const scrollToBottom = useCallback(
|
|
2017
2046
|
(behavior: ScrollBehavior = "smooth") => {
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
behavior,
|
|
2023
|
-
});
|
|
2024
|
-
}
|
|
2047
|
+
console.log(
|
|
2048
|
+
"USER_ACTION: Clicked scroll to bottom button. -> MOVING_TO_BOTTOM"
|
|
2049
|
+
);
|
|
2050
|
+
setStatus("MOVING_TO_BOTTOM");
|
|
2025
2051
|
},
|
|
2026
2052
|
[]
|
|
2027
2053
|
);
|
|
2028
2054
|
|
|
2029
2055
|
const scrollToIndex = useCallback(
|
|
2030
2056
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2031
|
-
if (containerRef.current && positions[index] !== undefined) {
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
}
|
|
2057
|
+
// if (containerRef.current && positions[index] !== undefined) {
|
|
2058
|
+
// isLockedToBottomRef.current = false;
|
|
2059
|
+
// containerRef.current.scrollTo({
|
|
2060
|
+
// top: positions[index],
|
|
2061
|
+
// behavior,
|
|
2062
|
+
// });
|
|
2063
|
+
// }
|
|
2038
2064
|
},
|
|
2039
2065
|
[positions]
|
|
2040
2066
|
);
|