cogsbox-state 0.5.367 → 0.5.368
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 +663 -686
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +65 -112
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1809,21 +1809,16 @@ function createProxyHandler<T>(
|
|
|
1809
1809
|
dependencies = [],
|
|
1810
1810
|
} = options;
|
|
1811
1811
|
|
|
1812
|
-
// YOUR STATE MACHINE STATES
|
|
1813
1812
|
type Status =
|
|
1814
|
-
| "
|
|
1815
|
-
| "
|
|
1816
|
-
| "SCROLLING_TO_BOTTOM" // Performing the scroll animation
|
|
1817
|
-
| "LOCKED_AT_BOTTOM" // Idle at the bottom, waiting for new messages
|
|
1818
|
-
| "IDLE_NOT_AT_BOTTOM"; // User has scrolled up and broken the lock
|
|
1813
|
+
| "IDLE" // User is in control, or we are resting at the bottom.
|
|
1814
|
+
| "SCROLLING_TO_BOTTOM"; // Performing a scroll animation.
|
|
1819
1815
|
|
|
1820
1816
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
1821
1817
|
const [range, setRange] = useState({
|
|
1822
1818
|
startIndex: 0,
|
|
1823
1819
|
endIndex: 10,
|
|
1824
1820
|
});
|
|
1825
|
-
const [status, setStatus] = useState<Status>("
|
|
1826
|
-
|
|
1821
|
+
const [status, setStatus] = useState<Status>("IDLE");
|
|
1827
1822
|
const prevTotalCountRef = useRef(0);
|
|
1828
1823
|
const prevDepsRef = useRef(dependencies);
|
|
1829
1824
|
|
|
@@ -1879,136 +1874,82 @@ function createProxyHandler<T>(
|
|
|
1879
1874
|
});
|
|
1880
1875
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1881
1876
|
|
|
1882
|
-
// ---
|
|
1883
|
-
// This effect decides which state to transition TO.
|
|
1877
|
+
// --- Main Controller Effect ---
|
|
1884
1878
|
useLayoutEffect(() => {
|
|
1879
|
+
const container = containerRef.current;
|
|
1880
|
+
if (!container) return;
|
|
1881
|
+
|
|
1885
1882
|
const depsChanged = !isDeepEqual(
|
|
1886
1883
|
dependencies,
|
|
1887
1884
|
prevDepsRef.current
|
|
1888
1885
|
);
|
|
1889
1886
|
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1890
1887
|
|
|
1888
|
+
// 1. On Chat Change: Reset to a clean state and trigger an instant scroll.
|
|
1891
1889
|
if (depsChanged) {
|
|
1892
|
-
console.log("TRANSITION: Deps changed -> IDLE_AT_TOP");
|
|
1893
|
-
setStatus("IDLE_AT_TOP");
|
|
1894
|
-
return; // Stop here, let the next effect handle the action for the new state.
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
if (
|
|
1898
|
-
hasNewItems &&
|
|
1899
|
-
status === "LOCKED_AT_BOTTOM" &&
|
|
1900
|
-
stickToBottom
|
|
1901
|
-
) {
|
|
1902
1890
|
console.log(
|
|
1903
|
-
"
|
|
1891
|
+
"EVENT: Chat changed. Resetting state and scrolling."
|
|
1904
1892
|
);
|
|
1905
|
-
setStatus("
|
|
1893
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
1894
|
+
// This return is important. It lets the effect re-run with the new status.
|
|
1895
|
+
return;
|
|
1906
1896
|
}
|
|
1907
1897
|
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1898
|
+
// 2. On New Message: Check if we SHOULD scroll.
|
|
1899
|
+
// This will only happen if we are IDLE (meaning we are already at the bottom).
|
|
1900
|
+
if (hasNewItems && status === "IDLE") {
|
|
1901
|
+
const isAtBottom =
|
|
1902
|
+
container.scrollHeight -
|
|
1903
|
+
container.scrollTop -
|
|
1904
|
+
container.clientHeight <
|
|
1905
|
+
itemHeight;
|
|
1906
|
+
if (isAtBottom && stickToBottom) {
|
|
1907
|
+
console.log(
|
|
1908
|
+
"EVENT: New message arrived while at bottom. -> SCROLLING_TO_BOTTOM"
|
|
1909
|
+
);
|
|
1910
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
1911
|
+
return; // Let the effect re-run with the new status.
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1917
1914
|
|
|
1918
|
-
let
|
|
1915
|
+
let scrollTimeoutId: NodeJS.Timeout | undefined;
|
|
1919
1916
|
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
console.log(
|
|
1927
|
-
"ACTION (IDLE_AT_TOP): Data has arrived -> GETTING_HEIGHTS"
|
|
1928
|
-
);
|
|
1929
|
-
setStatus("GETTING_HEIGHTS");
|
|
1930
|
-
} else if (status === "GETTING_HEIGHTS") {
|
|
1917
|
+
// 3. If we are in the SCROLLING state, perform the scroll action.
|
|
1918
|
+
if (status === "SCROLLING_TO_BOTTOM") {
|
|
1919
|
+
// A chat change or initial load should be instant. New messages should be smooth.
|
|
1920
|
+
const isInitial =
|
|
1921
|
+
prevTotalCountRef.current === 0 || depsChanged;
|
|
1922
|
+
const scrollBehavior = isInitial ? "auto" : "smooth";
|
|
1931
1923
|
console.log(
|
|
1932
|
-
|
|
1924
|
+
`ACTION: Scrolling to bottom. Behavior: ${scrollBehavior}`
|
|
1933
1925
|
);
|
|
1934
|
-
setRange({
|
|
1935
|
-
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
1936
|
-
endIndex: totalCount,
|
|
1937
|
-
});
|
|
1938
|
-
|
|
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 (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
|
|
1952
|
-
);
|
|
1953
|
-
setStatus("SCROLLING_TO_BOTTOM");
|
|
1954
|
-
}
|
|
1955
|
-
}, 100);
|
|
1956
|
-
} else if (status === "SCROLLING_TO_BOTTOM") {
|
|
1957
|
-
console.log(
|
|
1958
|
-
"ACTION (SCROLLING_TO_BOTTOM): Executing scroll."
|
|
1959
|
-
);
|
|
1960
|
-
// Use 'auto' for initial load, 'smooth' for new messages.
|
|
1961
|
-
const scrollBehavior =
|
|
1962
|
-
prevTotalCountRef.current === 0 ? "auto" : "smooth";
|
|
1963
1926
|
|
|
1964
1927
|
container.scrollTo({
|
|
1965
1928
|
top: container.scrollHeight,
|
|
1966
1929
|
behavior: scrollBehavior,
|
|
1967
1930
|
});
|
|
1968
1931
|
|
|
1969
|
-
|
|
1932
|
+
// After the scroll, transition back to IDLE.
|
|
1933
|
+
// The timeout lets the animation finish so this doesn't happen too early.
|
|
1934
|
+
scrollTimeoutId = setTimeout(
|
|
1970
1935
|
() => {
|
|
1971
|
-
console.log(
|
|
1972
|
-
|
|
1973
|
-
);
|
|
1974
|
-
setStatus("LOCKED_AT_BOTTOM");
|
|
1936
|
+
console.log("ACTION: Scroll finished. -> IDLE");
|
|
1937
|
+
setStatus("IDLE");
|
|
1975
1938
|
},
|
|
1976
|
-
|
|
1939
|
+
isInitial ? 50 : 500
|
|
1977
1940
|
);
|
|
1978
|
-
|
|
1979
|
-
return () => clearTimeout(timeoutId);
|
|
1980
1941
|
}
|
|
1981
1942
|
|
|
1982
|
-
//
|
|
1983
|
-
// The scroll has either finished or been disabled by the user.
|
|
1984
|
-
|
|
1985
|
-
return () => {
|
|
1986
|
-
if (intervalId) clearInterval(intervalId);
|
|
1987
|
-
};
|
|
1988
|
-
}, [status, totalCount, positions]);
|
|
1989
|
-
|
|
1990
|
-
// --- 3. USER INTERACTION & RANGE UPDATER ---
|
|
1991
|
-
useEffect(() => {
|
|
1992
|
-
const container = containerRef.current;
|
|
1993
|
-
if (!container) return;
|
|
1994
|
-
|
|
1943
|
+
// --- User Scroll Handling & Range Update ---
|
|
1995
1944
|
const handleUserScroll = () => {
|
|
1996
|
-
//
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
container.clientHeight <
|
|
2002
|
-
1;
|
|
2003
|
-
if (!isAtBottom) {
|
|
2004
|
-
console.log(
|
|
2005
|
-
"USER ACTION: Scrolled up -> IDLE_NOT_AT_BOTTOM"
|
|
2006
|
-
);
|
|
2007
|
-
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2008
|
-
}
|
|
1945
|
+
// If the user scrolls, they are in control. Immediately go to IDLE.
|
|
1946
|
+
// This will also cancel any pending scroll-end timeouts.
|
|
1947
|
+
if (status === "SCROLLING_TO_BOTTOM") {
|
|
1948
|
+
console.log("USER ACTION: Interrupted scroll. -> IDLE");
|
|
1949
|
+
setStatus("IDLE");
|
|
2009
1950
|
}
|
|
2010
|
-
|
|
2011
|
-
// This is the full
|
|
1951
|
+
|
|
1952
|
+
// This is the full range update function.
|
|
2012
1953
|
const { scrollTop, clientHeight } = container;
|
|
2013
1954
|
let low = 0,
|
|
2014
1955
|
high = totalCount - 1;
|
|
@@ -2035,9 +1976,21 @@ function createProxyHandler<T>(
|
|
|
2035
1976
|
container.addEventListener("scroll", handleUserScroll, {
|
|
2036
1977
|
passive: true,
|
|
2037
1978
|
});
|
|
2038
|
-
|
|
1979
|
+
|
|
1980
|
+
// Always update range on render if we are IDLE.
|
|
1981
|
+
if (status === "IDLE") {
|
|
1982
|
+
handleUserScroll();
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// Update refs for the next render cycle.
|
|
1986
|
+
prevTotalCountRef.current = totalCount;
|
|
1987
|
+
prevDepsRef.current = dependencies;
|
|
1988
|
+
|
|
1989
|
+
return () => {
|
|
2039
1990
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2040
|
-
|
|
1991
|
+
if (scrollTimeoutId) clearTimeout(scrollTimeoutId);
|
|
1992
|
+
};
|
|
1993
|
+
}, [totalCount, positions, status, ...dependencies]);
|
|
2041
1994
|
|
|
2042
1995
|
const scrollToBottom = useCallback(() => {
|
|
2043
1996
|
console.log(
|
|
@@ -2049,7 +2002,7 @@ function createProxyHandler<T>(
|
|
|
2049
2002
|
const scrollToIndex = useCallback(
|
|
2050
2003
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2051
2004
|
if (containerRef.current && positions[index] !== undefined) {
|
|
2052
|
-
setStatus("
|
|
2005
|
+
setStatus("IDLE");
|
|
2053
2006
|
containerRef.current.scrollTo({
|
|
2054
2007
|
top: positions[index],
|
|
2055
2008
|
behavior,
|