cogsbox-state 0.5.349 → 0.5.351

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.349",
3
+ "version": "0.5.351",
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
@@ -1813,12 +1813,7 @@ function createProxyHandler<T>(
1813
1813
  startIndex: 0,
1814
1814
  endIndex: 10,
1815
1815
  });
1816
- const sourceArray = getGlobalStore().getNestedState(
1817
- stateKey,
1818
- path
1819
- ) as any[];
1820
1816
  const isLockedToBottomRef = useRef(stickToBottom);
1821
- const prevTotalCountRef = useRef(sourceArray.length);
1822
1817
 
1823
1818
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1824
1819
 
@@ -1831,6 +1826,10 @@ function createProxyHandler<T>(
1831
1826
  return unsubscribe;
1832
1827
  }, [stateKey]);
1833
1828
 
1829
+ const sourceArray = getGlobalStore().getNestedState(
1830
+ stateKey,
1831
+ path
1832
+ ) as any[];
1834
1833
  const totalCount = sourceArray.length;
1835
1834
 
1836
1835
  const { totalHeight, positions } = useMemo(() => {
@@ -1868,71 +1867,85 @@ function createProxyHandler<T>(
1868
1867
  });
1869
1868
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1870
1869
 
1871
- // The one and only layout effect.
1870
+ // --- YOUR ALGORITHM IMPLEMENTED ---
1871
+ // This effect is the entry point. It triggers when new items are added.
1872
1872
  useLayoutEffect(() => {
1873
1873
  const container = containerRef.current;
1874
- if (!container) return;
1875
-
1876
- const hasNewItems = totalCount > prevTotalCountRef.current;
1877
-
1878
- // This function is now ALWAYS fresh.
1879
- const updateVirtualRange = () => {
1880
- const { scrollTop, clientHeight } = container;
1881
- let low = 0,
1882
- high = totalCount - 1;
1883
- while (low <= high) {
1884
- const mid = Math.floor((low + high) / 2);
1885
- if (positions[mid]! < scrollTop) low = mid + 1;
1886
- else high = mid - 1;
1887
- }
1888
- const startIndex = Math.max(0, high - overscan);
1889
- let endIndex = startIndex;
1890
- const visibleEnd = scrollTop + clientHeight;
1891
- while (
1892
- endIndex < totalCount &&
1893
- positions[endIndex]! < visibleEnd
1894
- ) {
1895
- endIndex++;
1896
- }
1897
- setRange({
1898
- startIndex,
1899
- endIndex: Math.min(totalCount, endIndex + overscan),
1900
- });
1901
- };
1874
+ // Only run if we have new items and are supposed to be at the bottom.
1875
+ if (
1876
+ !container ||
1877
+ !isLockedToBottomRef.current ||
1878
+ totalCount === 0
1879
+ ) {
1880
+ return;
1881
+ }
1902
1882
 
1903
- // --- YOUR SCROLLING LOGIC ---
1904
- // It only runs if we have new items and are locked to the bottom.
1905
- if (hasNewItems && isLockedToBottomRef.current) {
1906
- // STEP 1: Set range to the end to start measuring.
1907
- setRange({
1908
- startIndex: Math.max(0, totalCount - 10 - overscan),
1909
- endIndex: totalCount,
1910
- });
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
+ });
1911
1890
 
1912
- // STEP 2: Start the LOOP.
1913
- const intervalId = setInterval(() => {
1914
- const lastItemIndex = totalCount - 1;
1915
- const shadowArray =
1916
- getGlobalStore
1917
- .getState()
1918
- .getShadowMetadata(stateKey, path) || [];
1919
- const lastItemHeight =
1920
- shadowArray[lastItemIndex]?.virtualizer?.itemHeight || 0;
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.
1921
1916
 
1922
- if (lastItemHeight > 0) {
1917
+ // STEP 3: Scroll.
1918
+ container.scrollTo({
1919
+ top: container.scrollHeight,
1920
+ behavior: "smooth",
1921
+ });
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
+ );
1923
1929
  clearInterval(intervalId);
1924
- container.scrollTo({
1925
- top: container.scrollHeight,
1926
- behavior: "smooth",
1927
- });
1928
1930
  }
1929
- }, 100);
1931
+ }
1932
+ }, 100); // Check every 100ms.
1930
1933
 
1931
- // This return is the cleanup for the if-block.
1932
- return () => clearInterval(intervalId);
1933
- }
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.
1934
1940
 
1935
- // --- USER SCROLL HANDLING ---
1941
+ // Effect to handle user scrolling.
1942
+ useEffect(() => {
1943
+ const container = containerRef.current;
1944
+ if (!container) return;
1945
+
1946
+ const updateVirtualRange = () => {
1947
+ /* ... same as before ... */
1948
+ };
1936
1949
  const handleUserScroll = () => {
1937
1950
  const isAtBottom =
1938
1951
  container.scrollHeight -
@@ -1941,28 +1954,23 @@ function createProxyHandler<T>(
1941
1954
  1;
1942
1955
  if (!isAtBottom) {
1943
1956
  isLockedToBottomRef.current = false;
1957
+ console.log("USER ACTION: Scroll lock DISABLED.");
1944
1958
  }
1945
1959
  updateVirtualRange();
1946
1960
  };
1947
-
1948
1961
  container.addEventListener("scroll", handleUserScroll, {
1949
1962
  passive: true,
1950
1963
  });
1951
- updateVirtualRange(); // Always update range for current view.
1952
-
1953
- // This return is the cleanup for the whole effect.
1954
1964
  return () =>
1955
1965
  container.removeEventListener("scroll", handleUserScroll);
1956
- }, [totalCount, positions]); // Re-run when layout-related data changes.
1966
+ }, []);
1957
1967
 
1958
- // This simple effect tracks the item count for the next render.
1959
- useEffect(() => {
1960
- prevTotalCountRef.current = totalCount;
1961
- });
1962
1968
  const scrollToBottom = useCallback(
1963
1969
  (behavior: ScrollBehavior = "smooth") => {
1964
1970
  if (containerRef.current) {
1965
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.
1966
1974
  containerRef.current.scrollTo({
1967
1975
  top: containerRef.current.scrollHeight,
1968
1976
  behavior,
@@ -1976,6 +1984,7 @@ function createProxyHandler<T>(
1976
1984
  (index: number, behavior: ScrollBehavior = "smooth") => {
1977
1985
  if (containerRef.current && positions[index] !== undefined) {
1978
1986
  isLockedToBottomRef.current = false;
1987
+ console.log("USER ACTION: Scroll lock DISABLED.");
1979
1988
  containerRef.current.scrollTo({
1980
1989
  top: positions[index],
1981
1990
  behavior,
@@ -2002,6 +2011,7 @@ function createProxyHandler<T>(
2002
2011
  },
2003
2012
  },
2004
2013
  };
2014
+
2005
2015
  return {
2006
2016
  virtualState,
2007
2017
  virtualizerProps,