cogsbox-state 0.5.346 → 0.5.349

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.346",
3
+ "version": "0.5.349",
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,7 +1813,12 @@ 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[];
1816
1820
  const isLockedToBottomRef = useRef(stickToBottom);
1821
+ const prevTotalCountRef = useRef(sourceArray.length);
1817
1822
 
1818
1823
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1819
1824
 
@@ -1826,10 +1831,6 @@ function createProxyHandler<T>(
1826
1831
  return unsubscribe;
1827
1832
  }, [stateKey]);
1828
1833
 
1829
- const sourceArray = getGlobalStore().getNestedState(
1830
- stateKey,
1831
- path
1832
- ) as any[];
1833
1834
  const totalCount = sourceArray.length;
1834
1835
 
1835
1836
  const { totalHeight, positions } = useMemo(() => {
@@ -1867,85 +1868,71 @@ function createProxyHandler<T>(
1867
1868
  });
1868
1869
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1869
1870
 
1870
- // --- YOUR ALGORITHM IMPLEMENTED ---
1871
- // This effect is the entry point. It triggers when new items are added.
1871
+ // The one and only layout effect.
1872
1872
  useLayoutEffect(() => {
1873
1873
  const container = containerRef.current;
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
- }
1874
+ if (!container) return;
1882
1875
 
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
- });
1876
+ const hasNewItems = totalCount > prevTotalCountRef.current;
1890
1877
 
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.
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
+ };
1916
1902
 
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
- );
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
+ });
1911
+
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;
1921
+
1922
+ if (lastItemHeight > 0) {
1929
1923
  clearInterval(intervalId);
1924
+ container.scrollTo({
1925
+ top: container.scrollHeight,
1926
+ behavior: "smooth",
1927
+ });
1930
1928
  }
1931
- }
1932
- }, 100); // Check every 100ms.
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.
1929
+ }, 100);
1940
1930
 
1941
- // Effect to handle user scrolling.
1942
- useEffect(() => {
1943
- const container = containerRef.current;
1944
- if (!container) return;
1931
+ // This return is the cleanup for the if-block.
1932
+ return () => clearInterval(intervalId);
1933
+ }
1945
1934
 
1946
- const updateVirtualRange = () => {
1947
- /* ... same as before ... */
1948
- };
1935
+ // --- USER SCROLL HANDLING ---
1949
1936
  const handleUserScroll = () => {
1950
1937
  const isAtBottom =
1951
1938
  container.scrollHeight -
@@ -1954,23 +1941,28 @@ function createProxyHandler<T>(
1954
1941
  1;
1955
1942
  if (!isAtBottom) {
1956
1943
  isLockedToBottomRef.current = false;
1957
- console.log("USER ACTION: Scroll lock DISABLED.");
1958
1944
  }
1959
1945
  updateVirtualRange();
1960
1946
  };
1947
+
1961
1948
  container.addEventListener("scroll", handleUserScroll, {
1962
1949
  passive: true,
1963
1950
  });
1951
+ updateVirtualRange(); // Always update range for current view.
1952
+
1953
+ // This return is the cleanup for the whole effect.
1964
1954
  return () =>
1965
1955
  container.removeEventListener("scroll", handleUserScroll);
1966
- }, []);
1956
+ }, [totalCount, positions]); // Re-run when layout-related data changes.
1967
1957
 
1958
+ // This simple effect tracks the item count for the next render.
1959
+ useEffect(() => {
1960
+ prevTotalCountRef.current = totalCount;
1961
+ });
1968
1962
  const scrollToBottom = useCallback(
1969
1963
  (behavior: ScrollBehavior = "smooth") => {
1970
1964
  if (containerRef.current) {
1971
1965
  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
1966
  containerRef.current.scrollTo({
1975
1967
  top: containerRef.current.scrollHeight,
1976
1968
  behavior,
@@ -1984,7 +1976,6 @@ function createProxyHandler<T>(
1984
1976
  (index: number, behavior: ScrollBehavior = "smooth") => {
1985
1977
  if (containerRef.current && positions[index] !== undefined) {
1986
1978
  isLockedToBottomRef.current = false;
1987
- console.log("USER ACTION: Scroll lock DISABLED.");
1988
1979
  containerRef.current.scrollTo({
1989
1980
  top: positions[index],
1990
1981
  behavior,