cogsbox-state 0.5.369 → 0.5.370
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 +686 -663
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +113 -66
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1809,18 +1809,22 @@ function createProxyHandler<T>(
|
|
|
1809
1809
|
dependencies = [],
|
|
1810
1810
|
} = options;
|
|
1811
1811
|
|
|
1812
|
+
// YOUR STATE MACHINE STATES
|
|
1812
1813
|
type Status =
|
|
1813
|
-
| "
|
|
1814
|
-
| "
|
|
1814
|
+
| "IDLE_AT_TOP"
|
|
1815
|
+
| "GETTING_HEIGHTS"
|
|
1816
|
+
| "SCROLLING_TO_BOTTOM"
|
|
1817
|
+
| "LOCKED_AT_BOTTOM"
|
|
1818
|
+
| "IDLE_NOT_AT_BOTTOM";
|
|
1815
1819
|
|
|
1816
1820
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
1817
1821
|
const [range, setRange] = useState({
|
|
1818
1822
|
startIndex: 0,
|
|
1819
1823
|
endIndex: 10,
|
|
1820
1824
|
});
|
|
1821
|
-
const [status, setStatus] = useState<Status>("
|
|
1822
|
-
const prevTotalCountRef = useRef(0);
|
|
1825
|
+
const [status, setStatus] = useState<Status>("IDLE_AT_TOP");
|
|
1823
1826
|
|
|
1827
|
+
const prevTotalCountRef = useRef(0);
|
|
1824
1828
|
const prevDepsRef = useRef(dependencies);
|
|
1825
1829
|
|
|
1826
1830
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
@@ -1861,6 +1865,7 @@ function createProxyHandler<T>(
|
|
|
1861
1865
|
shadowUpdateTrigger,
|
|
1862
1866
|
]);
|
|
1863
1867
|
|
|
1868
|
+
// THIS IS THE FULL, NON-PLACEHOLDER FUNCTION
|
|
1864
1869
|
const virtualState = useMemo(() => {
|
|
1865
1870
|
const start = Math.max(0, range.startIndex);
|
|
1866
1871
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1875,82 +1880,136 @@ function createProxyHandler<T>(
|
|
|
1875
1880
|
});
|
|
1876
1881
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1877
1882
|
|
|
1878
|
-
// ---
|
|
1883
|
+
// --- 1. STATE CONTROLLER ---
|
|
1884
|
+
// This effect decides which state to transition TO.
|
|
1879
1885
|
useLayoutEffect(() => {
|
|
1880
|
-
const container = containerRef.current;
|
|
1881
|
-
if (!container) return;
|
|
1882
|
-
|
|
1883
1886
|
const depsChanged = !isDeepEqual(
|
|
1884
1887
|
dependencies,
|
|
1885
1888
|
prevDepsRef.current
|
|
1886
1889
|
);
|
|
1887
1890
|
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1888
1891
|
|
|
1889
|
-
// 1. On Chat Change: Reset to a clean state and trigger an instant scroll.
|
|
1890
1892
|
if (depsChanged) {
|
|
1893
|
+
console.log("TRANSITION: Deps changed -> IDLE_AT_TOP");
|
|
1894
|
+
setStatus("IDLE_AT_TOP");
|
|
1895
|
+
return; // Stop here, let the next effect handle the action for the new state.
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
if (
|
|
1899
|
+
hasNewItems &&
|
|
1900
|
+
status === "LOCKED_AT_BOTTOM" &&
|
|
1901
|
+
stickToBottom
|
|
1902
|
+
) {
|
|
1891
1903
|
console.log(
|
|
1892
|
-
"
|
|
1904
|
+
"TRANSITION: New items arrived while locked -> GETTING_HEIGHTS"
|
|
1893
1905
|
);
|
|
1894
|
-
setStatus("
|
|
1895
|
-
// This return is important. It lets the effect re-run with the new status.
|
|
1896
|
-
return;
|
|
1906
|
+
setStatus("GETTING_HEIGHTS");
|
|
1897
1907
|
}
|
|
1898
1908
|
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
console.log(
|
|
1909
|
-
"EVENT: New message arrived while at bottom. -> SCROLLING_TO_BOTTOM"
|
|
1910
|
-
);
|
|
1911
|
-
setStatus("SCROLLING_TO_BOTTOM");
|
|
1912
|
-
return; // Let the effect re-run with the new status.
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1909
|
+
prevTotalCountRef.current = totalCount;
|
|
1910
|
+
prevDepsRef.current = dependencies;
|
|
1911
|
+
}, [totalCount, ...dependencies]);
|
|
1912
|
+
|
|
1913
|
+
// --- 2. STATE ACTION HANDLER ---
|
|
1914
|
+
// This effect performs the ACTION for the current state.
|
|
1915
|
+
useLayoutEffect(() => {
|
|
1916
|
+
const container = containerRef.current;
|
|
1917
|
+
if (!container) return;
|
|
1915
1918
|
|
|
1916
|
-
let
|
|
1919
|
+
let intervalId: NodeJS.Timeout | undefined;
|
|
1920
|
+
|
|
1921
|
+
if (
|
|
1922
|
+
status === "IDLE_AT_TOP" &&
|
|
1923
|
+
stickToBottom &&
|
|
1924
|
+
totalCount > 0
|
|
1925
|
+
) {
|
|
1926
|
+
// If we just loaded a new chat, start the process.
|
|
1927
|
+
console.log(
|
|
1928
|
+
"ACTION (IDLE_AT_TOP): Data has arrived -> GETTING_HEIGHTS"
|
|
1929
|
+
);
|
|
1930
|
+
setStatus("GETTING_HEIGHTS");
|
|
1931
|
+
} else if (status === "GETTING_HEIGHTS") {
|
|
1932
|
+
console.log(
|
|
1933
|
+
"ACTION (GETTING_HEIGHTS): Setting range to end and starting loop."
|
|
1934
|
+
);
|
|
1935
|
+
setRange({
|
|
1936
|
+
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
1937
|
+
endIndex: totalCount,
|
|
1938
|
+
});
|
|
1917
1939
|
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1940
|
+
intervalId = setInterval(() => {
|
|
1941
|
+
const lastItemIndex = totalCount - 1;
|
|
1942
|
+
const shadowArray =
|
|
1943
|
+
getGlobalStore
|
|
1944
|
+
.getState()
|
|
1945
|
+
.getShadowMetadata(stateKey, path) || [];
|
|
1946
|
+
const lastItemHeight =
|
|
1947
|
+
shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
|
|
1948
|
+
|
|
1949
|
+
if (lastItemHeight > 0) {
|
|
1950
|
+
clearInterval(intervalId);
|
|
1951
|
+
console.log(
|
|
1952
|
+
"ACTION (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
|
|
1953
|
+
);
|
|
1954
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
1955
|
+
}
|
|
1956
|
+
}, 100);
|
|
1957
|
+
} else if (status === "SCROLLING_TO_BOTTOM") {
|
|
1924
1958
|
console.log(
|
|
1925
|
-
|
|
1959
|
+
"ACTION (SCROLLING_TO_BOTTOM): Executing scroll."
|
|
1926
1960
|
);
|
|
1961
|
+
// Use 'auto' for initial load, 'smooth' for new messages.
|
|
1962
|
+
const scrollBehavior =
|
|
1963
|
+
prevTotalCountRef.current === 0 ? "auto" : "smooth";
|
|
1927
1964
|
|
|
1928
1965
|
container.scrollTo({
|
|
1929
1966
|
top: container.scrollHeight,
|
|
1930
1967
|
behavior: scrollBehavior,
|
|
1931
1968
|
});
|
|
1932
1969
|
|
|
1933
|
-
|
|
1934
|
-
// The timeout lets the animation finish so this doesn't happen too early.
|
|
1935
|
-
scrollTimeoutId = setTimeout(
|
|
1970
|
+
const timeoutId = setTimeout(
|
|
1936
1971
|
() => {
|
|
1937
|
-
console.log(
|
|
1938
|
-
|
|
1972
|
+
console.log(
|
|
1973
|
+
"ACTION (SCROLLING_TO_BOTTOM): Scroll finished -> LOCKED_AT_BOTTOM"
|
|
1974
|
+
);
|
|
1975
|
+
setStatus("LOCKED_AT_BOTTOM");
|
|
1939
1976
|
},
|
|
1940
|
-
|
|
1977
|
+
scrollBehavior === "smooth" ? 500 : 50
|
|
1941
1978
|
);
|
|
1979
|
+
|
|
1980
|
+
return () => clearTimeout(timeoutId);
|
|
1942
1981
|
}
|
|
1943
1982
|
|
|
1944
|
-
//
|
|
1983
|
+
// If status is IDLE_NOT_AT_BOTTOM or LOCKED_AT_BOTTOM, we do nothing here.
|
|
1984
|
+
// The scroll has either finished or been disabled by the user.
|
|
1985
|
+
|
|
1986
|
+
return () => {
|
|
1987
|
+
if (intervalId) clearInterval(intervalId);
|
|
1988
|
+
};
|
|
1989
|
+
}, [status, totalCount, positions]);
|
|
1990
|
+
|
|
1991
|
+
// --- 3. USER INTERACTION & RANGE UPDATER ---
|
|
1992
|
+
useEffect(() => {
|
|
1993
|
+
const container = containerRef.current;
|
|
1994
|
+
if (!container) return;
|
|
1995
|
+
|
|
1945
1996
|
const handleUserScroll = () => {
|
|
1946
|
-
//
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1997
|
+
// This is the core logic you wanted.
|
|
1998
|
+
if (status !== "IDLE_NOT_AT_BOTTOM") {
|
|
1999
|
+
const isAtBottom =
|
|
2000
|
+
container.scrollHeight -
|
|
2001
|
+
container.scrollTop -
|
|
2002
|
+
container.clientHeight <
|
|
2003
|
+
1;
|
|
2004
|
+
if (!isAtBottom) {
|
|
2005
|
+
console.log(
|
|
2006
|
+
"USER ACTION: Scrolled up -> IDLE_NOT_AT_BOTTOM"
|
|
2007
|
+
);
|
|
2008
|
+
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2009
|
+
}
|
|
1951
2010
|
}
|
|
1952
|
-
|
|
1953
|
-
// This is the full
|
|
2011
|
+
// We always update the range, regardless of state.
|
|
2012
|
+
// This is the full, non-placeholder function.
|
|
1954
2013
|
const { scrollTop, clientHeight } = container;
|
|
1955
2014
|
let low = 0,
|
|
1956
2015
|
high = totalCount - 1;
|
|
@@ -1977,21 +2036,9 @@ function createProxyHandler<T>(
|
|
|
1977
2036
|
container.addEventListener("scroll", handleUserScroll, {
|
|
1978
2037
|
passive: true,
|
|
1979
2038
|
});
|
|
1980
|
-
|
|
1981
|
-
// Always update range on render if we are IDLE.
|
|
1982
|
-
if (status === "IDLE") {
|
|
1983
|
-
handleUserScroll();
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
|
-
// Update refs for the next render cycle.
|
|
1987
|
-
prevTotalCountRef.current = totalCount;
|
|
1988
|
-
prevDepsRef.current = dependencies;
|
|
1989
|
-
|
|
1990
|
-
return () => {
|
|
2039
|
+
return () =>
|
|
1991
2040
|
container.removeEventListener("scroll", handleUserScroll);
|
|
1992
|
-
|
|
1993
|
-
};
|
|
1994
|
-
}, [totalCount, positions, status, ...dependencies]);
|
|
2041
|
+
}, [totalCount, positions, status]); // Depends on status to know if it should break the lock
|
|
1995
2042
|
|
|
1996
2043
|
const scrollToBottom = useCallback(() => {
|
|
1997
2044
|
console.log(
|
|
@@ -2003,7 +2050,7 @@ function createProxyHandler<T>(
|
|
|
2003
2050
|
const scrollToIndex = useCallback(
|
|
2004
2051
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2005
2052
|
if (containerRef.current && positions[index] !== undefined) {
|
|
2006
|
-
setStatus("
|
|
2053
|
+
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2007
2054
|
containerRef.current.scrollTo({
|
|
2008
2055
|
top: positions[index],
|
|
2009
2056
|
behavior,
|