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