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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-state",
3
- "version": "0.5.365",
3
+ "version": "0.5.367",
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,19 +1809,21 @@ function createProxyHandler<T>(
1809
1809
  dependencies = [],
1810
1810
  } = options;
1811
1811
 
1812
+ // YOUR STATE MACHINE STATES
1812
1813
  type Status =
1813
- | "WAITING_FOR_ARRAY"
1814
- | "GETTING_ARRAY_HEIGHTS"
1815
- | "MOVING_TO_BOTTOM"
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>("WAITING_FOR_ARRAY");
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 MACHINE CONTROLLER ---
1881
- // This effect decides which state to transition to based on external changes.
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
- "STATE_TRANSITION: Deps changed. -> WAITING_FOR_ARRAY"
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
- "STATE_TRANSITION: New items arrived while locked. -> GETTING_ARRAY_HEIGHTS"
1903
+ "TRANSITION: New items arrived while locked -> GETTING_HEIGHTS"
1904
1904
  );
1905
- setStatus("GETTING_ARRAY_HEIGHTS");
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 MACHINE ACTIONS ---
1913
- // This effect handles the actions for each state.
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 === "WAITING_FOR_ARRAY" &&
1922
- totalCount > 0 &&
1923
- stickToBottom
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: WAITING_FOR_ARRAY -> GETTING_ARRAY_HEIGHTS"
1927
+ "ACTION (IDLE_AT_TOP): Data has arrived -> GETTING_HEIGHTS"
1927
1928
  );
1928
- setStatus("GETTING_ARRAY_HEIGHTS");
1929
- } else if (status === "GETTING_ARRAY_HEIGHTS") {
1929
+ setStatus("GETTING_HEIGHTS");
1930
+ } else if (status === "GETTING_HEIGHTS") {
1930
1931
  console.log(
1931
- "ACTION: GETTING_ARRAY_HEIGHTS. Setting range and starting loop."
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. -> MOVING_TO_BOTTOM"
1951
+ "ACTION (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
1951
1952
  );
1952
- setStatus("MOVING_TO_BOTTOM");
1953
+ setStatus("SCROLLING_TO_BOTTOM");
1953
1954
  }
1954
1955
  }, 100);
1955
- } else if (status === "MOVING_TO_BOTTOM") {
1956
- console.log("ACTION: MOVING_TO_BOTTOM. Executing scroll.");
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. -> LOCKED_AT_BOTTOM"
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
- 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");
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
- // Update the rendered range based on scroll position.
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
- (behavior: ScrollBehavior = "smooth") => {
2042
- console.log(
2043
- "USER_ACTION: Clicked scroll to bottom button. -> MOVING_TO_BOTTOM"
2044
- );
2045
- setStatus("MOVING_TO_BOTTOM");
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
- // if (containerRef.current && positions[index] !== undefined) {
2053
- // isLockedToBottomRef.current = false;
2054
- // containerRef.current.scrollTo({
2055
- // top: positions[index],
2056
- // behavior,
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
  );