cogsbox-state 0.5.320 → 0.5.321

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.320",
3
+ "version": "0.5.321",
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
@@ -1809,22 +1809,19 @@ function createProxyHandler<T>(
1809
1809
  } = options;
1810
1810
 
1811
1811
  const containerRef = useRef<HTMLDivElement | null>(null);
1812
-
1813
- const sourceArray = getGlobalStore().getNestedState(
1814
- stateKey,
1815
- path
1816
- ) as any[];
1817
- const totalCount = sourceArray.length;
1818
-
1819
- // Start at top, will adjust once container is measured
1820
1812
  const [range, setRange] = useState({
1821
1813
  startIndex: 0,
1822
1814
  endIndex: 10,
1823
1815
  });
1824
-
1825
1816
  const isLockedToBottomRef = useRef(stickToBottom);
1826
1817
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1827
- const hasInitializedRef = useRef(false);
1818
+ const hasScrolledToBottomRef = useRef(false);
1819
+
1820
+ const sourceArray = getGlobalStore().getNestedState(
1821
+ stateKey,
1822
+ path
1823
+ ) as any[];
1824
+ const totalCount = sourceArray.length;
1828
1825
 
1829
1826
  useEffect(() => {
1830
1827
  const unsubscribe = getGlobalStore
@@ -1835,26 +1832,51 @@ function createProxyHandler<T>(
1835
1832
  return unsubscribe;
1836
1833
  }, [stateKey]);
1837
1834
 
1838
- const { totalHeight, positions } = useMemo(() => {
1839
- const shadowArray =
1840
- getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
1841
- [];
1842
- let height = 0;
1843
- const pos: number[] = [];
1844
- for (let i = 0; i < totalCount; i++) {
1845
- pos[i] = height;
1846
- const measuredHeight =
1847
- shadowArray[i]?.virtualizer?.itemHeight;
1848
- height += measuredHeight || itemHeight;
1849
- }
1850
- return { totalHeight: height, positions: pos };
1851
- }, [
1852
- totalCount,
1853
- stateKey,
1854
- path.join("."),
1855
- itemHeight,
1856
- shadowUpdateTrigger,
1857
- ]);
1835
+ const { totalHeight, positions, bottomItemsMeasured } =
1836
+ useMemo(() => {
1837
+ const shadowArray =
1838
+ getGlobalStore
1839
+ .getState()
1840
+ .getShadowMetadata(stateKey, path) || [];
1841
+ let height = 0;
1842
+ const pos: number[] = [];
1843
+ let bottomMeasuredCount = 0;
1844
+
1845
+ // Check how many of the last 20 items are measured
1846
+ const checkFromIndex = Math.max(0, totalCount - 20);
1847
+
1848
+ for (let i = 0; i < totalCount; i++) {
1849
+ pos[i] = height;
1850
+ const measuredHeight =
1851
+ shadowArray[i]?.virtualizer?.itemHeight;
1852
+
1853
+ if (measuredHeight) {
1854
+ height += measuredHeight;
1855
+ if (i >= checkFromIndex) {
1856
+ bottomMeasuredCount++;
1857
+ }
1858
+ } else {
1859
+ height += itemHeight;
1860
+ }
1861
+ }
1862
+
1863
+ // Bottom items are measured if we have measurements for the last 20 items
1864
+ const bottomReady =
1865
+ bottomMeasuredCount >=
1866
+ Math.min(20, totalCount - checkFromIndex);
1867
+
1868
+ return {
1869
+ totalHeight: height,
1870
+ positions: pos,
1871
+ bottomItemsMeasured: bottomReady,
1872
+ };
1873
+ }, [
1874
+ totalCount,
1875
+ stateKey,
1876
+ path.join("."),
1877
+ itemHeight,
1878
+ shadowUpdateTrigger,
1879
+ ]);
1858
1880
 
1859
1881
  const virtualState = useMemo(() => {
1860
1882
  const start = Math.max(0, range.startIndex);
@@ -1910,32 +1932,31 @@ function createProxyHandler<T>(
1910
1932
  passive: true,
1911
1933
  });
1912
1934
 
1913
- // Initialize at bottom if needed
1935
+ // STICK TO BOTTOM LOGIC
1914
1936
  if (
1915
1937
  stickToBottom &&
1916
- !hasInitializedRef.current &&
1917
- totalCount > 0 &&
1918
- container.clientHeight > 0
1938
+ !hasScrolledToBottomRef.current &&
1939
+ totalCount > 0
1919
1940
  ) {
1920
- hasInitializedRef.current = true;
1921
-
1922
- // Now we have the actual container height, calculate visible items
1923
- const visibleCount = Math.ceil(
1924
- container.clientHeight / itemHeight
1925
- );
1926
- const startIdx = Math.max(
1927
- 0,
1928
- totalCount - visibleCount - overscan
1929
- );
1930
-
1931
- // Set range to show bottom items
1932
- setRange({
1933
- startIndex: startIdx,
1934
- endIndex: totalCount,
1935
- });
1936
-
1937
- // Scroll to bottom
1938
- container.scrollTop = container.scrollHeight;
1941
+ if (!bottomItemsMeasured) {
1942
+ // Step 1: Jump to near bottom to trigger rendering of bottom items
1943
+ console.log(
1944
+ "[VirtualView] Jumping to near bottom to trigger measurements"
1945
+ );
1946
+ const jumpPosition = Math.max(
1947
+ 0,
1948
+ (totalCount - 30) * itemHeight
1949
+ );
1950
+ container.scrollTop = jumpPosition;
1951
+ } else {
1952
+ // Step 2: Bottom items are measured, now scroll to actual bottom
1953
+ console.log(
1954
+ "[VirtualView] Bottom items measured, scrolling to true bottom"
1955
+ );
1956
+ hasScrolledToBottomRef.current = true;
1957
+ container.scrollTop = container.scrollHeight;
1958
+ isLockedToBottomRef.current = true;
1959
+ }
1939
1960
  }
1940
1961
 
1941
1962
  updateVirtualRange();
@@ -1943,7 +1964,7 @@ function createProxyHandler<T>(
1943
1964
  return () => {
1944
1965
  container.removeEventListener("scroll", handleUserScroll);
1945
1966
  };
1946
- }, [totalCount, positions, stickToBottom, itemHeight, overscan]);
1967
+ }, [totalCount, positions, stickToBottom, bottomItemsMeasured]);
1947
1968
 
1948
1969
  const scrollToBottom = useCallback(
1949
1970
  (behavior: ScrollBehavior = "smooth") => {
@@ -1974,7 +1995,13 @@ function createProxyHandler<T>(
1974
1995
  const virtualizerProps = {
1975
1996
  outer: {
1976
1997
  ref: containerRef,
1977
- style: { overflowY: "auto" as const, height: "100%" },
1998
+ style: {
1999
+ overflowY: "auto" as const,
2000
+ height: "100%",
2001
+ overflowAnchor: stickToBottom
2002
+ ? ("auto" as const)
2003
+ : ("none" as const),
2004
+ },
1978
2005
  },
1979
2006
  inner: {
1980
2007
  style: {