cogsbox-state 0.5.403 → 0.5.404

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.403",
3
+ "version": "0.5.404",
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
@@ -1804,6 +1804,7 @@ function createProxyHandler<T>(
1804
1804
  };
1805
1805
  }
1806
1806
  // Simplified useVirtualView approach
1807
+ // Optimal approach - replace the useVirtualView implementation
1807
1808
  if (prop === "useVirtualView") {
1808
1809
  return (
1809
1810
  options: VirtualViewOptions
@@ -1820,9 +1821,12 @@ function createProxyHandler<T>(
1820
1821
  startIndex: 0,
1821
1822
  endIndex: 10,
1822
1823
  });
1823
- const isUserScrolling = useRef(false);
1824
- const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
1825
1824
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1825
+ const isUserScrollingRef = useRef(false);
1826
+ const shouldStickToBottomRef = useRef(true);
1827
+ const scrollToBottomIntervalRef = useRef<NodeJS.Timeout | null>(
1828
+ null
1829
+ );
1826
1830
 
1827
1831
  // Subscribe to shadow state updates
1828
1832
  useEffect(() => {
@@ -1877,38 +1881,71 @@ function createProxyHandler<T>(
1877
1881
  });
1878
1882
  }, [range.startIndex, range.endIndex, sourceArray, totalCount]);
1879
1883
 
1880
- // Simple scroll to bottom when items are added
1884
+ // Handle auto-scroll to bottom
1881
1885
  useEffect(() => {
1882
1886
  if (!stickToBottom || !containerRef.current || totalCount === 0)
1883
1887
  return;
1888
+ if (!shouldStickToBottomRef.current) return;
1884
1889
 
1885
- const container = containerRef.current;
1886
- const isNearBottom =
1887
- container.scrollHeight -
1888
- container.scrollTop -
1889
- container.clientHeight <
1890
- 100;
1891
-
1892
- // If user is near bottom or we're auto-scrolling, scroll to bottom
1893
- if (isNearBottom || !isUserScrolling.current) {
1894
- // Clear any pending scroll
1895
- if (scrollTimeoutRef.current) {
1896
- clearTimeout(scrollTimeoutRef.current);
1897
- }
1890
+ // Clear any existing interval
1891
+ if (scrollToBottomIntervalRef.current) {
1892
+ clearInterval(scrollToBottomIntervalRef.current);
1893
+ }
1898
1894
 
1899
- // Delay scroll to allow items to render and measure
1900
- scrollTimeoutRef.current = setTimeout(() => {
1901
- if (containerRef.current) {
1902
- containerRef.current.scrollTo({
1903
- top: containerRef.current.scrollHeight,
1904
- behavior: "smooth",
1905
- });
1906
- }
1907
- }, 100);
1895
+ // For initial load or big jumps, show the end immediately
1896
+ const jumpThreshold = 50;
1897
+ const isInitialLoad = range.endIndex < jumpThreshold;
1898
+ const isBigJump = totalCount > range.endIndex + jumpThreshold;
1899
+
1900
+ if (isInitialLoad || isBigJump) {
1901
+ // Jump to show the last items immediately
1902
+ setRange({
1903
+ startIndex: Math.max(0, totalCount - 20),
1904
+ endIndex: totalCount,
1905
+ });
1908
1906
  }
1907
+
1908
+ // Keep scrolling to bottom until we're actually there
1909
+ let attempts = 0;
1910
+ const maxAttempts = 50; // 5 seconds max
1911
+
1912
+ scrollToBottomIntervalRef.current = setInterval(() => {
1913
+ const container = containerRef.current;
1914
+ if (!container) return;
1915
+
1916
+ attempts++;
1917
+
1918
+ const { scrollTop, scrollHeight, clientHeight } = container;
1919
+ const currentBottom = scrollTop + clientHeight;
1920
+ const actualBottom = scrollHeight;
1921
+ const isAtBottom = actualBottom - currentBottom < 5;
1922
+
1923
+ console.log(
1924
+ `Scroll attempt ${attempts}: currentBottom=${currentBottom}, actualBottom=${actualBottom}, isAtBottom=${isAtBottom}`
1925
+ );
1926
+
1927
+ if (isAtBottom || attempts >= maxAttempts) {
1928
+ clearInterval(scrollToBottomIntervalRef.current!);
1929
+ scrollToBottomIntervalRef.current = null;
1930
+ console.log(
1931
+ isAtBottom ? "Reached bottom!" : "Timeout - giving up"
1932
+ );
1933
+ } else {
1934
+ // Use instant scroll, not smooth
1935
+ container.scrollTop = container.scrollHeight;
1936
+ }
1937
+ }, 100);
1938
+
1939
+ // Cleanup
1940
+ return () => {
1941
+ if (scrollToBottomIntervalRef.current) {
1942
+ clearInterval(scrollToBottomIntervalRef.current);
1943
+ scrollToBottomIntervalRef.current = null;
1944
+ }
1945
+ };
1909
1946
  }, [totalCount, stickToBottom]);
1910
1947
 
1911
- // Handle scroll events
1948
+ // Handle user scroll
1912
1949
  useEffect(() => {
1913
1950
  const container = containerRef.current;
1914
1951
  if (!container) return;
@@ -1916,21 +1953,27 @@ function createProxyHandler<T>(
1916
1953
  let scrollTimeout: NodeJS.Timeout;
1917
1954
 
1918
1955
  const handleScroll = () => {
1919
- // Clear existing timeout
1920
- clearTimeout(scrollTimeout);
1956
+ if (scrollToBottomIntervalRef.current) {
1957
+ // Stop auto-scrolling if user scrolls
1958
+ clearInterval(scrollToBottomIntervalRef.current);
1959
+ scrollToBottomIntervalRef.current = null;
1960
+ }
1921
1961
 
1922
- // Mark as user scrolling
1923
- isUserScrolling.current = true;
1962
+ const { scrollTop, scrollHeight, clientHeight } = container;
1963
+ const isAtBottom =
1964
+ scrollHeight - scrollTop - clientHeight < 10;
1965
+
1966
+ // Update whether we should stick to bottom
1967
+ shouldStickToBottomRef.current = isAtBottom;
1924
1968
 
1925
- // Reset after scrolling stops
1969
+ // Mark as user scrolling
1970
+ clearTimeout(scrollTimeout);
1971
+ isUserScrollingRef.current = true;
1926
1972
  scrollTimeout = setTimeout(() => {
1927
- isUserScrolling.current = false;
1973
+ isUserScrollingRef.current = false;
1928
1974
  }, 150);
1929
1975
 
1930
1976
  // Update visible range
1931
- const { scrollTop, clientHeight } = container;
1932
-
1933
- // Find first visible item
1934
1977
  let startIndex = 0;
1935
1978
  for (let i = 0; i < positions.length; i++) {
1936
1979
  if (positions[i]! > scrollTop - itemHeight * overscan) {
@@ -1939,7 +1982,6 @@ function createProxyHandler<T>(
1939
1982
  }
1940
1983
  }
1941
1984
 
1942
- // Find last visible item
1943
1985
  let endIndex = startIndex;
1944
1986
  const viewportEnd = scrollTop + clientHeight;
1945
1987
  for (let i = startIndex; i < positions.length; i++) {
@@ -1958,9 +2000,7 @@ function createProxyHandler<T>(
1958
2000
  container.addEventListener("scroll", handleScroll, {
1959
2001
  passive: true,
1960
2002
  });
1961
-
1962
- // Initial range calculation
1963
- handleScroll();
2003
+ handleScroll(); // Initial calculation
1964
2004
 
1965
2005
  return () => {
1966
2006
  container.removeEventListener("scroll", handleScroll);
@@ -1968,22 +2008,12 @@ function createProxyHandler<T>(
1968
2008
  };
1969
2009
  }, [positions, totalCount, itemHeight, overscan]);
1970
2010
 
1971
- // Cleanup scroll timeout on unmount
1972
- useEffect(() => {
1973
- return () => {
1974
- if (scrollTimeoutRef.current) {
1975
- clearTimeout(scrollTimeoutRef.current);
1976
- }
1977
- };
1978
- }, []);
1979
-
1980
2011
  const scrollToBottom = useCallback(
1981
- (behavior: ScrollBehavior = "smooth") => {
2012
+ (behavior: ScrollBehavior = "auto") => {
2013
+ shouldStickToBottomRef.current = true;
1982
2014
  if (containerRef.current) {
1983
- containerRef.current.scrollTo({
1984
- top: containerRef.current.scrollHeight,
1985
- behavior,
1986
- });
2015
+ containerRef.current.scrollTop =
2016
+ containerRef.current.scrollHeight;
1987
2017
  }
1988
2018
  },
1989
2019
  []