cogsbox-state 0.5.316 → 0.5.318

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.316",
3
+ "version": "0.5.318",
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
@@ -1814,17 +1814,13 @@ function createProxyHandler<T>(
1814
1814
  endIndex: 10,
1815
1815
  });
1816
1816
 
1817
- // This ref tracks if the user is locked to the bottom.
1818
1817
  const isLockedToBottomRef = useRef(stickToBottom);
1819
-
1820
- // This state triggers a re-render when item heights change.
1821
1818
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1822
1819
 
1823
- // Track when we've attempted initial scroll
1824
- const hasAttemptedScrollRef = useRef(false);
1820
+ // Track if we've scrolled to bottom after initial load
1821
+ const hasScrolledToBottomRef = useRef(false);
1825
1822
  const lastTotalCountRef = useRef(0);
1826
1823
 
1827
- // Subscribe to shadow state changes with limited logging
1828
1824
  useEffect(() => {
1829
1825
  let updateCount = 0;
1830
1826
  const unsubscribe = getGlobalStore
@@ -1832,7 +1828,6 @@ function createProxyHandler<T>(
1832
1828
  .subscribeToShadowState(stateKey, () => {
1833
1829
  updateCount++;
1834
1830
  if (updateCount <= 5) {
1835
- // Only log first 5 updates to avoid spam
1836
1831
  console.log(
1837
1832
  `[VirtualView] Shadow update #${updateCount}`
1838
1833
  );
@@ -1852,49 +1847,65 @@ function createProxyHandler<T>(
1852
1847
  `[VirtualView] Initial setup - totalCount: ${totalCount}, itemHeight: ${itemHeight}, stickToBottom: ${stickToBottom}`
1853
1848
  );
1854
1849
 
1855
- // Reset scroll attempt when array size changes
1850
+ // Reset when array size changes significantly
1856
1851
  if (totalCount !== lastTotalCountRef.current) {
1857
1852
  console.log(
1858
1853
  `[VirtualView] Array size changed from ${lastTotalCountRef.current} to ${totalCount}`
1859
1854
  );
1860
- hasAttemptedScrollRef.current = false;
1855
+ hasScrolledToBottomRef.current = false;
1861
1856
  lastTotalCountRef.current = totalCount;
1862
1857
  }
1863
1858
 
1864
- // Calculate heights from shadow state and track if all measured
1865
- const { totalHeight, positions, allMeasured } = useMemo(() => {
1866
- const shadowArray =
1867
- getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
1868
- [];
1869
- let height = 0;
1870
- const pos: number[] = [];
1871
- let measuredCount = 0;
1872
-
1873
- for (let i = 0; i < totalCount; i++) {
1874
- pos[i] = height;
1875
- const measuredHeight =
1876
- shadowArray[i]?.virtualizer?.itemHeight;
1877
- if (measuredHeight) measuredCount++;
1878
- height += measuredHeight || itemHeight;
1879
- }
1859
+ // Calculate heights from shadow state
1860
+ const { totalHeight, positions, visibleMeasured } =
1861
+ useMemo(() => {
1862
+ const shadowArray =
1863
+ getGlobalStore
1864
+ .getState()
1865
+ .getShadowMetadata(stateKey, path) || [];
1866
+ let height = 0;
1867
+ const pos: number[] = [];
1868
+ let measuredCount = 0;
1869
+ let visibleMeasuredCount = 0;
1870
+
1871
+ for (let i = 0; i < totalCount; i++) {
1872
+ pos[i] = height;
1873
+ const measuredHeight =
1874
+ shadowArray[i]?.virtualizer?.itemHeight;
1875
+ if (measuredHeight) {
1876
+ measuredCount++;
1877
+ // Count measured items in current visible range
1878
+ if (i >= range.startIndex && i < range.endIndex) {
1879
+ visibleMeasuredCount++;
1880
+ }
1881
+ }
1882
+ height += measuredHeight || itemHeight;
1883
+ }
1880
1884
 
1881
- const allMeasured =
1882
- measuredCount === totalCount && totalCount > 0;
1885
+ // Check if all VISIBLE items are measured
1886
+ const visibleCount = range.endIndex - range.startIndex;
1887
+ const allVisibleMeasured =
1888
+ visibleMeasuredCount === visibleCount && visibleCount > 0;
1883
1889
 
1884
- console.log(
1885
- `[VirtualView] Heights calc - measured: ${measuredCount}/${totalCount}, allMeasured: ${allMeasured}, totalHeight: ${height}`
1886
- );
1890
+ console.log(
1891
+ `[VirtualView] Heights calc - measured: ${measuredCount}/${totalCount}, visible measured: ${visibleMeasuredCount}/${visibleCount}, totalHeight: ${height}`
1892
+ );
1887
1893
 
1888
- return { totalHeight: height, positions: pos, allMeasured };
1889
- }, [
1890
- totalCount,
1891
- stateKey,
1892
- path.join("."),
1893
- itemHeight,
1894
- shadowUpdateTrigger,
1895
- ]);
1894
+ return {
1895
+ totalHeight: height,
1896
+ positions: pos,
1897
+ visibleMeasured: allVisibleMeasured,
1898
+ };
1899
+ }, [
1900
+ totalCount,
1901
+ stateKey,
1902
+ path.join("."),
1903
+ itemHeight,
1904
+ shadowUpdateTrigger,
1905
+ range, // Add range dependency
1906
+ ]);
1896
1907
 
1897
- // Memoize the virtualized slice of data.
1908
+ // Memoize the virtualized slice
1898
1909
  const virtualState = useMemo(() => {
1899
1910
  const start = Math.max(0, range.startIndex);
1900
1911
  const end = Math.min(totalCount, range.endIndex);
@@ -1914,12 +1925,11 @@ function createProxyHandler<T>(
1914
1925
  });
1915
1926
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1916
1927
 
1917
- // Simplified layout effect that waits for all measurements
1928
+ // Main layout effect
1918
1929
  useLayoutEffect(() => {
1919
1930
  const container = containerRef.current;
1920
1931
  if (!container) return;
1921
1932
 
1922
- // This function determines what's visible in the viewport.
1923
1933
  const updateVirtualRange = () => {
1924
1934
  if (!container) return;
1925
1935
  const { scrollTop } = container;
@@ -1943,7 +1953,6 @@ function createProxyHandler<T>(
1943
1953
  setRange({ startIndex, endIndex });
1944
1954
  };
1945
1955
 
1946
- // This function handles ONLY user-initiated scrolls.
1947
1956
  const handleUserScroll = () => {
1948
1957
  isLockedToBottomRef.current =
1949
1958
  container.scrollHeight -
@@ -1957,38 +1966,52 @@ function createProxyHandler<T>(
1957
1966
  passive: true,
1958
1967
  });
1959
1968
 
1960
- // Scroll to bottom logic
1969
+ // For stick to bottom: check conditions without triggering re-renders
1961
1970
  if (
1962
1971
  stickToBottom &&
1963
- !hasAttemptedScrollRef.current &&
1972
+ !hasScrolledToBottomRef.current &&
1964
1973
  totalCount > 0
1965
1974
  ) {
1966
- if (allMeasured) {
1967
- // All items measured, safe to scroll
1975
+ // Check current range without dependency
1976
+ const currentRange = range;
1977
+ const atEnd = currentRange.endIndex >= totalCount - 5; // Close to end
1978
+
1979
+ if (atEnd) {
1968
1980
  console.log(
1969
- `[VirtualView] All items measured, scrolling to bottom`
1981
+ `[VirtualView] At end of list, scrolling to bottom`
1970
1982
  );
1971
- hasAttemptedScrollRef.current = true;
1972
- container.scrollTo({
1973
- top: container.scrollHeight,
1974
- behavior: "auto",
1975
- });
1983
+ hasScrolledToBottomRef.current = true;
1984
+
1985
+ setTimeout(() => {
1986
+ const scrollTarget = container.scrollHeight + 1000;
1987
+ container.scrollTo({
1988
+ top: scrollTarget,
1989
+ behavior: "auto",
1990
+ });
1991
+ isLockedToBottomRef.current = true;
1992
+ }, 50);
1976
1993
  } else {
1977
- // Still waiting for measurements, try again next render
1994
+ // Jump close to the bottom
1978
1995
  console.log(
1979
- `[VirtualView] Waiting for all measurements before scroll`
1996
+ `[VirtualView] Jumping near bottom to trigger measurements`
1980
1997
  );
1998
+ const estimatedScrollPosition = Math.max(
1999
+ 0,
2000
+ (totalCount - 20) * itemHeight
2001
+ );
2002
+ container.scrollTo({
2003
+ top: estimatedScrollPosition,
2004
+ behavior: "auto",
2005
+ });
1981
2006
  }
1982
2007
  }
1983
2008
 
1984
- // Update the visible range on initial load.
1985
2009
  updateVirtualRange();
1986
2010
 
1987
- // Cleanup function
1988
2011
  return () => {
1989
2012
  container.removeEventListener("scroll", handleUserScroll);
1990
2013
  };
1991
- }, [totalCount, positions, stickToBottom, allMeasured]);
2014
+ }, [totalCount, positions, stickToBottom]); // Removed visibleMeasured and range.endIndex
1992
2015
 
1993
2016
  const scrollToBottom = useCallback(
1994
2017
  (behavior: ScrollBehavior = "smooth") => {