cogsbox-state 0.5.362 → 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 +639 -625
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +133 -102
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1809,14 +1809,21 @@ 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);
|
|
1826
|
+
const prevDepsRef = useRef(dependencies);
|
|
1820
1827
|
|
|
1821
1828
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1822
1829
|
|
|
@@ -1870,90 +1877,136 @@ function createProxyHandler<T>(
|
|
|
1870
1877
|
});
|
|
1871
1878
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1872
1879
|
|
|
1873
|
-
// ---
|
|
1880
|
+
// --- STATE MACHINE CONTROLLER ---
|
|
1881
|
+
// This effect decides which state to transition to based on external changes.
|
|
1874
1882
|
useLayoutEffect(() => {
|
|
1883
|
+
const depsChanged = !isDeepEqual(
|
|
1884
|
+
dependencies,
|
|
1885
|
+
prevDepsRef.current
|
|
1886
|
+
);
|
|
1875
1887
|
const hasNewItems = totalCount > prevTotalCountRef.current;
|
|
1876
|
-
|
|
1888
|
+
|
|
1889
|
+
if (depsChanged) {
|
|
1877
1890
|
console.log(
|
|
1878
|
-
"
|
|
1891
|
+
"STATE_TRANSITION: Deps changed. -> WAITING_FOR_ARRAY"
|
|
1879
1892
|
);
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1893
|
+
setStatus("WAITING_FOR_ARRAY");
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
if (
|
|
1898
|
+
hasNewItems &&
|
|
1899
|
+
status === "LOCKED_AT_BOTTOM" &&
|
|
1900
|
+
stickToBottom
|
|
1901
|
+
) {
|
|
1902
|
+
console.log(
|
|
1903
|
+
"STATE_TRANSITION: New items arrived while locked. -> GETTING_ARRAY_HEIGHTS"
|
|
1904
|
+
);
|
|
1905
|
+
setStatus("GETTING_ARRAY_HEIGHTS");
|
|
1884
1906
|
}
|
|
1907
|
+
|
|
1885
1908
|
prevTotalCountRef.current = totalCount;
|
|
1886
|
-
|
|
1909
|
+
prevDepsRef.current = dependencies;
|
|
1910
|
+
}, [totalCount, ...dependencies]);
|
|
1887
1911
|
|
|
1888
|
-
// ---
|
|
1912
|
+
// --- STATE MACHINE ACTIONS ---
|
|
1913
|
+
// This effect handles the actions for each state.
|
|
1889
1914
|
useLayoutEffect(() => {
|
|
1890
1915
|
const container = containerRef.current;
|
|
1891
|
-
|
|
1892
|
-
|
|
1916
|
+
if (!container) return;
|
|
1917
|
+
|
|
1918
|
+
let intervalId: NodeJS.Timeout | undefined;
|
|
1893
1919
|
|
|
1894
1920
|
if (
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1921
|
+
status === "WAITING_FOR_ARRAY" &&
|
|
1922
|
+
totalCount > 0 &&
|
|
1923
|
+
stickToBottom
|
|
1898
1924
|
) {
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
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
|
+
});
|
|
1909
1937
|
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
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
|
+
});
|
|
1917
1964
|
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
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
|
+
);
|
|
1924
1976
|
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
top: container.scrollHeight,
|
|
1928
|
-
behavior: "smooth",
|
|
1929
|
-
});
|
|
1930
|
-
setTimeout(() => {
|
|
1931
|
-
isAutoScrolling.current = false;
|
|
1932
|
-
}, 1000);
|
|
1933
|
-
} else if (loopCount > 20) {
|
|
1934
|
-
console.error(
|
|
1935
|
-
"LOOP TIMEOUT: Last item was never measured. Stopping loop."
|
|
1936
|
-
);
|
|
1937
|
-
clearInterval(intervalId);
|
|
1938
|
-
} else {
|
|
1939
|
-
console.log("...WAITING. Height is not ready.");
|
|
1940
|
-
}
|
|
1941
|
-
}, 100);
|
|
1977
|
+
return () => clearTimeout(timeoutId);
|
|
1978
|
+
}
|
|
1942
1979
|
|
|
1943
|
-
return () =>
|
|
1944
|
-
|
|
1980
|
+
return () => {
|
|
1981
|
+
if (intervalId) clearInterval(intervalId);
|
|
1982
|
+
};
|
|
1983
|
+
}, [status, totalCount, positions]);
|
|
1945
1984
|
|
|
1946
|
-
// ---
|
|
1985
|
+
// --- USER INTERACTION ---
|
|
1986
|
+
// This effect only handles user scrolls.
|
|
1947
1987
|
useEffect(() => {
|
|
1948
1988
|
const container = containerRef.current;
|
|
1949
1989
|
if (!container) return;
|
|
1950
1990
|
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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
|
+
}
|
|
1955
2008
|
|
|
1956
|
-
|
|
2009
|
+
// Update the rendered range based on scroll position.
|
|
1957
2010
|
const { scrollTop, clientHeight } = container;
|
|
1958
2011
|
let low = 0,
|
|
1959
2012
|
high = totalCount - 1;
|
|
@@ -1971,60 +2024,38 @@ function createProxyHandler<T>(
|
|
|
1971
2024
|
) {
|
|
1972
2025
|
endIndex++;
|
|
1973
2026
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
`RANGE UPDATE: Start: ${startIndex}, End: ${newEndIndex}, Total: ${totalCount}`
|
|
1979
|
-
);
|
|
1980
|
-
|
|
1981
|
-
setRange({ startIndex, endIndex: newEndIndex });
|
|
1982
|
-
};
|
|
1983
|
-
|
|
1984
|
-
const handleUserScroll = () => {
|
|
1985
|
-
if (isAutoScrolling.current) return;
|
|
1986
|
-
const isAtBottom =
|
|
1987
|
-
container.scrollHeight -
|
|
1988
|
-
container.scrollTop -
|
|
1989
|
-
container.clientHeight <
|
|
1990
|
-
1;
|
|
1991
|
-
if (!isAtBottom) {
|
|
1992
|
-
isLockedToBottomRef.current = false;
|
|
1993
|
-
}
|
|
1994
|
-
updateVirtualRange();
|
|
2027
|
+
setRange({
|
|
2028
|
+
startIndex,
|
|
2029
|
+
endIndex: Math.min(totalCount, endIndex + overscan),
|
|
2030
|
+
});
|
|
1995
2031
|
};
|
|
1996
2032
|
|
|
1997
2033
|
container.addEventListener("scroll", handleUserScroll, {
|
|
1998
2034
|
passive: true,
|
|
1999
2035
|
});
|
|
2000
|
-
updateVirtualRange();
|
|
2001
|
-
|
|
2002
2036
|
return () =>
|
|
2003
2037
|
container.removeEventListener("scroll", handleUserScroll);
|
|
2004
|
-
}, [
|
|
2038
|
+
}, [totalCount, positions, status]);
|
|
2005
2039
|
|
|
2006
2040
|
const scrollToBottom = useCallback(
|
|
2007
2041
|
(behavior: ScrollBehavior = "smooth") => {
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
behavior,
|
|
2013
|
-
});
|
|
2014
|
-
}
|
|
2042
|
+
console.log(
|
|
2043
|
+
"USER_ACTION: Clicked scroll to bottom button. -> MOVING_TO_BOTTOM"
|
|
2044
|
+
);
|
|
2045
|
+
setStatus("MOVING_TO_BOTTOM");
|
|
2015
2046
|
},
|
|
2016
2047
|
[]
|
|
2017
2048
|
);
|
|
2018
2049
|
|
|
2019
2050
|
const scrollToIndex = useCallback(
|
|
2020
2051
|
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
2021
|
-
if (containerRef.current && positions[index] !== undefined) {
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
}
|
|
2052
|
+
// if (containerRef.current && positions[index] !== undefined) {
|
|
2053
|
+
// isLockedToBottomRef.current = false;
|
|
2054
|
+
// containerRef.current.scrollTo({
|
|
2055
|
+
// top: positions[index],
|
|
2056
|
+
// behavior,
|
|
2057
|
+
// });
|
|
2058
|
+
// }
|
|
2028
2059
|
},
|
|
2029
2060
|
[positions]
|
|
2030
2061
|
);
|