cogsbox-state 0.5.364 → 0.5.365
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 +603 -589
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +117 -96
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1809,13 +1809,19 @@ function createProxyHandler<T>(
|
|
|
1809
1809
|
dependencies = [],
|
|
1810
1810
|
} = options;
|
|
1811
1811
|
|
|
1812
|
+
type Status =
|
|
1813
|
+
| "WAITING_FOR_ARRAY"
|
|
1814
|
+
| "GETTING_ARRAY_HEIGHTS"
|
|
1815
|
+
| "MOVING_TO_BOTTOM"
|
|
1816
|
+
| "LOCKED_AT_BOTTOM"
|
|
1817
|
+
| "IDLE_NOT_AT_BOTTOM";
|
|
1818
|
+
|
|
1812
1819
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
1813
1820
|
const [range, setRange] = useState({
|
|
1814
1821
|
startIndex: 0,
|
|
1815
1822
|
endIndex: 10,
|
|
1816
1823
|
});
|
|
1817
|
-
const
|
|
1818
|
-
const isAutoScrolling = useRef(false);
|
|
1824
|
+
const [status, setStatus] = useState<Status>("WAITING_FOR_ARRAY");
|
|
1819
1825
|
const prevTotalCountRef = useRef(0);
|
|
1820
1826
|
const prevDepsRef = useRef(dependencies);
|
|
1821
1827
|
|
|
@@ -1871,103 +1877,136 @@ function createProxyHandler<T>(
|
|
|
1871
1877
|
});
|
|
1872
1878
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1873
1879
|
|
|
1874
|
-
// ---
|
|
1875
|
-
// This effect
|
|
1880
|
+
// --- STATE MACHINE CONTROLLER ---
|
|
1881
|
+
// This effect decides which state to transition to based on external changes.
|
|
1876
1882
|
useLayoutEffect(() => {
|
|
1877
|
-
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1878
1883
|
const depsChanged = !isDeepEqual(
|
|
1879
1884
|
dependencies,
|
|
1880
1885
|
prevDepsRef.current
|
|
1881
1886
|
);
|
|
1887
|
+
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1882
1888
|
|
|
1883
1889
|
if (depsChanged) {
|
|
1884
|
-
|
|
1890
|
+
console.log(
|
|
1891
|
+
"STATE_TRANSITION: Deps changed. -> WAITING_FOR_ARRAY"
|
|
1892
|
+
);
|
|
1893
|
+
setStatus("WAITING_FOR_ARRAY");
|
|
1894
|
+
return;
|
|
1885
1895
|
}
|
|
1886
1896
|
|
|
1887
1897
|
if (
|
|
1888
|
-
|
|
1889
|
-
|
|
1898
|
+
hasNewItems &&
|
|
1899
|
+
status === "LOCKED_AT_BOTTOM" &&
|
|
1900
|
+
stickToBottom
|
|
1890
1901
|
) {
|
|
1891
1902
|
console.log(
|
|
1892
|
-
"
|
|
1903
|
+
"STATE_TRANSITION: New items arrived while locked. -> GETTING_ARRAY_HEIGHTS"
|
|
1893
1904
|
);
|
|
1894
|
-
|
|
1895
|
-
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
1896
|
-
endIndex: totalCount,
|
|
1897
|
-
});
|
|
1905
|
+
setStatus("GETTING_ARRAY_HEIGHTS");
|
|
1898
1906
|
}
|
|
1899
1907
|
|
|
1900
1908
|
prevTotalCountRef.current = totalCount;
|
|
1901
1909
|
prevDepsRef.current = dependencies;
|
|
1902
1910
|
}, [totalCount, ...dependencies]);
|
|
1903
1911
|
|
|
1904
|
-
// ---
|
|
1905
|
-
// This effect
|
|
1912
|
+
// --- STATE MACHINE ACTIONS ---
|
|
1913
|
+
// This effect handles the actions for each state.
|
|
1906
1914
|
useLayoutEffect(() => {
|
|
1907
1915
|
const container = containerRef.current;
|
|
1908
|
-
|
|
1909
|
-
|
|
1916
|
+
if (!container) return;
|
|
1917
|
+
|
|
1918
|
+
let intervalId: NodeJS.Timeout | undefined;
|
|
1910
1919
|
|
|
1911
|
-
// We only start the loop if the range is correctly set to the end and we are locked.
|
|
1912
1920
|
if (
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1921
|
+
status === "WAITING_FOR_ARRAY" &&
|
|
1922
|
+
totalCount > 0 &&
|
|
1923
|
+
stickToBottom
|
|
1916
1924
|
) {
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1925
|
+
console.log(
|
|
1926
|
+
"ACTION: WAITING_FOR_ARRAY -> GETTING_ARRAY_HEIGHTS"
|
|
1927
|
+
);
|
|
1928
|
+
setStatus("GETTING_ARRAY_HEIGHTS");
|
|
1929
|
+
} else if (status === "GETTING_ARRAY_HEIGHTS") {
|
|
1930
|
+
console.log(
|
|
1931
|
+
"ACTION: GETTING_ARRAY_HEIGHTS. Setting range and starting loop."
|
|
1932
|
+
);
|
|
1933
|
+
setRange({
|
|
1934
|
+
startIndex: Math.max(0, totalCount - 10 - overscan),
|
|
1935
|
+
endIndex: totalCount,
|
|
1936
|
+
});
|
|
1927
1937
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1938
|
+
intervalId = setInterval(() => {
|
|
1939
|
+
const lastItemIndex = totalCount - 1;
|
|
1940
|
+
const shadowArray =
|
|
1941
|
+
getGlobalStore
|
|
1942
|
+
.getState()
|
|
1943
|
+
.getShadowMetadata(stateKey, path) || [];
|
|
1944
|
+
const lastItemHeight =
|
|
1945
|
+
shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
|
|
1946
|
+
|
|
1947
|
+
if (lastItemHeight > 0) {
|
|
1948
|
+
clearInterval(intervalId);
|
|
1949
|
+
console.log(
|
|
1950
|
+
"ACTION: Measurement success. -> MOVING_TO_BOTTOM"
|
|
1951
|
+
);
|
|
1952
|
+
setStatus("MOVING_TO_BOTTOM");
|
|
1953
|
+
}
|
|
1954
|
+
}, 100);
|
|
1955
|
+
} else if (status === "MOVING_TO_BOTTOM") {
|
|
1956
|
+
console.log("ACTION: MOVING_TO_BOTTOM. Executing scroll.");
|
|
1957
|
+
const scrollBehavior =
|
|
1958
|
+
prevTotalCountRef.current === 0 ? "auto" : "smooth";
|
|
1959
|
+
|
|
1960
|
+
container.scrollTo({
|
|
1961
|
+
top: container.scrollHeight,
|
|
1962
|
+
behavior: scrollBehavior,
|
|
1963
|
+
});
|
|
1935
1964
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1965
|
+
// After scrolling, we are locked at the bottom.
|
|
1966
|
+
// Use a timeout to wait for the animation to finish.
|
|
1967
|
+
const timeoutId = setTimeout(
|
|
1968
|
+
() => {
|
|
1969
|
+
console.log(
|
|
1970
|
+
"ACTION: Scroll finished. -> LOCKED_AT_BOTTOM"
|
|
1971
|
+
);
|
|
1972
|
+
setStatus("LOCKED_AT_BOTTOM");
|
|
1973
|
+
},
|
|
1974
|
+
scrollBehavior === "smooth" ? 500 : 50
|
|
1975
|
+
);
|
|
1942
1976
|
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
top: container.scrollHeight,
|
|
1946
|
-
behavior: "smooth",
|
|
1947
|
-
});
|
|
1948
|
-
// Give the animation time to finish before unsetting the flag
|
|
1949
|
-
setTimeout(() => {
|
|
1950
|
-
isAutoScrolling.current = false;
|
|
1951
|
-
}, 1000);
|
|
1952
|
-
} else if (loopCount > 30) {
|
|
1953
|
-
console.error(
|
|
1954
|
-
"LOOP TIMEOUT: Last item was never measured. Stopping loop."
|
|
1955
|
-
);
|
|
1956
|
-
clearInterval(intervalId);
|
|
1957
|
-
} else {
|
|
1958
|
-
console.log("...WAITING. Height is not ready.");
|
|
1959
|
-
}
|
|
1960
|
-
}, 100);
|
|
1977
|
+
return () => clearTimeout(timeoutId);
|
|
1978
|
+
}
|
|
1961
1979
|
|
|
1962
|
-
return () =>
|
|
1963
|
-
|
|
1980
|
+
return () => {
|
|
1981
|
+
if (intervalId) clearInterval(intervalId);
|
|
1982
|
+
};
|
|
1983
|
+
}, [status, totalCount, positions]);
|
|
1964
1984
|
|
|
1965
|
-
// ---
|
|
1985
|
+
// --- USER INTERACTION ---
|
|
1986
|
+
// This effect only handles user scrolls.
|
|
1966
1987
|
useEffect(() => {
|
|
1967
1988
|
const container = containerRef.current;
|
|
1968
1989
|
if (!container) return;
|
|
1969
1990
|
|
|
1970
|
-
const
|
|
1991
|
+
const handleUserScroll = () => {
|
|
1992
|
+
const isAtBottom =
|
|
1993
|
+
container.scrollHeight -
|
|
1994
|
+
container.scrollTop -
|
|
1995
|
+
container.clientHeight <
|
|
1996
|
+
1;
|
|
1997
|
+
// If the user scrolls up from a locked or scrolling state, move to idle.
|
|
1998
|
+
if (
|
|
1999
|
+
!isAtBottom &&
|
|
2000
|
+
(status === "LOCKED_AT_BOTTOM" ||
|
|
2001
|
+
status === "MOVING_TO_BOTTOM")
|
|
2002
|
+
) {
|
|
2003
|
+
console.log(
|
|
2004
|
+
"USER_ACTION: Scrolled up. -> IDLE_NOT_AT_BOTTOM"
|
|
2005
|
+
);
|
|
2006
|
+
setStatus("IDLE_NOT_AT_BOTTOM");
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
// Update the rendered range based on scroll position.
|
|
1971
2010
|
const { scrollTop, clientHeight } = container;
|
|
1972
2011
|
let low = 0,
|
|
1973
2012
|
high = totalCount - 1;
|
|
@@ -1991,50 +2030,32 @@ function createProxyHandler<T>(
|
|
|
1991
2030
|
});
|
|
1992
2031
|
};
|
|
1993
2032
|
|
|
1994
|
-
const handleUserScroll = () => {
|
|
1995
|
-
if (isAutoScrolling.current) return;
|
|
1996
|
-
const isAtBottom =
|
|
1997
|
-
container.scrollHeight -
|
|
1998
|
-
container.scrollTop -
|
|
1999
|
-
container.clientHeight <
|
|
2000
|
-
1;
|
|
2001
|
-
if (!isAtBottom) {
|
|
2002
|
-
isLockedToBottomRef.current = false;
|
|
2003
|
-
}
|
|
2004
|
-
updateVirtualRange();
|
|
2005
|
-
};
|
|
2006
|
-
|
|
2007
2033
|
container.addEventListener("scroll", handleUserScroll, {
|
|
2008
2034
|
passive: true,
|
|
2009
2035
|
});
|
|
2010
|
-
updateVirtualRange(); // Always run to set the initial view
|
|
2011
|
-
|
|
2012
2036
|
return () =>
|
|
2013
2037
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2014
|
-
}, [totalCount, positions,
|
|
2038
|
+
}, [totalCount, positions, status]);
|
|
2015
2039
|
|
|
2016
2040
|
const scrollToBottom = useCallback(
|
|
2017
2041
|
(behavior: ScrollBehavior = "smooth") => {
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
behavior,
|
|
2023
|
-
});
|
|
2024
|
-
}
|
|
2042
|
+
console.log(
|
|
2043
|
+
"USER_ACTION: Clicked scroll to bottom button. -> MOVING_TO_BOTTOM"
|
|
2044
|
+
);
|
|
2045
|
+
setStatus("MOVING_TO_BOTTOM");
|
|
2025
2046
|
},
|
|
2026
2047
|
[]
|
|
2027
2048
|
);
|
|
2028
2049
|
|
|
2029
2050
|
const scrollToIndex = useCallback(
|
|
2030
2051
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2031
|
-
if (containerRef.current && positions[index] !== undefined) {
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
}
|
|
2052
|
+
// if (containerRef.current && positions[index] !== undefined) {
|
|
2053
|
+
// isLockedToBottomRef.current = false;
|
|
2054
|
+
// containerRef.current.scrollTo({
|
|
2055
|
+
// top: positions[index],
|
|
2056
|
+
// behavior,
|
|
2057
|
+
// });
|
|
2058
|
+
// }
|
|
2038
2059
|
},
|
|
2039
2060
|
[positions]
|
|
2040
2061
|
);
|