cogsbox-state 0.5.315 → 0.5.316

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.315",
3
+ "version": "0.5.316",
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
@@ -1820,10 +1820,23 @@ function createProxyHandler<T>(
1820
1820
  // This state triggers a re-render when item heights change.
1821
1821
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1822
1822
 
1823
+ // Track when we've attempted initial scroll
1824
+ const hasAttemptedScrollRef = useRef(false);
1825
+ const lastTotalCountRef = useRef(0);
1826
+
1827
+ // Subscribe to shadow state changes with limited logging
1823
1828
  useEffect(() => {
1829
+ let updateCount = 0;
1824
1830
  const unsubscribe = getGlobalStore
1825
1831
  .getState()
1826
1832
  .subscribeToShadowState(stateKey, () => {
1833
+ updateCount++;
1834
+ if (updateCount <= 5) {
1835
+ // Only log first 5 updates to avoid spam
1836
+ console.log(
1837
+ `[VirtualView] Shadow update #${updateCount}`
1838
+ );
1839
+ }
1827
1840
  setShadowUpdateTrigger((prev) => prev + 1);
1828
1841
  });
1829
1842
  return unsubscribe;
@@ -1835,20 +1848,44 @@ function createProxyHandler<T>(
1835
1848
  ) as any[];
1836
1849
  const totalCount = sourceArray.length;
1837
1850
 
1838
- // Calculate heights from shadow state. This runs when data or measurements change.
1839
- const { totalHeight, positions } = useMemo(() => {
1851
+ console.log(
1852
+ `[VirtualView] Initial setup - totalCount: ${totalCount}, itemHeight: ${itemHeight}, stickToBottom: ${stickToBottom}`
1853
+ );
1854
+
1855
+ // Reset scroll attempt when array size changes
1856
+ if (totalCount !== lastTotalCountRef.current) {
1857
+ console.log(
1858
+ `[VirtualView] Array size changed from ${lastTotalCountRef.current} to ${totalCount}`
1859
+ );
1860
+ hasAttemptedScrollRef.current = false;
1861
+ lastTotalCountRef.current = totalCount;
1862
+ }
1863
+
1864
+ // Calculate heights from shadow state and track if all measured
1865
+ const { totalHeight, positions, allMeasured } = useMemo(() => {
1840
1866
  const shadowArray =
1841
1867
  getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
1842
1868
  [];
1843
1869
  let height = 0;
1844
1870
  const pos: number[] = [];
1871
+ let measuredCount = 0;
1872
+
1845
1873
  for (let i = 0; i < totalCount; i++) {
1846
1874
  pos[i] = height;
1847
1875
  const measuredHeight =
1848
1876
  shadowArray[i]?.virtualizer?.itemHeight;
1877
+ if (measuredHeight) measuredCount++;
1849
1878
  height += measuredHeight || itemHeight;
1850
1879
  }
1851
- return { totalHeight: height, positions: pos };
1880
+
1881
+ const allMeasured =
1882
+ measuredCount === totalCount && totalCount > 0;
1883
+
1884
+ console.log(
1885
+ `[VirtualView] Heights calc - measured: ${measuredCount}/${totalCount}, allMeasured: ${allMeasured}, totalHeight: ${height}`
1886
+ );
1887
+
1888
+ return { totalHeight: height, positions: pos, allMeasured };
1852
1889
  }, [
1853
1890
  totalCount,
1854
1891
  stateKey,
@@ -1861,6 +1898,11 @@ function createProxyHandler<T>(
1861
1898
  const virtualState = useMemo(() => {
1862
1899
  const start = Math.max(0, range.startIndex);
1863
1900
  const end = Math.min(totalCount, range.endIndex);
1901
+
1902
+ console.log(
1903
+ `[VirtualView] Creating virtual slice - range: ${start}-${end} (${end - start} items)`
1904
+ );
1905
+
1864
1906
  const validIndices = Array.from(
1865
1907
  { length: end - start },
1866
1908
  (_, i) => start + i
@@ -1872,13 +1914,11 @@ function createProxyHandler<T>(
1872
1914
  });
1873
1915
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1874
1916
 
1875
- // This is the main effect that handles all scrolling and updates.
1917
+ // Simplified layout effect that waits for all measurements
1876
1918
  useLayoutEffect(() => {
1877
1919
  const container = containerRef.current;
1878
1920
  if (!container) return;
1879
1921
 
1880
- let scrollTimeoutId: NodeJS.Timeout;
1881
-
1882
1922
  // This function determines what's visible in the viewport.
1883
1923
  const updateVirtualRange = () => {
1884
1924
  if (!container) return;
@@ -1917,32 +1957,38 @@ function createProxyHandler<T>(
1917
1957
  passive: true,
1918
1958
  });
1919
1959
 
1920
- // --- THE CORE FIX ---
1921
- if (stickToBottom) {
1922
- // We use a timeout to wait for React to render AND for useMeasure to update heights.
1923
- // This is the CRUCIAL part that fixes the race condition.
1924
- scrollTimeoutId = setTimeout(() => {
1925
- // By the time this runs, `container.scrollHeight` is accurate.
1926
- // We only scroll if the user hasn't manually scrolled up in the meantime.
1927
- if (isLockedToBottomRef.current) {
1928
- container.scrollTo({
1929
- top: container.scrollHeight,
1930
- behavior: "auto", // ALWAYS 'auto' for an instant, correct jump.
1931
- });
1932
- }
1933
- }, 200); // A small 50ms delay is a robust buffer.
1960
+ // Scroll to bottom logic
1961
+ if (
1962
+ stickToBottom &&
1963
+ !hasAttemptedScrollRef.current &&
1964
+ totalCount > 0
1965
+ ) {
1966
+ if (allMeasured) {
1967
+ // All items measured, safe to scroll
1968
+ console.log(
1969
+ `[VirtualView] All items measured, scrolling to bottom`
1970
+ );
1971
+ hasAttemptedScrollRef.current = true;
1972
+ container.scrollTo({
1973
+ top: container.scrollHeight,
1974
+ behavior: "auto",
1975
+ });
1976
+ } else {
1977
+ // Still waiting for measurements, try again next render
1978
+ console.log(
1979
+ `[VirtualView] Waiting for all measurements before scroll`
1980
+ );
1981
+ }
1934
1982
  }
1935
1983
 
1936
1984
  // Update the visible range on initial load.
1937
1985
  updateVirtualRange();
1938
1986
 
1939
- // Cleanup function is vital to prevent memory leaks.
1987
+ // Cleanup function
1940
1988
  return () => {
1941
- clearTimeout(scrollTimeoutId);
1942
1989
  container.removeEventListener("scroll", handleUserScroll);
1943
1990
  };
1944
- // This effect re-runs whenever the list size or item heights change.
1945
- }, [totalCount, positions, stickToBottom]);
1991
+ }, [totalCount, positions, stickToBottom, allMeasured]);
1946
1992
 
1947
1993
  const scrollToBottom = useCallback(
1948
1994
  (behavior: ScrollBehavior = "smooth") => {