cogsbox-state 0.5.388 → 0.5.389

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.388",
3
+ "version": "0.5.389",
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
@@ -1885,26 +1885,37 @@ function createProxyHandler<T>(
1885
1885
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1886
1886
 
1887
1887
  // --- 1. STATE CONTROLLER ---
1888
- // This effect decides which state to transition TO.
1888
+ // --- 1. STATE CONTROLLER (CORRECTED) ---
1889
1889
  useLayoutEffect(() => {
1890
- // THIS `IF` BLOCK IS THE NEW LOGIC
1891
1890
  const container = containerRef.current;
1891
+ if (!container) return;
1892
+
1892
1893
  const hasNewItems = totalCount > prevTotalCountRef.current;
1893
1894
 
1894
- if (hasNewItems && scrollAnchorRef.current && container) {
1895
- // User was scrolled up, new items arrived. Restore the scroll position.
1895
+ // THIS IS THE NEW, IMPORTANT LOGIC FOR ANCHORING
1896
+ if (hasNewItems && scrollAnchorRef.current) {
1896
1897
  const { top: prevScrollTop, height: prevScrollHeight } =
1897
1898
  scrollAnchorRef.current;
1899
+
1900
+ // This is the key: Tell the app we are about to programmatically scroll.
1901
+ isProgrammaticScroll.current = true;
1902
+
1903
+ // Restore the scroll position.
1898
1904
  container.scrollTop =
1899
1905
  prevScrollTop + (container.scrollHeight - prevScrollHeight);
1900
-
1901
- // IMPORTANT: Clear the anchor after using it.
1902
- scrollAnchorRef.current = null;
1903
1906
  console.log(
1904
1907
  `ANCHOR RESTORED to scrollTop: ${container.scrollTop}`
1905
1908
  );
1909
+
1910
+ // IMPORTANT: After the scroll, allow user scroll events again.
1911
+ // Use a timeout to ensure this runs after the scroll event has fired and been ignored.
1912
+ setTimeout(() => {
1913
+ isProgrammaticScroll.current = false;
1914
+ }, 100);
1915
+
1916
+ scrollAnchorRef.current = null; // Clear the anchor after using it.
1906
1917
  }
1907
- // YOUR EXISTING LOGIC CONTINUES BELOW in an else-if structure
1918
+ // YOUR ORIGINAL LOGIC CONTINUES UNCHANGED IN THE `ELSE` BLOCK
1908
1919
  else {
1909
1920
  const depsChanged = !isDeepEqual(
1910
1921
  dependencies,
@@ -1914,7 +1925,7 @@ function createProxyHandler<T>(
1914
1925
  if (depsChanged) {
1915
1926
  console.log("TRANSITION: Deps changed -> IDLE_AT_TOP");
1916
1927
  setStatus("IDLE_AT_TOP");
1917
- return; // Stop here, let the next effect handle the action for the new state.
1928
+ return;
1918
1929
  }
1919
1930
 
1920
1931
  if (
@@ -2021,35 +2032,35 @@ function createProxyHandler<T>(
2021
2032
  const scrollThreshold = itemHeight;
2022
2033
 
2023
2034
  const handleUserScroll = () => {
2035
+ // This guard is now critical. It will ignore our anchor restoration scroll.
2024
2036
  if (isProgrammaticScroll.current) {
2025
2037
  return;
2026
2038
  }
2027
2039
 
2028
2040
  const { scrollTop, scrollHeight, clientHeight } = container;
2029
2041
 
2042
+ // Part 1: Handle Status and Anchoring
2030
2043
  const isAtBottom =
2031
2044
  scrollHeight - scrollTop - clientHeight < 10;
2032
2045
 
2033
2046
  if (isAtBottom) {
2034
2047
  if (status !== "LOCKED_AT_BOTTOM") {
2035
2048
  setStatus("LOCKED_AT_BOTTOM");
2036
- // We are at the bottom, so we don't need an anchor.
2037
- scrollAnchorRef.current = null;
2038
2049
  }
2050
+ // If we are at the bottom, there is no anchor needed.
2051
+ scrollAnchorRef.current = null;
2039
2052
  } else {
2040
2053
  if (status !== "IDLE_NOT_AT_BOTTOM") {
2041
2054
  setStatus("IDLE_NOT_AT_BOTTOM");
2042
- // THE USER SCROLLED UP. SET THE ANCHOR.
2043
- scrollAnchorRef.current = {
2044
- top: scrollTop,
2045
- height: scrollHeight,
2046
- };
2047
- console.log(`ANCHOR SET at scrollTop: ${scrollTop}`);
2048
2055
  }
2056
+ // User is scrolled up. Continuously update the anchor with their latest position.
2057
+ scrollAnchorRef.current = {
2058
+ top: scrollTop,
2059
+ height: scrollHeight,
2060
+ };
2049
2061
  }
2050
- // --- END OF MINIMAL FIX ---
2051
2062
 
2052
- // The rest is YOUR original, working logic for updating the visible items.
2063
+ // Part 2: YOUR original, working logic for updating the visible range.
2053
2064
  if (
2054
2065
  Math.abs(scrollTop - lastUpdateAtScrollTop.current) <
2055
2066
  scrollThreshold
@@ -2061,7 +2072,7 @@ function createProxyHandler<T>(
2061
2072
  `Threshold passed at ${scrollTop}px. Recalculating range...`
2062
2073
  );
2063
2074
 
2064
- // NOW we do the expensive work.
2075
+ // ... your logic to find startIndex and endIndex ...
2065
2076
  let high = totalCount - 1;
2066
2077
  let low = 0;
2067
2078
  let topItemIndex = 0;
@@ -2090,7 +2101,6 @@ function createProxyHandler<T>(
2090
2101
  endIndex: Math.min(totalCount, endIndex + overscan),
2091
2102
  });
2092
2103
 
2093
- // Finally, we record that we did the work at THIS scroll position.
2094
2104
  lastUpdateAtScrollTop.current = scrollTop;
2095
2105
  };
2096
2106