cogsbox-state 0.5.366 → 0.5.367
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 +387 -384
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +70 -76
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1809,19 +1809,21 @@ function createProxyHandler<T>(
|
|
|
1809
1809
|
dependencies = [],
|
|
1810
1810
|
} = options;
|
|
1811
1811
|
|
|
1812
|
+
// YOUR STATE MACHINE STATES
|
|
1812
1813
|
type Status =
|
|
1813
|
-
| "
|
|
1814
|
-
| "
|
|
1815
|
-
| "
|
|
1816
|
-
| "LOCKED_AT_BOTTOM"
|
|
1817
|
-
| "IDLE_NOT_AT_BOTTOM";
|
|
1814
|
+
| "IDLE_AT_TOP" // Initial state for a new chat
|
|
1815
|
+
| "GETTING_HEIGHTS" // Waiting for the last item to be measured
|
|
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
|
|
1818
1819
|
|
|
1819
1820
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
1820
1821
|
const [range, setRange] = useState({
|
|
1821
1822
|
startIndex: 0,
|
|
1822
1823
|
endIndex: 10,
|
|
1823
1824
|
});
|
|
1824
|
-
const [status, setStatus] = useState<Status>("
|
|
1825
|
+
const [status, setStatus] = useState<Status>("IDLE_AT_TOP");
|
|
1826
|
+
|
|
1825
1827
|
const prevTotalCountRef = useRef(0);
|
|
1826
1828
|
const prevDepsRef = useRef(dependencies);
|
|
1827
1829
|
|
|
@@ -1877,8 +1879,8 @@ function createProxyHandler<T>(
|
|
|
1877
1879
|
});
|
|
1878
1880
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1879
1881
|
|
|
1880
|
-
// --- STATE
|
|
1881
|
-
// This effect decides which state to transition
|
|
1882
|
+
// --- 1. STATE CONTROLLER ---
|
|
1883
|
+
// This effect decides which state to transition TO.
|
|
1882
1884
|
useLayoutEffect(() => {
|
|
1883
1885
|
const depsChanged = !isDeepEqual(
|
|
1884
1886
|
dependencies,
|
|
@@ -1887,11 +1889,9 @@ function createProxyHandler<T>(
|
|
|
1887
1889
|
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1888
1890
|
|
|
1889
1891
|
if (depsChanged) {
|
|
1890
|
-
console.log(
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
setStatus("WAITING_FOR_ARRAY");
|
|
1894
|
-
return;
|
|
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
1895
|
}
|
|
1896
1896
|
|
|
1897
1897
|
if (
|
|
@@ -1900,36 +1900,36 @@ function createProxyHandler<T>(
|
|
|
1900
1900
|
stickToBottom
|
|
1901
1901
|
) {
|
|
1902
1902
|
console.log(
|
|
1903
|
-
"
|
|
1903
|
+
"TRANSITION: New items arrived while locked -> GETTING_HEIGHTS"
|
|
1904
1904
|
);
|
|
1905
|
-
setStatus("
|
|
1905
|
+
setStatus("GETTING_HEIGHTS");
|
|
1906
1906
|
}
|
|
1907
1907
|
|
|
1908
1908
|
prevTotalCountRef.current = totalCount;
|
|
1909
1909
|
prevDepsRef.current = dependencies;
|
|
1910
1910
|
}, [totalCount, ...dependencies]);
|
|
1911
1911
|
|
|
1912
|
-
// --- STATE
|
|
1913
|
-
// This effect
|
|
1912
|
+
// --- 2. STATE ACTION HANDLER ---
|
|
1913
|
+
// This effect performs the ACTION for the current state.
|
|
1914
1914
|
useLayoutEffect(() => {
|
|
1915
1915
|
const container = containerRef.current;
|
|
1916
1916
|
if (!container) return;
|
|
1917
1917
|
|
|
1918
1918
|
let intervalId: NodeJS.Timeout | undefined;
|
|
1919
|
-
let scrollTimeoutId: NodeJS.Timeout | undefined;
|
|
1920
1919
|
|
|
1921
1920
|
if (
|
|
1922
|
-
status === "
|
|
1923
|
-
|
|
1924
|
-
|
|
1921
|
+
status === "IDLE_AT_TOP" &&
|
|
1922
|
+
stickToBottom &&
|
|
1923
|
+
totalCount > 0
|
|
1925
1924
|
) {
|
|
1925
|
+
// If we just loaded a new chat, start the process.
|
|
1926
1926
|
console.log(
|
|
1927
|
-
"ACTION:
|
|
1927
|
+
"ACTION (IDLE_AT_TOP): Data has arrived -> GETTING_HEIGHTS"
|
|
1928
1928
|
);
|
|
1929
|
-
setStatus("
|
|
1930
|
-
} else if (status === "
|
|
1929
|
+
setStatus("GETTING_HEIGHTS");
|
|
1930
|
+
} else if (status === "GETTING_HEIGHTS") {
|
|
1931
1931
|
console.log(
|
|
1932
|
-
"ACTION:
|
|
1932
|
+
"ACTION (GETTING_HEIGHTS): Setting range to end and starting loop."
|
|
1933
1933
|
);
|
|
1934
1934
|
setRange({
|
|
1935
1935
|
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
@@ -1948,13 +1948,16 @@ function createProxyHandler<T>(
|
|
|
1948
1948
|
if (lastItemHeight > 0) {
|
|
1949
1949
|
clearInterval(intervalId);
|
|
1950
1950
|
console.log(
|
|
1951
|
-
"ACTION: Measurement success
|
|
1951
|
+
"ACTION (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
|
|
1952
1952
|
);
|
|
1953
|
-
setStatus("
|
|
1953
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
1954
1954
|
}
|
|
1955
1955
|
}, 100);
|
|
1956
|
-
} else if (status === "
|
|
1957
|
-
console.log(
|
|
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.
|
|
1958
1961
|
const scrollBehavior =
|
|
1959
1962
|
prevTotalCountRef.current === 0 ? "auto" : "smooth";
|
|
1960
1963
|
|
|
@@ -1963,55 +1966,49 @@ function createProxyHandler<T>(
|
|
|
1963
1966
|
behavior: scrollBehavior,
|
|
1964
1967
|
});
|
|
1965
1968
|
|
|
1966
|
-
|
|
1969
|
+
const timeoutId = setTimeout(
|
|
1967
1970
|
() => {
|
|
1968
1971
|
console.log(
|
|
1969
|
-
"ACTION: Scroll finished
|
|
1972
|
+
"ACTION (SCROLLING_TO_BOTTOM): Scroll finished -> LOCKED_AT_BOTTOM"
|
|
1970
1973
|
);
|
|
1971
1974
|
setStatus("LOCKED_AT_BOTTOM");
|
|
1972
1975
|
},
|
|
1973
1976
|
scrollBehavior === "smooth" ? 500 : 50
|
|
1974
1977
|
);
|
|
1978
|
+
|
|
1979
|
+
return () => clearTimeout(timeoutId);
|
|
1975
1980
|
}
|
|
1976
1981
|
|
|
1977
|
-
//
|
|
1982
|
+
// If status is IDLE_NOT_AT_BOTTOM or LOCKED_AT_BOTTOM, we do nothing here.
|
|
1983
|
+
// The scroll has either finished or been disabled by the user.
|
|
1984
|
+
|
|
1978
1985
|
return () => {
|
|
1979
|
-
if (intervalId)
|
|
1980
|
-
console.log("CLEANUP: Clearing measurement loop timer.");
|
|
1981
|
-
clearInterval(intervalId);
|
|
1982
|
-
}
|
|
1983
|
-
if (scrollTimeoutId) {
|
|
1984
|
-
console.log("CLEANUP: Clearing scroll-end timer.");
|
|
1985
|
-
clearTimeout(scrollTimeoutId);
|
|
1986
|
-
}
|
|
1986
|
+
if (intervalId) clearInterval(intervalId);
|
|
1987
1987
|
};
|
|
1988
1988
|
}, [status, totalCount, positions]);
|
|
1989
1989
|
|
|
1990
|
-
// --- USER INTERACTION ---
|
|
1991
|
-
// This effect only handles user scrolls.
|
|
1990
|
+
// --- 3. USER INTERACTION & RANGE UPDATER ---
|
|
1992
1991
|
useEffect(() => {
|
|
1993
1992
|
const container = containerRef.current;
|
|
1994
1993
|
if (!container) return;
|
|
1995
1994
|
|
|
1996
1995
|
const handleUserScroll = () => {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
container.
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
!isAtBottom
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
);
|
|
2011
|
-
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
1996
|
+
// This is the core logic you wanted.
|
|
1997
|
+
if (status !== "IDLE_NOT_AT_BOTTOM") {
|
|
1998
|
+
const isAtBottom =
|
|
1999
|
+
container.scrollHeight -
|
|
2000
|
+
container.scrollTop -
|
|
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
|
+
}
|
|
2012
2009
|
}
|
|
2013
|
-
|
|
2014
|
-
//
|
|
2010
|
+
// We always update the range, regardless of state.
|
|
2011
|
+
// This is the full, non-placeholder function.
|
|
2015
2012
|
const { scrollTop, clientHeight } = container;
|
|
2016
2013
|
let low = 0,
|
|
2017
2014
|
high = totalCount - 1;
|
|
@@ -2040,27 +2037,24 @@ function createProxyHandler<T>(
|
|
|
2040
2037
|
});
|
|
2041
2038
|
return () =>
|
|
2042
2039
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2043
|
-
}, [totalCount, positions, status]);
|
|
2040
|
+
}, [totalCount, positions, status]); // Depends on status to know if it should break the lock
|
|
2044
2041
|
|
|
2045
|
-
const scrollToBottom = useCallback(
|
|
2046
|
-
(
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
},
|
|
2052
|
-
[]
|
|
2053
|
-
);
|
|
2042
|
+
const scrollToBottom = useCallback(() => {
|
|
2043
|
+
console.log(
|
|
2044
|
+
"USER ACTION: Clicked scroll button -> SCROLLING_TO_BOTTOM"
|
|
2045
|
+
);
|
|
2046
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
2047
|
+
}, []);
|
|
2054
2048
|
|
|
2055
2049
|
const scrollToIndex = useCallback(
|
|
2056
2050
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2051
|
+
if (containerRef.current && positions[index] !== undefined) {
|
|
2052
|
+
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2053
|
+
containerRef.current.scrollTo({
|
|
2054
|
+
top: positions[index],
|
|
2055
|
+
behavior,
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2064
2058
|
},
|
|
2065
2059
|
[positions]
|
|
2066
2060
|
);
|