cogsbox-state 0.5.305 → 0.5.307

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.305",
3
+ "version": "0.5.307",
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,10 +1814,21 @@ function createProxyHandler<T>(
1814
1814
  endIndex: 10,
1815
1815
  });
1816
1816
 
1817
+ // Force re-render when heights change
1818
+ const [, forceUpdate] = useState({});
1819
+
1817
1820
  const isAtBottomRef = useRef(stickToBottom);
1818
1821
  const previousTotalCountRef = useRef(0);
1819
1822
  const isInitialMountRef = useRef(true);
1820
- const hasMeasurementsRef = useRef(false);
1823
+ const previousTotalHeightRef = useRef(0);
1824
+
1825
+ // Subscribe to shadow state changes
1826
+ useEffect(() => {
1827
+ const unsubscribe = getGlobalStore
1828
+ .getState()
1829
+ .subscribeToShadowState(stateKey, () => forceUpdate({}));
1830
+ return unsubscribe;
1831
+ }, [stateKey]);
1821
1832
 
1822
1833
  const sourceArray = getGlobalStore().getNestedState(
1823
1834
  stateKey,
@@ -1832,20 +1843,39 @@ function createProxyHandler<T>(
1832
1843
  [];
1833
1844
  let height = 0;
1834
1845
  const pos: number[] = [];
1835
- let hasMeasurements = false;
1836
1846
 
1837
1847
  for (let i = 0; i < totalCount; i++) {
1838
1848
  pos[i] = height;
1839
1849
  const measuredHeight =
1840
1850
  shadowArray[i]?.virtualizer?.itemHeight;
1841
- if (measuredHeight) hasMeasurements = true;
1842
1851
  height += measuredHeight || itemHeight;
1843
1852
  }
1844
1853
 
1845
- hasMeasurementsRef.current = hasMeasurements;
1846
1854
  return { totalHeight: height, positions: pos };
1847
1855
  }, [totalCount, stateKey, path.join("."), itemHeight]);
1848
1856
 
1857
+ // Adjust scroll when height changes while at bottom
1858
+ useLayoutEffect(() => {
1859
+ const container = containerRef.current;
1860
+ if (!container) return;
1861
+
1862
+ const heightChanged =
1863
+ totalHeight !== previousTotalHeightRef.current;
1864
+ previousTotalHeightRef.current = totalHeight;
1865
+
1866
+ // If we're at bottom and height changed, maintain bottom position
1867
+ if (
1868
+ heightChanged &&
1869
+ isAtBottomRef.current &&
1870
+ !isInitialMountRef.current
1871
+ ) {
1872
+ container.scrollTo({
1873
+ top: container.scrollHeight,
1874
+ behavior: "auto",
1875
+ });
1876
+ }
1877
+ }, [totalHeight]);
1878
+
1849
1879
  const virtualState = useMemo(() => {
1850
1880
  const start = Math.max(0, range.startIndex);
1851
1881
  const end = Math.min(totalCount, range.endIndex);
@@ -1912,17 +1942,36 @@ function createProxyHandler<T>(
1912
1942
  passive: true,
1913
1943
  });
1914
1944
 
1915
- // Handle stick to bottom
1916
- if (stickToBottom && !isInitialMountRef.current) {
1917
- // Only auto-scroll for new items after initial mount
1918
- if (wasAtBottom && listGrew) {
1919
- requestAnimationFrame(() => {
1920
- container.scrollTo({
1921
- top: container.scrollHeight,
1922
- behavior: "smooth",
1945
+ // Handle stick to bottom for initial mount only
1946
+ if (
1947
+ stickToBottom &&
1948
+ isInitialMountRef.current &&
1949
+ totalCount > 0
1950
+ ) {
1951
+ // Set flag first to prevent height adjustment from interfering
1952
+ isAtBottomRef.current = true;
1953
+ // Wait for next frame to ensure everything is rendered
1954
+ requestAnimationFrame(() => {
1955
+ if (containerRef.current) {
1956
+ containerRef.current.scrollTo({
1957
+ top: containerRef.current.scrollHeight,
1958
+ behavior: "auto",
1923
1959
  });
1960
+ isInitialMountRef.current = false;
1961
+ }
1962
+ });
1963
+ } else if (
1964
+ !isInitialMountRef.current &&
1965
+ wasAtBottom &&
1966
+ listGrew
1967
+ ) {
1968
+ // New items added and we were at bottom - stay at bottom
1969
+ requestAnimationFrame(() => {
1970
+ container.scrollTo({
1971
+ top: container.scrollHeight,
1972
+ behavior: "smooth",
1924
1973
  });
1925
- }
1974
+ });
1926
1975
  }
1927
1976
 
1928
1977
  // Run handleScroll once to set initial range
@@ -1932,30 +1981,6 @@ function createProxyHandler<T>(
1932
1981
  container.removeEventListener("scroll", handleScroll);
1933
1982
  }, [totalCount, positions, overscan, stickToBottom]);
1934
1983
 
1935
- // Separate effect for initial scroll to bottom
1936
- useEffect(() => {
1937
- if (
1938
- stickToBottom &&
1939
- isInitialMountRef.current &&
1940
- totalCount > 0 &&
1941
- hasMeasurementsRef.current
1942
- ) {
1943
- const container = containerRef.current;
1944
- if (container) {
1945
- // Use rAF to ensure DOM is updated
1946
- requestAnimationFrame(() => {
1947
- requestAnimationFrame(() => {
1948
- container.scrollTo({
1949
- top: container.scrollHeight,
1950
- behavior: "auto",
1951
- });
1952
- isInitialMountRef.current = false;
1953
- });
1954
- });
1955
- }
1956
- }
1957
- }, [stickToBottom, totalCount, positions]); // positions change triggers this when measurements come in
1958
-
1959
1984
  const scrollToBottom = useCallback(
1960
1985
  (behavior: ScrollBehavior = "smooth") => {
1961
1986
  if (containerRef.current) {