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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-state",
3
- "version": "0.5.362",
3
+ "version": "0.5.365",
4
4
  "description": "React state management library with form controls and server sync",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
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 isLockedToBottomRef = useRef(stickToBottom);
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
- // --- PHASE 1: Detect auto-scroll need and SET THE RANGE ---
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
- if (isLockedToBottomRef.current && hasNewItems) {
1888
+
1889
+ if (depsChanged) {
1877
1890
  console.log(
1878
- "PHASE 1: Auto-scroll needed. Setting range to render the last item."
1891
+ "STATE_TRANSITION: Deps changed. -> WAITING_FOR_ARRAY"
1879
1892
  );
1880
- setRange({
1881
- startIndex: Math.max(0, totalCount - 10 - overscan),
1882
- endIndex: totalCount,
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
- }, [totalCount]);
1909
+ prevDepsRef.current = dependencies;
1910
+ }, [totalCount, ...dependencies]);
1887
1911
 
1888
- // --- PHASE 2: Wait for measurement and SCROLL ---
1912
+ // --- STATE MACHINE ACTIONS ---
1913
+ // This effect handles the actions for each state.
1889
1914
  useLayoutEffect(() => {
1890
1915
  const container = containerRef.current;
1891
- const isRangeAtEnd =
1892
- range.endIndex === totalCount && totalCount > 0;
1916
+ if (!container) return;
1917
+
1918
+ let intervalId: NodeJS.Timeout | undefined;
1893
1919
 
1894
1920
  if (
1895
- !container ||
1896
- !isLockedToBottomRef.current ||
1897
- !isRangeAtEnd
1921
+ status === "WAITING_FOR_ARRAY" &&
1922
+ totalCount > 0 &&
1923
+ stickToBottom
1898
1924
  ) {
1899
- return;
1900
- }
1901
-
1902
- console.log(
1903
- "PHASE 2: Range is at the end. Starting the measurement loop."
1904
- );
1905
- let loopCount = 0;
1906
- const intervalId = setInterval(() => {
1907
- loopCount++;
1908
- console.log(`LOOP ${loopCount}: Checking last item...`);
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
- const lastItemIndex = totalCount - 1;
1911
- const shadowArray =
1912
- getGlobalStore
1913
- .getState()
1914
- .getShadowMetadata(stateKey, path) || [];
1915
- const lastItemHeight =
1916
- shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
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
- if (lastItemHeight > 0) {
1919
- console.log(
1920
- `%cSUCCESS: Last item height is ${lastItemHeight}. Scrolling now.`,
1921
- "color: green; font-weight: bold;"
1922
- );
1923
- clearInterval(intervalId);
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
- isAutoScrolling.current = true;
1926
- container.scrollTo({
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 () => clearInterval(intervalId);
1944
- }, [range.endIndex, totalCount, positions]);
1980
+ return () => {
1981
+ if (intervalId) clearInterval(intervalId);
1982
+ };
1983
+ }, [status, totalCount, positions]);
1945
1984
 
1946
- // --- PHASE 3: Handle User Interaction and Resets ---
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
- console.log(
1952
- "DEPENDENCY CHANGE: Resetting scroll lock and initial view."
1953
- );
1954
- isLockedToBottomRef.current = stickToBottom;
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
- const updateVirtualRange = () => {
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
- const newEndIndex = Math.min(totalCount, endIndex + overscan);
1975
-
1976
- // --- LOGGING ADDED HERE ---
1977
- console.log(
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
- }, [...dependencies, totalCount, positions]); // <--- THE FIX
2038
+ }, [totalCount, positions, status]);
2005
2039
 
2006
2040
  const scrollToBottom = useCallback(
2007
2041
  (behavior: ScrollBehavior = "smooth") => {
2008
- if (containerRef.current) {
2009
- isLockedToBottomRef.current = true;
2010
- containerRef.current.scrollTo({
2011
- top: containerRef.current.scrollHeight,
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
- isLockedToBottomRef.current = false;
2023
- containerRef.current.scrollTo({
2024
- top: positions[index],
2025
- behavior,
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
  );