cogsbox-state 0.5.283 → 0.5.285

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.283",
3
+ "version": "0.5.285",
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,7 @@ function createProxyHandler<T>(
1802
1802
  options: VirtualViewOptions
1803
1803
  ): VirtualStateObjectResult<any[]> => {
1804
1804
  const {
1805
- itemHeight,
1805
+ itemHeight = 50, // Now optional with default
1806
1806
  overscan = 5,
1807
1807
  stickToBottom = false,
1808
1808
  } = options;
@@ -1812,16 +1812,37 @@ function createProxyHandler<T>(
1812
1812
  startIndex: 0,
1813
1813
  endIndex: 10,
1814
1814
  });
1815
- const getItemHeight = useCallback((index: number) => {
1816
- const metadata = getGlobalStore
1815
+
1816
+ // Get item height from shadow state or fall back to default
1817
+ const getItemHeight = useCallback(
1818
+ (index: number) => {
1819
+ const metadata = getGlobalStore
1820
+ .getState()
1821
+ .getShadowMetadata(stateKey, [...path, index.toString()]);
1822
+ return metadata?.virtualizer?.itemHeight || itemHeight;
1823
+ },
1824
+ [itemHeight]
1825
+ );
1826
+
1827
+ // Calculate total height and item positions
1828
+ const calculateHeights = useCallback(() => {
1829
+ const sourceArray = getGlobalStore
1817
1830
  .getState()
1818
- .getShadowMetadata(stateKey, [...path, index.toString()]);
1819
- return metadata?.virtualizer?.itemHeight || options.itemHeight;
1820
- }, []);
1821
- // --- State Tracking Refs ---
1831
+ .getNestedState(stateKey, path) as any[];
1832
+
1833
+ let totalHeight = 0;
1834
+ const positions: number[] = [];
1835
+
1836
+ for (let i = 0; i < sourceArray.length; i++) {
1837
+ positions[i] = totalHeight;
1838
+ totalHeight += getItemHeight(i);
1839
+ }
1840
+
1841
+ return { totalHeight, positions };
1842
+ }, [getItemHeight]);
1843
+
1822
1844
  const isAtBottomRef = useRef(stickToBottom);
1823
1845
  const previousTotalCountRef = useRef(0);
1824
- // NEW: Ref to explicitly track if this is the component's first render cycle.
1825
1846
  const isInitialMountRef = useRef(true);
1826
1847
 
1827
1848
  const sourceArray = getGlobalStore().getNestedState(
@@ -1852,25 +1873,43 @@ function createProxyHandler<T>(
1852
1873
  const listGrew = totalCount > previousTotalCountRef.current;
1853
1874
  previousTotalCountRef.current = totalCount;
1854
1875
 
1876
+ const { totalHeight, positions } = calculateHeights();
1877
+
1855
1878
  const handleScroll = () => {
1856
1879
  const { scrollTop, clientHeight, scrollHeight } = container;
1857
1880
  isAtBottomRef.current =
1858
1881
  scrollHeight - scrollTop - clientHeight < 10;
1859
- const start = Math.max(
1860
- 0,
1861
- Math.floor(scrollTop / itemHeight) - overscan
1862
- );
1863
- const end = Math.min(
1864
- totalCount,
1865
- Math.ceil((scrollTop + clientHeight) / itemHeight) +
1866
- overscan
1867
- );
1882
+
1883
+ // Find start index using binary search
1884
+ let start = 0;
1885
+ let end = positions.length - 1;
1886
+ while (start < end) {
1887
+ const mid = Math.floor((start + end) / 2);
1888
+ if (positions[mid]! < scrollTop) {
1889
+ start = mid + 1;
1890
+ } else {
1891
+ end = mid;
1892
+ }
1893
+ }
1894
+ start = Math.max(0, start - overscan);
1895
+
1896
+ // Find end index
1897
+ const visibleEnd = scrollTop + clientHeight;
1898
+ let endIndex = start;
1899
+ while (
1900
+ endIndex < positions.length &&
1901
+ positions[endIndex]! < visibleEnd
1902
+ ) {
1903
+ endIndex++;
1904
+ }
1905
+ endIndex = Math.min(totalCount, endIndex + overscan);
1906
+
1868
1907
  setRange((prevRange) => {
1869
1908
  if (
1870
1909
  prevRange.startIndex !== start ||
1871
- prevRange.endIndex !== end
1910
+ prevRange.endIndex !== endIndex
1872
1911
  ) {
1873
- return { startIndex: start, endIndex: end };
1912
+ return { startIndex: start, endIndex: endIndex };
1874
1913
  }
1875
1914
  return prevRange;
1876
1915
  });
@@ -1880,19 +1919,13 @@ function createProxyHandler<T>(
1880
1919
  passive: true,
1881
1920
  });
1882
1921
 
1883
- // --- THE CORRECTED DECISION LOGIC ---
1884
1922
  if (stickToBottom) {
1885
1923
  if (isInitialMountRef.current) {
1886
- // SCENARIO 1: First render of the component.
1887
- // Go to the bottom unconditionally. Use `auto` scroll for an instant jump.
1888
1924
  container.scrollTo({
1889
1925
  top: container.scrollHeight,
1890
1926
  behavior: "auto",
1891
1927
  });
1892
1928
  } else if (wasAtBottom && listGrew) {
1893
- // SCENARIO 2: Subsequent renders (new messages arrive).
1894
- // Only scroll if the user was already at the bottom.
1895
- // Use `smooth` for a nice animated scroll for new messages.
1896
1929
  requestAnimationFrame(() => {
1897
1930
  container.scrollTo({
1898
1931
  top: container.scrollHeight,
@@ -1902,15 +1935,12 @@ function createProxyHandler<T>(
1902
1935
  }
1903
1936
  }
1904
1937
 
1905
- // After the logic runs, it's no longer the initial mount.
1906
1938
  isInitialMountRef.current = false;
1907
-
1908
- // Always run handleScroll once to set the initial visible window.
1909
1939
  handleScroll();
1910
1940
 
1911
1941
  return () =>
1912
1942
  container.removeEventListener("scroll", handleScroll);
1913
- }, [totalCount, itemHeight, overscan, stickToBottom]);
1943
+ }, [totalCount, calculateHeights, overscan, stickToBottom]);
1914
1944
 
1915
1945
  const scrollToBottom = useCallback(
1916
1946
  (behavior: ScrollBehavior = "smooth") => {
@@ -1927,37 +1957,44 @@ function createProxyHandler<T>(
1927
1957
  const scrollToIndex = useCallback(
1928
1958
  (index: number, behavior: ScrollBehavior = "smooth") => {
1929
1959
  if (containerRef.current) {
1960
+ const { positions } = calculateHeights();
1930
1961
  containerRef.current.scrollTo({
1931
- top: index * itemHeight,
1962
+ top: positions[index] || 0,
1932
1963
  behavior,
1933
1964
  });
1934
1965
  }
1935
1966
  },
1936
- [itemHeight]
1967
+ [calculateHeights]
1937
1968
  );
1938
1969
 
1939
- // Same virtualizer props as before
1970
+ // Calculate actual heights for rendering
1971
+ const {
1972
+ totalHeight: totalHeightForRender,
1973
+ positions: positionsForRender,
1974
+ } = calculateHeights();
1975
+ const offsetY = positionsForRender[range.startIndex] || 0;
1976
+
1940
1977
  const virtualizerProps = {
1941
1978
  outer: {
1942
1979
  ref: containerRef,
1943
- style: { overflowY: "auto", height: "100%" },
1980
+ style: { overflowY: "auto", height: "100%" } as CSSProperties,
1944
1981
  },
1945
1982
  inner: {
1946
1983
  style: {
1947
- height: `${totalCount * itemHeight}px`,
1984
+ height: `${totalHeightForRender}px`,
1948
1985
  position: "relative",
1949
- },
1986
+ } as CSSProperties,
1950
1987
  },
1951
1988
  list: {
1952
1989
  style: {
1953
- transform: `translateY(${range.startIndex * itemHeight}px)`,
1954
- },
1990
+ transform: `translateY(${offsetY}px)`,
1991
+ } as CSSProperties,
1955
1992
  },
1956
1993
  };
1957
1994
 
1958
1995
  return {
1959
1996
  virtualState,
1960
- virtualizerProps: virtualizerProps as any,
1997
+ virtualizerProps,
1961
1998
  scrollToBottom,
1962
1999
  scrollToIndex,
1963
2000
  };