cogsbox-state 0.5.295 → 0.5.297

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.295",
3
+ "version": "0.5.297",
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
@@ -1802,7 +1802,6 @@ function createProxyHandler<T>(
1802
1802
  return (
1803
1803
  options: VirtualViewOptions
1804
1804
  ): VirtualStateObjectResult<any[]> => {
1805
- // --- CHANGE 1: itemHeight is now optional, with a default fallback.
1806
1805
  const {
1807
1806
  itemHeight = 50, // Default/estimated height
1808
1807
  overscan = 5,
@@ -1815,18 +1814,26 @@ function createProxyHandler<T>(
1815
1814
  endIndex: 10,
1816
1815
  });
1817
1816
 
1818
- // --- CHANGE 2: Add a helper to get the real height of each item. ---
1819
- const getItemHeight = useCallback(
1820
- (index: number): number => {
1821
- const metadata = getGlobalStore
1822
- .getState()
1823
- .getShadowMetadata(stateKey, [...path, index.toString()]);
1824
- return metadata?.virtualizer?.itemHeight || itemHeight;
1825
- },
1826
- [itemHeight, stateKey, path]
1817
+ // --- STATE AND CALLBACKS FOR HEIGHTS ---
1818
+ // This state value is the key. We increment it to force a re-calculation.
1819
+ const [heightsVersion, setHeightsVersion] = useState(0);
1820
+ // This callback is stable and won't cause re-renders itself.
1821
+ const forceRecalculate = useCallback(
1822
+ () => setHeightsVersion((v) => v + 1),
1823
+ []
1827
1824
  );
1828
1825
 
1829
- // --- These refs are from your original code. NO CHANGE. ---
1826
+ // --- ON MOUNT: SCHEDULE A RECALCULATION ---
1827
+ // This solves the "initial load" problem. It ensures that after the first
1828
+ // items render and measure themselves, we run the calculations again
1829
+ // with the new, correct height data.
1830
+ useEffect(() => {
1831
+ const timer = setTimeout(() => {
1832
+ forceRecalculate();
1833
+ }, 50); // A small delay helps batch initial measurements.
1834
+ return () => clearTimeout(timer);
1835
+ }, [forceRecalculate]);
1836
+
1830
1837
  const isAtBottomRef = useRef(stickToBottom);
1831
1838
  const previousTotalCountRef = useRef(0);
1832
1839
  const isInitialMountRef = useRef(true);
@@ -1837,19 +1844,27 @@ function createProxyHandler<T>(
1837
1844
  ) as any[];
1838
1845
  const totalCount = sourceArray.length;
1839
1846
 
1840
- // --- CHANGE 3: Calculate the total height and position of each item. ---
1841
- // This is the only new block of logic required.
1847
+ // --- EFFICIENT HEIGHT & POSITION CALCULATION ---
1842
1848
  const { totalHeight, positions } = useMemo(() => {
1849
+ // Get the shadow object for the whole array ONCE. This is fast.
1850
+ const shadowArray =
1851
+ getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
1852
+ [];
1853
+
1843
1854
  let height = 0;
1844
1855
  const pos: number[] = [];
1845
1856
  for (let i = 0; i < totalCount; i++) {
1846
1857
  pos[i] = height;
1847
- height += getItemHeight(i);
1858
+ // Access the height from the local shadowArray. No repeated deep lookups.
1859
+ const measuredHeight =
1860
+ shadowArray[i]?.virtualizer?.itemHeight;
1861
+ height += measuredHeight || itemHeight;
1848
1862
  }
1849
1863
  return { totalHeight: height, positions: pos };
1850
- }, [totalCount, getItemHeight]);
1864
+ // This now depends on `heightsVersion`, so it re-runs when we force it.
1865
+ }, [totalCount, stateKey, path, itemHeight, heightsVersion]);
1851
1866
 
1852
- // --- The virtualState logic is IDENTICAL to your original. NO CHANGE. ---
1867
+ // This logic is from your original working code.
1853
1868
  const virtualState = useMemo(() => {
1854
1869
  const start = Math.max(0, range.startIndex);
1855
1870
  const end = Math.min(totalCount, range.endIndex);
@@ -1864,8 +1879,7 @@ function createProxyHandler<T>(
1864
1879
  });
1865
1880
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1866
1881
 
1867
- // --- This useLayoutEffect is from your original code. ---
1868
- // --- We only change the math inside handleScroll. ---
1882
+ // This is your original useLayoutEffect with the robust index calculation.
1869
1883
  useLayoutEffect(() => {
1870
1884
  const container = containerRef.current;
1871
1885
  if (!container) return;
@@ -1879,19 +1893,24 @@ function createProxyHandler<T>(
1879
1893
  isAtBottomRef.current =
1880
1894
  scrollHeight - scrollTop - clientHeight < 10;
1881
1895
 
1882
- // --- CHANGE 4: The math to find the start and end index. ---
1883
- // This replaces `scrollTop / itemHeight` with a more accurate search.
1884
- let startIndex = 0;
1885
- // Find the first item whose top position is past the scroll top.
1886
- for (let i = 0; i < positions.length; i++) {
1887
- if (positions[i]! >= scrollTop) {
1888
- startIndex = i;
1889
- break;
1896
+ // ROBUST: Binary search to find the start index. Prevents errors.
1897
+ let search = (list: number[], value: number) => {
1898
+ let low = 0;
1899
+ let high = list.length - 1;
1900
+ while (low <= high) {
1901
+ const mid = Math.floor((low + high) / 2);
1902
+ if (list[mid]! < value) {
1903
+ low = mid + 1;
1904
+ } else {
1905
+ high = mid - 1;
1906
+ }
1890
1907
  }
1891
- }
1908
+ return low;
1909
+ };
1910
+
1911
+ let startIndex = search(positions, scrollTop);
1892
1912
 
1893
1913
  let endIndex = startIndex;
1894
- // Find the first item whose top position is past the bottom of the viewport.
1895
1914
  while (
1896
1915
  endIndex < totalCount &&
1897
1916
  positions[endIndex]! < scrollTop + clientHeight
@@ -1899,23 +1918,15 @@ function createProxyHandler<T>(
1899
1918
  endIndex++;
1900
1919
  }
1901
1920
 
1902
- // Apply overscan, identical to your original code.
1903
1921
  startIndex = Math.max(0, startIndex - overscan);
1904
1922
  endIndex = Math.min(totalCount, endIndex + overscan);
1905
- console.log(
1906
- "startIndex",
1907
- startIndex,
1908
- "endIndex",
1909
- endIndex,
1910
- "totalHeight",
1911
- totalHeight
1912
- );
1923
+
1913
1924
  setRange((prevRange) => {
1914
1925
  if (
1915
1926
  prevRange.startIndex !== startIndex ||
1916
1927
  prevRange.endIndex !== endIndex
1917
1928
  ) {
1918
- return { startIndex: startIndex, endIndex: endIndex };
1929
+ return { startIndex, endIndex };
1919
1930
  }
1920
1931
  return prevRange;
1921
1932
  });
@@ -1925,7 +1936,7 @@ function createProxyHandler<T>(
1925
1936
  passive: true,
1926
1937
  });
1927
1938
 
1928
- // --- This stickToBottom logic is IDENTICAL to your original. NO CHANGE. ---
1939
+ // This stickToBottom logic is from your original working code.
1929
1940
  if (stickToBottom) {
1930
1941
  if (isInitialMountRef.current) {
1931
1942
  container.scrollTo({
@@ -1947,7 +1958,6 @@ function createProxyHandler<T>(
1947
1958
 
1948
1959
  return () =>
1949
1960
  container.removeEventListener("scroll", handleScroll);
1950
- // --- We swap `itemHeight` for `positions` in the dependency array. ---
1951
1961
  }, [totalCount, overscan, stickToBottom, positions]);
1952
1962
 
1953
1963
  const scrollToBottom = useCallback(
@@ -1962,34 +1972,29 @@ function createProxyHandler<T>(
1962
1972
  []
1963
1973
  );
1964
1974
 
1965
- // --- CHANGE 5: Update scrollToIndex to use the positions array. ---
1966
1975
  const scrollToIndex = useCallback(
1967
1976
  (index: number, behavior: ScrollBehavior = "smooth") => {
1968
1977
  if (containerRef.current) {
1969
1978
  containerRef.current.scrollTo({
1970
- top: positions[index] || 0, // Use the calculated position
1979
+ top: positions[index] || 0,
1971
1980
  behavior,
1972
1981
  });
1973
1982
  }
1974
1983
  },
1975
- [positions] // Dependency is now `positions`
1984
+ [positions]
1976
1985
  );
1977
1986
 
1978
- // --- CHANGE 6: Update virtualizer props to use dynamic values. ---
1979
1987
  const virtualizerProps = {
1980
1988
  outer: {
1981
1989
  ref: containerRef,
1982
1990
  style: { overflowY: "auto", height: "100%" },
1983
1991
  },
1984
1992
  inner: {
1985
- style: {
1986
- height: `${totalHeight}px`, // Use calculated dynamic height
1987
- position: "relative",
1988
- },
1993
+ style: { height: `${totalHeight}px`, position: "relative" },
1989
1994
  },
1990
1995
  list: {
1991
1996
  style: {
1992
- transform: `translateY(${positions[range.startIndex] || 0}px)`, // Use calculated position
1997
+ transform: `translateY(${positions[range.startIndex] || 0}px)`,
1993
1998
  },
1994
1999
  },
1995
2000
  };