cogsbox-state 0.5.383 → 0.5.385

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.383",
3
+ "version": "0.5.385",
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
@@ -1817,7 +1817,7 @@ function createProxyHandler<T>(
1817
1817
  | "LOCKED_AT_BOTTOM"
1818
1818
  | "IDLE_NOT_AT_BOTTOM";
1819
1819
 
1820
- // Refs and State
1820
+ const shouldNotScroll = useRef(false);
1821
1821
  const containerRef = useRef<HTMLDivElement | null>(null);
1822
1822
  const [range, setRange] = useState({
1823
1823
  startIndex: 0,
@@ -1827,15 +1827,9 @@ function createProxyHandler<T>(
1827
1827
  const isProgrammaticScroll = useRef(false);
1828
1828
  const prevTotalCountRef = useRef(0);
1829
1829
  const prevDepsRef = useRef(dependencies);
1830
+ const lastUpdateAtScrollTop = useRef(0);
1830
1831
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1831
- // CHANGE: Add a ref to store the scroll position BEFORE new items are added.
1832
- // This is the key to preventing the jump when scrolled up.
1833
- const scrollAnchorRef = useRef<{
1834
- scrollTop: number;
1835
- scrollHeight: number;
1836
- } | null>(null);
1837
-
1838
- // ... (Your existing useEffect for shadow state and useMemo for data are fine) ...
1832
+
1839
1833
  useEffect(() => {
1840
1834
  const unsubscribe = getGlobalStore
1841
1835
  .getState()
@@ -1872,6 +1866,7 @@ function createProxyHandler<T>(
1872
1866
  shadowUpdateTrigger,
1873
1867
  ]);
1874
1868
 
1869
+ // THIS IS THE FULL, NON-PLACEHOLDER FUNCTION
1875
1870
  const virtualState = useMemo(() => {
1876
1871
  const start = Math.max(0, range.startIndex);
1877
1872
  const end = Math.min(totalCount, range.endIndex);
@@ -1886,28 +1881,22 @@ function createProxyHandler<T>(
1886
1881
  });
1887
1882
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1888
1883
 
1889
- // --- 1. STATE CONTROLLER (REVISED LOGIC) ---
1884
+ // --- 1. STATE CONTROLLER ---
1885
+ // This effect decides which state to transition TO.
1890
1886
  useLayoutEffect(() => {
1891
- const container = containerRef.current;
1892
- if (!container) return;
1893
-
1894
- const hasNewItems = totalCount > prevTotalCountRef.current;
1895
1887
  const depsChanged = !isDeepEqual(
1896
1888
  dependencies,
1897
1889
  prevDepsRef.current
1898
1890
  );
1891
+ const hasNewItems = totalCount > prevTotalCountRef.current;
1899
1892
 
1900
- // Condition 1: Hard Reset.
1901
- // This happens when you load a completely new list (e.g., switch chats).
1902
1893
  if (depsChanged) {
1903
- console.log(
1904
- "TRANSITION (Hard Reset): Deps changed -> IDLE_AT_TOP"
1905
- );
1894
+ console.log("TRANSITION: Deps changed -> IDLE_AT_TOP");
1906
1895
  setStatus("IDLE_AT_TOP");
1907
- container.scrollTo({ top: 0 }); // Reset scroll position
1896
+ return; // Stop here, let the next effect handle the action for the new state.
1908
1897
  }
1909
- // Condition 2: New items arrive while we're locked to the bottom.
1910
- else if (
1898
+
1899
+ if (
1911
1900
  hasNewItems &&
1912
1901
  status === "LOCKED_AT_BOTTOM" &&
1913
1902
  stickToBottom
@@ -1917,29 +1906,14 @@ function createProxyHandler<T>(
1917
1906
  );
1918
1907
  setStatus("GETTING_HEIGHTS");
1919
1908
  }
1920
- // CHANGE: Condition 3: New items arrive while we are scrolled up.
1921
- // This is the "scroll anchoring" logic that prevents the jump.
1922
- else if (hasNewItems && scrollAnchorRef.current) {
1923
- console.log(
1924
- "ACTION: Maintaining scroll position after new items added."
1925
- );
1926
- // We adjust the scroll position by the amount of height that was just added.
1927
- container.scrollTop =
1928
- scrollAnchorRef.current.scrollTop +
1929
- (container.scrollHeight -
1930
- scrollAnchorRef.current.scrollHeight);
1931
- }
1932
1909
 
1933
- // Finally, update the refs for the next render.
1934
1910
  prevTotalCountRef.current = totalCount;
1935
1911
  prevDepsRef.current = dependencies;
1936
- // Clear the anchor after using it. It will be re-set by the user scroll handler.
1937
- scrollAnchorRef.current = null;
1938
- }, [totalCount, ...dependencies]); // This dependency array is correct.
1912
+ }, [totalCount, ...dependencies]);
1939
1913
 
1940
- // --- 2. STATE ACTION HANDLER (Mostly Unchanged) ---
1914
+ // --- 2. STATE ACTION HANDLER ---
1915
+ // This effect performs the ACTION for the current state.
1941
1916
  useLayoutEffect(() => {
1942
- // ... (This effect's logic for GETTING_HEIGHTS and SCROLLING_TO_BOTTOM is correct and can remain the same)
1943
1917
  const container = containerRef.current;
1944
1918
  if (!container) return;
1945
1919
 
@@ -1950,12 +1924,15 @@ function createProxyHandler<T>(
1950
1924
  stickToBottom &&
1951
1925
  totalCount > 0
1952
1926
  ) {
1927
+ // If we just loaded a new chat, start the process.
1953
1928
  console.log(
1954
- "ACTION (IDLE_AT_TOP): Data arrived -> GETTING_HEIGHTS"
1929
+ "ACTION (IDLE_AT_TOP): Data has arrived -> GETTING_HEIGHTS"
1955
1930
  );
1956
1931
  setStatus("GETTING_HEIGHTS");
1957
1932
  } else if (status === "GETTING_HEIGHTS") {
1958
- console.log("ACTION (GETTING_HEIGHTS): Setting range to end");
1933
+ console.log(
1934
+ "ACTION (GETTING_HEIGHTS): Setting range to end and starting loop."
1935
+ );
1959
1936
  setRange({
1960
1937
  startIndex: Math.max(0, totalCount - 10 - overscan),
1961
1938
  endIndex: totalCount,
@@ -1972,10 +1949,13 @@ function createProxyHandler<T>(
1972
1949
 
1973
1950
  if (lastItemHeight > 0) {
1974
1951
  clearInterval(intervalId);
1975
- console.log(
1976
- "ACTION (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
1977
- );
1978
- setStatus("SCROLLING_TO_BOTTOM");
1952
+ if (!shouldNotScroll.current) {
1953
+ console.log(
1954
+ "ACTION (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
1955
+ );
1956
+
1957
+ setStatus("SCROLLING_TO_BOTTOM");
1958
+ }
1979
1959
  }
1980
1960
  }, 100);
1981
1961
  } else if (status === "SCROLLING_TO_BOTTOM") {
@@ -1983,6 +1963,7 @@ function createProxyHandler<T>(
1983
1963
  "ACTION (SCROLLING_TO_BOTTOM): Executing scroll."
1984
1964
  );
1985
1965
  isProgrammaticScroll.current = true;
1966
+ // Use 'auto' for initial load, 'smooth' for new messages.
1986
1967
  const scrollBehavior =
1987
1968
  prevTotalCountRef.current === 0 ? "auto" : "smooth";
1988
1969
 
@@ -1993,11 +1974,16 @@ function createProxyHandler<T>(
1993
1974
 
1994
1975
  const timeoutId = setTimeout(
1995
1976
  () => {
1977
+ console.log(
1978
+ "ACTION (SCROLLING_TO_BOTTOM): Scroll finished -> LOCKED_AT_BOTTOM"
1979
+ );
1996
1980
  isProgrammaticScroll.current = false;
1981
+ shouldNotScroll.current = false;
1997
1982
  setStatus("LOCKED_AT_BOTTOM");
1998
1983
  },
1999
1984
  scrollBehavior === "smooth" ? 500 : 50
2000
1985
  );
1986
+
2001
1987
  return () => clearTimeout(timeoutId);
2002
1988
  }
2003
1989
 
@@ -2006,70 +1992,72 @@ function createProxyHandler<T>(
2006
1992
  };
2007
1993
  }, [status, totalCount, positions]);
2008
1994
 
2009
- // --- 3. USER INTERACTION & RANGE UPDATER (REVISED) ---
2010
1995
  useEffect(() => {
2011
1996
  const container = containerRef.current;
2012
1997
  if (!container) return;
2013
1998
 
2014
- // CHANGE: Add a buffer to prevent jittering at the bottom.
2015
- const bottomLockThreshold = 10; // 10px buffer
1999
+ const scrollThreshold = itemHeight;
2016
2000
 
2017
2001
  const handleUserScroll = () => {
2018
2002
  if (isProgrammaticScroll.current) {
2019
2003
  return;
2020
2004
  }
2021
2005
 
2022
- const { scrollTop, clientHeight, scrollHeight } = container;
2023
-
2024
- // CHANGE: Before the next state update, save the current scroll positions.
2025
- // This 'anchors' our view, so the State Controller knows where we were.
2026
- scrollAnchorRef.current = { scrollTop, scrollHeight };
2027
-
2028
- // Part 1: Update the state machine with a tolerance
2006
+ const { scrollTop, scrollHeight, clientHeight } = container;
2007
+ console.log(
2008
+ "scrollTop, scrollHeight, clientHeight ",
2009
+ scrollTop,
2010
+ scrollHeight,
2011
+ clientHeight
2012
+ );
2013
+ // --- START OF MINIMAL FIX ---
2014
+ // This block is the only thing added. It updates the master 'status'.
2015
+ // This is the critical missing piece that tells the component if it should auto-scroll.
2016
+ // A 10px buffer prevents jittering when you are scrolled to the very bottom.
2029
2017
  const isAtBottom =
2030
- scrollHeight - scrollTop - clientHeight <
2031
- bottomLockThreshold;
2032
-
2018
+ scrollHeight - scrollTop - clientHeight < 10;
2019
+ console.log("isAtBottom", isAtBottom);
2033
2020
  if (isAtBottom) {
2021
+ // If we scroll back to the bottom, re-lock.
2034
2022
  if (status !== "LOCKED_AT_BOTTOM") {
2035
- console.log(
2036
- "SCROLL EVENT: Reached bottom -> LOCKED_AT_BOTTOM"
2037
- );
2038
2023
  setStatus("LOCKED_AT_BOTTOM");
2039
2024
  }
2040
2025
  } else {
2026
+ // If we have scrolled up, unlock from the bottom.
2041
2027
  if (status !== "IDLE_NOT_AT_BOTTOM") {
2042
- console.log(
2043
- "SCROLL EVENT: Scrolled up -> IDLE_NOT_AT_BOTTOM"
2044
- );
2045
2028
  setStatus("IDLE_NOT_AT_BOTTOM");
2046
2029
  }
2047
2030
  }
2031
+ // --- END OF MINIMAL FIX ---
2032
+
2033
+ // The rest is YOUR original, working logic for updating the visible items.
2034
+ if (
2035
+ Math.abs(scrollTop - lastUpdateAtScrollTop.current) <
2036
+ scrollThreshold
2037
+ ) {
2038
+ return;
2039
+ }
2048
2040
 
2049
- // Part 2: Efficiently update the rendered range (this logic is good)
2041
+ console.log(
2042
+ `Threshold passed at ${scrollTop}px. Recalculating range...`
2043
+ );
2044
+
2045
+ // NOW we do the expensive work.
2050
2046
  let high = totalCount - 1;
2051
2047
  let low = 0;
2052
- let potentialTopIndex = 0;
2048
+ let topItemIndex = 0;
2053
2049
  while (low <= high) {
2054
2050
  const mid = Math.floor((low + high) / 2);
2055
2051
  if (positions[mid]! < scrollTop) {
2056
- potentialTopIndex = mid;
2052
+ topItemIndex = mid;
2057
2053
  low = mid + 1;
2058
2054
  } else {
2059
2055
  high = mid - 1;
2060
2056
  }
2061
2057
  }
2062
2058
 
2063
- const potentialStartIndex = Math.max(
2064
- 0,
2065
- potentialTopIndex - overscan
2066
- );
2067
-
2068
- if (potentialStartIndex === range.startIndex) {
2069
- return;
2070
- }
2071
-
2072
- let endIndex = potentialStartIndex;
2059
+ const startIndex = Math.max(0, topItemIndex - overscan);
2060
+ let endIndex = startIndex;
2073
2061
  const visibleEnd = scrollTop + clientHeight;
2074
2062
  while (
2075
2063
  endIndex < totalCount &&
@@ -2078,13 +2066,13 @@ function createProxyHandler<T>(
2078
2066
  endIndex++;
2079
2067
  }
2080
2068
 
2081
- console.log(
2082
- `Index changed from ${range.startIndex} to ${potentialStartIndex}. Updating range.`
2083
- );
2084
2069
  setRange({
2085
- startIndex: potentialStartIndex,
2070
+ startIndex,
2086
2071
  endIndex: Math.min(totalCount, endIndex + overscan),
2087
2072
  });
2073
+
2074
+ // Finally, we record that we did the work at THIS scroll position.
2075
+ lastUpdateAtScrollTop.current = scrollTop;
2088
2076
  };
2089
2077
 
2090
2078
  container.addEventListener("scroll", handleUserScroll, {
@@ -2092,16 +2080,14 @@ function createProxyHandler<T>(
2092
2080
  });
2093
2081
  return () =>
2094
2082
  container.removeEventListener("scroll", handleUserScroll);
2095
- }, [totalCount, positions, status, range.startIndex]);
2083
+ }, [totalCount, positions, itemHeight, overscan, status]);
2096
2084
 
2097
- // --- (The rest of your code: scrollToBottom, scrollToIndex, virtualizerProps is fine) ---
2098
2085
  const scrollToBottom = useCallback(() => {
2099
2086
  console.log(
2100
2087
  "USER ACTION: Clicked scroll button -> SCROLLING_TO_BOTTOM"
2101
2088
  );
2102
- if (totalCount === 0) return;
2103
2089
  setStatus("SCROLLING_TO_BOTTOM");
2104
- }, [totalCount]);
2090
+ }, []);
2105
2091
 
2106
2092
  const scrollToIndex = useCallback(
2107
2093
  (index: number, behavior: ScrollBehavior = "smooth") => {