cogsbox-state 0.5.311 → 0.5.313

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.311",
3
+ "version": "0.5.313",
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
@@ -1816,20 +1816,18 @@ function createProxyHandler<T>(
1816
1816
 
1817
1817
  // --- State and Lock Management Refs ---
1818
1818
  const isLockedToBottomRef = useRef(stickToBottom);
1819
- const isInitialMountRef = useRef(true);
1819
+ // This ref tracks the item count to determine when to scroll smoothly vs instantly.
1820
+ const previousTotalCountRef = useRef(0);
1820
1821
 
1821
1822
  // Subscribe to shadow state changes for height updates
1822
1823
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1823
1824
 
1824
1825
  useEffect(() => {
1825
- // Subscribe to shadow state updates for this stateKey
1826
1826
  const unsubscribe = getGlobalStore
1827
1827
  .getState()
1828
1828
  .subscribeToShadowState(stateKey, () => {
1829
- // Force recalculation when shadow state updates
1830
1829
  setShadowUpdateTrigger((prev) => prev + 1);
1831
1830
  });
1832
-
1833
1831
  return unsubscribe;
1834
1832
  }, [stateKey]);
1835
1833
 
@@ -1839,21 +1837,19 @@ function createProxyHandler<T>(
1839
1837
  ) as any[];
1840
1838
  const totalCount = sourceArray.length;
1841
1839
 
1842
- // Calculate heights from shadow state
1840
+ // Calculate heights and positions from shadow state
1843
1841
  const { totalHeight, positions } = useMemo(() => {
1844
1842
  const shadowArray =
1845
1843
  getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
1846
1844
  [];
1847
1845
  let height = 0;
1848
1846
  const pos: number[] = [];
1849
-
1850
1847
  for (let i = 0; i < totalCount; i++) {
1851
1848
  pos[i] = height;
1852
1849
  const measuredHeight =
1853
1850
  shadowArray[i]?.virtualizer?.itemHeight;
1854
1851
  height += measuredHeight || itemHeight;
1855
1852
  }
1856
-
1857
1853
  return { totalHeight: height, positions: pos };
1858
1854
  }, [
1859
1855
  totalCount,
@@ -1881,30 +1877,23 @@ function createProxyHandler<T>(
1881
1877
  const container = containerRef.current;
1882
1878
  if (!container) return;
1883
1879
 
1880
+ const listGrew = totalCount > previousTotalCountRef.current;
1881
+
1884
1882
  const handleScroll = () => {
1885
1883
  const { scrollTop, clientHeight, scrollHeight } = container;
1886
-
1887
- // Determine if the user is at the bottom
1888
1884
  const isNowAtBottom =
1889
1885
  scrollHeight - scrollTop - clientHeight < 1;
1890
-
1891
- // If the user scrolls up, unlock. If they scroll back down, re-lock.
1892
1886
  isLockedToBottomRef.current = isNowAtBottom;
1893
1887
 
1894
- // Binary search for start index
1888
+ // ... (virtualization range calculation logic is unchanged)
1895
1889
  let low = 0,
1896
1890
  high = totalCount - 1;
1897
1891
  while (low <= high) {
1898
1892
  const mid = Math.floor((low + high) / 2);
1899
- if (positions[mid]! < scrollTop) {
1900
- low = mid + 1;
1901
- } else {
1902
- high = mid - 1;
1903
- }
1893
+ if (positions[mid]! < scrollTop) low = mid + 1;
1894
+ else high = mid - 1;
1904
1895
  }
1905
1896
  const startIndex = Math.max(0, high - overscan);
1906
-
1907
- // Find end index
1908
1897
  let endIndex = startIndex;
1909
1898
  const visibleEnd = scrollTop + clientHeight;
1910
1899
  while (
@@ -1914,7 +1903,6 @@ function createProxyHandler<T>(
1914
1903
  endIndex++;
1915
1904
  }
1916
1905
  endIndex = Math.min(totalCount, endIndex + overscan);
1917
-
1918
1906
  setRange((prevRange) => {
1919
1907
  if (
1920
1908
  prevRange.startIndex !== startIndex ||
@@ -1930,12 +1918,15 @@ function createProxyHandler<T>(
1930
1918
  passive: true,
1931
1919
  });
1932
1920
 
1933
- // --- REFINED STICK-TO-BOTTOM LOGIC ---
1921
+ // --- ROBUST STICK-TO-BOTTOM LOGIC ---
1934
1922
  if (stickToBottom && isLockedToBottomRef.current) {
1935
- // Use 'auto' for instant snap on first load, 'smooth' for subsequent updates.
1936
- const behavior = isInitialMountRef.current
1937
- ? "auto"
1938
- : "smooth";
1923
+ // If the list grew (a new message was added), we want a smooth scroll.
1924
+ // For all other cases (initial load, height recalculation), we MUST
1925
+ // use 'auto' to instantly snap to the bottom and prevent the "jump up".
1926
+ const behavior =
1927
+ listGrew && previousTotalCountRef.current > 0
1928
+ ? "smooth"
1929
+ : "auto";
1939
1930
 
1940
1931
  container.scrollTo({
1941
1932
  top: container.scrollHeight,
@@ -1943,15 +1934,9 @@ function createProxyHandler<T>(
1943
1934
  });
1944
1935
  }
1945
1936
 
1946
- // After the first layout effect, it's no longer the initial mount.
1947
- // queueMicrotask ensures this is set *after* the current render cycle.
1948
- queueMicrotask(() => {
1949
- if (isInitialMountRef.current) {
1950
- isInitialMountRef.current = false;
1951
- }
1952
- });
1937
+ // Update the count for the next run AFTER the scroll logic.
1938
+ previousTotalCountRef.current = totalCount;
1953
1939
 
1954
- // Run handleScroll once on setup to set initial range and lock status
1955
1940
  handleScroll();
1956
1941
 
1957
1942
  return () =>