cogsbox-state 0.5.368 → 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 -65
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1809,16 +1809,21 @@ 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>("
|
|
1825
|
+
const [status, setStatus] = useState<Status>("IDLE_AT_TOP");
|
|
1826
|
+
|
|
1822
1827
|
const prevTotalCountRef = useRef(0);
|
|
1823
1828
|
const prevDepsRef = useRef(dependencies);
|
|
1824
1829
|
|
|
@@ -1860,6 +1865,7 @@ function createProxyHandler<T>(
|
|
|
1860
1865
|
shadowUpdateTrigger,
|
|
1861
1866
|
]);
|
|
1862
1867
|
|
|
1868
|
+
// THIS IS THE FULL, NON-PLACEHOLDER FUNCTION
|
|
1863
1869
|
const virtualState = useMemo(() => {
|
|
1864
1870
|
const start = Math.max(0, range.startIndex);
|
|
1865
1871
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1874,82 +1880,136 @@ function createProxyHandler<T>(
|
|
|
1874
1880
|
});
|
|
1875
1881
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1876
1882
|
|
|
1877
|
-
// ---
|
|
1883
|
+
// --- 1. STATE CONTROLLER ---
|
|
1884
|
+
// This effect decides which state to transition TO.
|
|
1878
1885
|
useLayoutEffect(() => {
|
|
1879
|
-
const container = containerRef.current;
|
|
1880
|
-
if (!container) return;
|
|
1881
|
-
|
|
1882
1886
|
const depsChanged = !isDeepEqual(
|
|
1883
1887
|
dependencies,
|
|
1884
1888
|
prevDepsRef.current
|
|
1885
1889
|
);
|
|
1886
1890
|
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1887
1891
|
|
|
1888
|
-
// 1. On Chat Change: Reset to a clean state and trigger an instant scroll.
|
|
1889
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
|
+
) {
|
|
1890
1903
|
console.log(
|
|
1891
|
-
"
|
|
1904
|
+
"TRANSITION: New items arrived while locked -> GETTING_HEIGHTS"
|
|
1892
1905
|
);
|
|
1893
|
-
setStatus("
|
|
1894
|
-
// This return is important. It lets the effect re-run with the new status.
|
|
1895
|
-
return;
|
|
1906
|
+
setStatus("GETTING_HEIGHTS");
|
|
1896
1907
|
}
|
|
1897
1908
|
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
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
|
-
}
|
|
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;
|
|
1914
1918
|
|
|
1915
|
-
let
|
|
1919
|
+
let intervalId: NodeJS.Timeout | undefined;
|
|
1916
1920
|
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
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") {
|
|
1923
1932
|
console.log(
|
|
1924
|
-
|
|
1933
|
+
"ACTION (GETTING_HEIGHTS): Setting range to end and starting loop."
|
|
1925
1934
|
);
|
|
1935
|
+
setRange({
|
|
1936
|
+
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
1937
|
+
endIndex: totalCount,
|
|
1938
|
+
});
|
|
1939
|
+
|
|
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") {
|
|
1958
|
+
console.log(
|
|
1959
|
+
"ACTION (SCROLLING_TO_BOTTOM): Executing scroll."
|
|
1960
|
+
);
|
|
1961
|
+
// Use 'auto' for initial load, 'smooth' for new messages.
|
|
1962
|
+
const scrollBehavior =
|
|
1963
|
+
prevTotalCountRef.current === 0 ? "auto" : "smooth";
|
|
1926
1964
|
|
|
1927
1965
|
container.scrollTo({
|
|
1928
1966
|
top: container.scrollHeight,
|
|
1929
1967
|
behavior: scrollBehavior,
|
|
1930
1968
|
});
|
|
1931
1969
|
|
|
1932
|
-
|
|
1933
|
-
// The timeout lets the animation finish so this doesn't happen too early.
|
|
1934
|
-
scrollTimeoutId = setTimeout(
|
|
1970
|
+
const timeoutId = setTimeout(
|
|
1935
1971
|
() => {
|
|
1936
|
-
console.log(
|
|
1937
|
-
|
|
1972
|
+
console.log(
|
|
1973
|
+
"ACTION (SCROLLING_TO_BOTTOM): Scroll finished -> LOCKED_AT_BOTTOM"
|
|
1974
|
+
);
|
|
1975
|
+
setStatus("LOCKED_AT_BOTTOM");
|
|
1938
1976
|
},
|
|
1939
|
-
|
|
1977
|
+
scrollBehavior === "smooth" ? 500 : 50
|
|
1940
1978
|
);
|
|
1979
|
+
|
|
1980
|
+
return () => clearTimeout(timeoutId);
|
|
1941
1981
|
}
|
|
1942
1982
|
|
|
1943
|
-
//
|
|
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
|
+
|
|
1944
1996
|
const handleUserScroll = () => {
|
|
1945
|
-
//
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
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
|
+
}
|
|
1950
2010
|
}
|
|
1951
|
-
|
|
1952
|
-
// This is the full
|
|
2011
|
+
// We always update the range, regardless of state.
|
|
2012
|
+
// This is the full, non-placeholder function.
|
|
1953
2013
|
const { scrollTop, clientHeight } = container;
|
|
1954
2014
|
let low = 0,
|
|
1955
2015
|
high = totalCount - 1;
|
|
@@ -1976,21 +2036,9 @@ function createProxyHandler<T>(
|
|
|
1976
2036
|
container.addEventListener("scroll", handleUserScroll, {
|
|
1977
2037
|
passive: true,
|
|
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
|
+
return () =>
|
|
1990
2040
|
container.removeEventListener("scroll", handleUserScroll);
|
|
1991
|
-
|
|
1992
|
-
};
|
|
1993
|
-
}, [totalCount, positions, status, ...dependencies]);
|
|
2041
|
+
}, [totalCount, positions, status]); // Depends on status to know if it should break the lock
|
|
1994
2042
|
|
|
1995
2043
|
const scrollToBottom = useCallback(() => {
|
|
1996
2044
|
console.log(
|
|
@@ -2002,7 +2050,7 @@ function createProxyHandler<T>(
|
|
|
2002
2050
|
const scrollToIndex = useCallback(
|
|
2003
2051
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2004
2052
|
if (containerRef.current && positions[index] !== undefined) {
|
|
2005
|
-
setStatus("
|
|
2053
|
+
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2006
2054
|
containerRef.current.scrollTo({
|
|
2007
2055
|
top: positions[index],
|
|
2008
2056
|
behavior,
|