cogsbox-state 0.5.365 → 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 -386
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +66 -67
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,17 +1900,17 @@ 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;
|
|
@@ -1918,17 +1918,18 @@ function createProxyHandler<T>(
|
|
|
1918
1918
|
let intervalId: NodeJS.Timeout | undefined;
|
|
1919
1919
|
|
|
1920
1920
|
if (
|
|
1921
|
-
status === "
|
|
1922
|
-
|
|
1923
|
-
|
|
1921
|
+
status === "IDLE_AT_TOP" &&
|
|
1922
|
+
stickToBottom &&
|
|
1923
|
+
totalCount > 0
|
|
1924
1924
|
) {
|
|
1925
|
+
// If we just loaded a new chat, start the process.
|
|
1925
1926
|
console.log(
|
|
1926
|
-
"ACTION:
|
|
1927
|
+
"ACTION (IDLE_AT_TOP): Data has arrived -> GETTING_HEIGHTS"
|
|
1927
1928
|
);
|
|
1928
|
-
setStatus("
|
|
1929
|
-
} else if (status === "
|
|
1929
|
+
setStatus("GETTING_HEIGHTS");
|
|
1930
|
+
} else if (status === "GETTING_HEIGHTS") {
|
|
1930
1931
|
console.log(
|
|
1931
|
-
"ACTION:
|
|
1932
|
+
"ACTION (GETTING_HEIGHTS): Setting range to end and starting loop."
|
|
1932
1933
|
);
|
|
1933
1934
|
setRange({
|
|
1934
1935
|
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
@@ -1947,13 +1948,16 @@ function createProxyHandler<T>(
|
|
|
1947
1948
|
if (lastItemHeight > 0) {
|
|
1948
1949
|
clearInterval(intervalId);
|
|
1949
1950
|
console.log(
|
|
1950
|
-
"ACTION: Measurement success
|
|
1951
|
+
"ACTION (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
|
|
1951
1952
|
);
|
|
1952
|
-
setStatus("
|
|
1953
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
1953
1954
|
}
|
|
1954
1955
|
}, 100);
|
|
1955
|
-
} else if (status === "
|
|
1956
|
-
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.
|
|
1957
1961
|
const scrollBehavior =
|
|
1958
1962
|
prevTotalCountRef.current === 0 ? "auto" : "smooth";
|
|
1959
1963
|
|
|
@@ -1962,12 +1966,10 @@ function createProxyHandler<T>(
|
|
|
1962
1966
|
behavior: scrollBehavior,
|
|
1963
1967
|
});
|
|
1964
1968
|
|
|
1965
|
-
// After scrolling, we are locked at the bottom.
|
|
1966
|
-
// Use a timeout to wait for the animation to finish.
|
|
1967
1969
|
const timeoutId = setTimeout(
|
|
1968
1970
|
() => {
|
|
1969
1971
|
console.log(
|
|
1970
|
-
"ACTION: Scroll finished
|
|
1972
|
+
"ACTION (SCROLLING_TO_BOTTOM): Scroll finished -> LOCKED_AT_BOTTOM"
|
|
1971
1973
|
);
|
|
1972
1974
|
setStatus("LOCKED_AT_BOTTOM");
|
|
1973
1975
|
},
|
|
@@ -1977,36 +1979,36 @@ function createProxyHandler<T>(
|
|
|
1977
1979
|
return () => clearTimeout(timeoutId);
|
|
1978
1980
|
}
|
|
1979
1981
|
|
|
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
|
+
|
|
1980
1985
|
return () => {
|
|
1981
1986
|
if (intervalId) clearInterval(intervalId);
|
|
1982
1987
|
};
|
|
1983
1988
|
}, [status, totalCount, positions]);
|
|
1984
1989
|
|
|
1985
|
-
// --- USER INTERACTION ---
|
|
1986
|
-
// This effect only handles user scrolls.
|
|
1990
|
+
// --- 3. USER INTERACTION & RANGE UPDATER ---
|
|
1987
1991
|
useEffect(() => {
|
|
1988
1992
|
const container = containerRef.current;
|
|
1989
1993
|
if (!container) return;
|
|
1990
1994
|
|
|
1991
1995
|
const handleUserScroll = () => {
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
container.
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
!isAtBottom
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
);
|
|
2006
|
-
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
|
+
}
|
|
2007
2009
|
}
|
|
2008
|
-
|
|
2009
|
-
//
|
|
2010
|
+
// We always update the range, regardless of state.
|
|
2011
|
+
// This is the full, non-placeholder function.
|
|
2010
2012
|
const { scrollTop, clientHeight } = container;
|
|
2011
2013
|
let low = 0,
|
|
2012
2014
|
high = totalCount - 1;
|
|
@@ -2035,27 +2037,24 @@ function createProxyHandler<T>(
|
|
|
2035
2037
|
});
|
|
2036
2038
|
return () =>
|
|
2037
2039
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2038
|
-
}, [totalCount, positions, status]);
|
|
2040
|
+
}, [totalCount, positions, status]); // Depends on status to know if it should break the lock
|
|
2039
2041
|
|
|
2040
|
-
const scrollToBottom = useCallback(
|
|
2041
|
-
(
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
},
|
|
2047
|
-
[]
|
|
2048
|
-
);
|
|
2042
|
+
const scrollToBottom = useCallback(() => {
|
|
2043
|
+
console.log(
|
|
2044
|
+
"USER ACTION: Clicked scroll button -> SCROLLING_TO_BOTTOM"
|
|
2045
|
+
);
|
|
2046
|
+
setStatus("SCROLLING_TO_BOTTOM");
|
|
2047
|
+
}, []);
|
|
2049
2048
|
|
|
2050
2049
|
const scrollToIndex = useCallback(
|
|
2051
2050
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
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
|
+
}
|
|
2059
2058
|
},
|
|
2060
2059
|
[positions]
|
|
2061
2060
|
);
|