cogsbox-state 0.5.345 → 0.5.346

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.345",
3
+ "version": "0.5.346",
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
@@ -1807,29 +1807,12 @@ function createProxyHandler<T>(
1807
1807
  overscan = 6,
1808
1808
  stickToBottom = false,
1809
1809
  } = options;
1810
- const sourceArray = getGlobalStore().getNestedState(
1811
- stateKey,
1812
- path
1813
- ) as any[];
1810
+
1814
1811
  const containerRef = useRef<HTMLDivElement | null>(null);
1815
- // We'll set the range to the end first, then let an effect handle the scroll.
1816
- const initialRange = () => {
1817
- const sourceArray = getGlobalStore
1818
- .getState()
1819
- .getNestedState(stateKey, path) as any[];
1820
- if (stickToBottom) {
1821
- const visibleCount = 10; // A reasonable guess for initial render
1822
- return {
1823
- startIndex: Math.max(
1824
- 0,
1825
- sourceArray.length - visibleCount - overscan
1826
- ),
1827
- endIndex: sourceArray.length,
1828
- };
1829
- }
1830
- return { startIndex: 0, endIndex: 10 };
1831
- };
1832
- const [range, setRange] = useState(initialRange);
1812
+ const [range, setRange] = useState({
1813
+ startIndex: 0,
1814
+ endIndex: 10,
1815
+ });
1833
1816
  const isLockedToBottomRef = useRef(stickToBottom);
1834
1817
 
1835
1818
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
@@ -1843,6 +1826,10 @@ function createProxyHandler<T>(
1843
1826
  return unsubscribe;
1844
1827
  }, [stateKey]);
1845
1828
 
1829
+ const sourceArray = getGlobalStore().getNestedState(
1830
+ stateKey,
1831
+ path
1832
+ ) as any[];
1846
1833
  const totalCount = sourceArray.length;
1847
1834
 
1848
1835
  const { totalHeight, positions } = useMemo(() => {
@@ -1880,71 +1867,85 @@ function createProxyHandler<T>(
1880
1867
  });
1881
1868
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1882
1869
 
1883
- // This is the implementation of YOUR ALGORITHM.
1870
+ // --- YOUR ALGORITHM IMPLEMENTED ---
1871
+ // This effect is the entry point. It triggers when new items are added.
1884
1872
  useLayoutEffect(() => {
1885
1873
  const container = containerRef.current;
1874
+ // Only run if we have new items and are supposed to be at the bottom.
1886
1875
  if (
1887
1876
  !container ||
1888
- !stickToBottom ||
1889
- !isLockedToBottomRef.current
1877
+ !isLockedToBottomRef.current ||
1878
+ totalCount === 0
1890
1879
  ) {
1891
1880
  return;
1892
1881
  }
1893
1882
 
1894
- // STEP 1: Check if the last item is measured. This is our "ready" signal.
1895
- const lastItemIndex = totalCount - 1;
1896
- const shadowArray =
1897
- getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
1898
- [];
1899
- const lastItemIsMeasured =
1900
- lastItemIndex >= 0 &&
1901
- shadowArray[lastItemIndex]?.virtualizer?.itemHeight > 0;
1902
-
1903
- // STEP 2: If it's measured, we know totalHeight is correct. We can now scroll.
1904
- if (lastItemIsMeasured || totalCount === 0) {
1905
- // A timeout is essential for 'smooth' to work reliably after a render.
1906
- const scrollTimeout = setTimeout(() => {
1883
+ // STEP 1: Set the range to the end so the last items are rendered.
1884
+ console.log("ALGORITHM: Starting...");
1885
+ const visibleCount = 10;
1886
+ setRange({
1887
+ startIndex: Math.max(0, totalCount - visibleCount - overscan),
1888
+ endIndex: totalCount,
1889
+ });
1890
+
1891
+ // STEP 2: Start the LOOP.
1892
+ console.log(
1893
+ "ALGORITHM: Starting LOOP to wait for measurement."
1894
+ );
1895
+ let loopCount = 0;
1896
+ const intervalId = setInterval(() => {
1897
+ loopCount++;
1898
+ console.log(`LOOP ${loopCount}: Checking last item...`);
1899
+
1900
+ // The Check: Get the last item's height FROM THE SHADOW OBJECT.
1901
+ const lastItemIndex = totalCount - 1;
1902
+ const shadowArray =
1903
+ getGlobalStore
1904
+ .getState()
1905
+ .getShadowMetadata(stateKey, path) || [];
1906
+ const lastItemHeight =
1907
+ shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
1908
+
1909
+ if (lastItemHeight > 0) {
1910
+ // EXIT CONDITION MET
1911
+ console.log(
1912
+ `%cSUCCESS: Last item height is ${lastItemHeight}. Scrolling now.`,
1913
+ "color: green; font-weight: bold;"
1914
+ );
1915
+ clearInterval(intervalId); // Stop the loop.
1916
+
1917
+ // STEP 3: Scroll.
1907
1918
  container.scrollTo({
1908
1919
  top: container.scrollHeight,
1909
1920
  behavior: "smooth",
1910
1921
  });
1911
- }, 50); // A small buffer is safer than 0ms.
1922
+ } else {
1923
+ console.log("...WAITING. Height is not ready.");
1924
+ if (loopCount > 20) {
1925
+ // Safety break to prevent infinite loops
1926
+ console.error(
1927
+ "LOOP TIMEOUT: Last item was never measured. Stopping loop."
1928
+ );
1929
+ clearInterval(intervalId);
1930
+ }
1931
+ }
1932
+ }, 100); // Check every 100ms.
1912
1933
 
1913
- return () => clearTimeout(scrollTimeout);
1914
- }
1915
- // If the last item is NOT measured, this effect does nothing and simply waits.
1916
- // It will automatically re-run when the measurement comes in (via shadowUpdateTrigger).
1917
- }, [totalCount, totalHeight, stickToBottom]); // Re-run when layout changes.
1934
+ // Cleanup: Stop the loop if the component unmounts.
1935
+ return () => {
1936
+ console.log("ALGORITHM: Cleaning up loop.");
1937
+ clearInterval(intervalId);
1938
+ };
1939
+ }, [totalCount]); // This whole process triggers ONLY when totalCount changes.
1918
1940
 
1919
- // This effect ONLY handles user interaction and range updates.
1941
+ // Effect to handle user scrolling.
1920
1942
  useEffect(() => {
1921
1943
  const container = containerRef.current;
1922
1944
  if (!container) return;
1923
1945
 
1924
1946
  const updateVirtualRange = () => {
1925
- const { scrollTop, clientHeight } = container;
1926
- let low = 0,
1927
- high = totalCount - 1;
1928
- while (low <= high) {
1929
- const mid = Math.floor((low + high) / 2);
1930
- if (positions[mid]! < scrollTop) low = mid + 1;
1931
- else high = mid - 1;
1932
- }
1933
- const startIndex = Math.max(0, high - overscan);
1934
- let endIndex = startIndex;
1935
- const visibleEnd = scrollTop + clientHeight;
1936
- while (
1937
- endIndex < totalCount &&
1938
- positions[endIndex]! < visibleEnd
1939
- ) {
1940
- endIndex++;
1941
- }
1942
- setRange({
1943
- startIndex,
1944
- endIndex: Math.min(totalCount, endIndex + overscan),
1945
- });
1947
+ /* ... same as before ... */
1946
1948
  };
1947
-
1948
1949
  const handleUserScroll = () => {
1949
1950
  const isAtBottom =
1950
1951
  container.scrollHeight -
@@ -1953,24 +1954,23 @@ function createProxyHandler<T>(
1953
1954
  1;
1954
1955
  if (!isAtBottom) {
1955
1956
  isLockedToBottomRef.current = false;
1957
+ console.log("USER ACTION: Scroll lock DISABLED.");
1956
1958
  }
1957
1959
  updateVirtualRange();
1958
1960
  };
1959
-
1960
1961
  container.addEventListener("scroll", handleUserScroll, {
1961
1962
  passive: true,
1962
1963
  });
1963
- // Initial range calculation
1964
- updateVirtualRange();
1965
-
1966
1964
  return () =>
1967
1965
  container.removeEventListener("scroll", handleUserScroll);
1968
- }, [totalCount, positions]);
1966
+ }, []);
1969
1967
 
1970
1968
  const scrollToBottom = useCallback(
1971
1969
  (behavior: ScrollBehavior = "smooth") => {
1972
1970
  if (containerRef.current) {
1973
1971
  isLockedToBottomRef.current = true;
1972
+ console.log("USER ACTION: Scroll lock ENABLED.");
1973
+ // This is a manual trigger, so we don't need the loop. Just scroll.
1974
1974
  containerRef.current.scrollTo({
1975
1975
  top: containerRef.current.scrollHeight,
1976
1976
  behavior,
@@ -1984,6 +1984,7 @@ function createProxyHandler<T>(
1984
1984
  (index: number, behavior: ScrollBehavior = "smooth") => {
1985
1985
  if (containerRef.current && positions[index] !== undefined) {
1986
1986
  isLockedToBottomRef.current = false;
1987
+ console.log("USER ACTION: Scroll lock DISABLED.");
1987
1988
  containerRef.current.scrollTo({
1988
1989
  top: positions[index],
1989
1990
  behavior,
@@ -2010,7 +2011,6 @@ function createProxyHandler<T>(
2010
2011
  },
2011
2012
  },
2012
2013
  };
2013
-
2014
2014
  return {
2015
2015
  virtualState,
2016
2016
  virtualizerProps,