cogsbox-state 0.5.376 → 0.5.378

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.376",
3
+ "version": "0.5.378",
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
@@ -1827,8 +1827,7 @@ function createProxyHandler<T>(
1827
1827
  const isProgrammaticScroll = useRef(false);
1828
1828
  const prevTotalCountRef = useRef(0);
1829
1829
  const prevDepsRef = useRef(dependencies);
1830
- const lastScrollTopRef = useRef(0);
1831
-
1830
+ const lastUpdateAtScrollTop = useRef(0);
1832
1831
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1833
1832
 
1834
1833
  useEffect(() => {
@@ -1934,11 +1933,13 @@ function createProxyHandler<T>(
1934
1933
  console.log(
1935
1934
  "ACTION (GETTING_HEIGHTS): Setting range to end and starting loop."
1936
1935
  );
1936
+
1937
1937
  setRange({
1938
1938
  startIndex: Math.max(0, totalCount - 10 - overscan),
1939
1939
  endIndex: totalCount,
1940
1940
  });
1941
1941
 
1942
+ let intervalId: NodeJS.Timeout;
1942
1943
  intervalId = setInterval(() => {
1943
1944
  const lastItemIndex = totalCount - 1;
1944
1945
  const shadowArray =
@@ -1950,15 +1951,36 @@ function createProxyHandler<T>(
1950
1951
 
1951
1952
  if (lastItemHeight > 0) {
1952
1953
  clearInterval(intervalId);
1953
- if (!shouldNotScroll.current) {
1954
- console.log(
1955
- "ACTION (GETTING_HEIGHTS): Measurement success -> SCROLLING_TO_BOTTOM"
1956
- );
1957
1954
 
1958
- setStatus("SCROLLING_TO_BOTTOM");
1955
+ if (!shouldNotScroll.current) {
1956
+ const prevCount = prevTotalCountRef.current;
1957
+ const addedItems = totalCount - prevCount;
1958
+ const smallAddition = addedItems > 0 && addedItems <= 3;
1959
+
1960
+ if (smallAddition) {
1961
+ const addedHeight =
1962
+ positions[totalCount - 1]! -
1963
+ (positions[prevCount] ?? 0);
1964
+ container.scrollBy({
1965
+ top: addedHeight,
1966
+ behavior: "smooth",
1967
+ });
1968
+
1969
+ console.log(
1970
+ "ACTION (GETTING_HEIGHTS): Small addition -> LOCKED_AT_BOTTOM"
1971
+ );
1972
+ setStatus("LOCKED_AT_BOTTOM");
1973
+ } else {
1974
+ console.log(
1975
+ "ACTION (GETTING_HEIGHTS): Large change -> SCROLLING_TO_BOTTOM"
1976
+ );
1977
+ setStatus("SCROLLING_TO_BOTTOM");
1978
+ }
1959
1979
  }
1960
1980
  }
1961
1981
  }, 100);
1982
+
1983
+ return () => clearInterval(intervalId);
1962
1984
  } else if (status === "SCROLLING_TO_BOTTOM") {
1963
1985
  console.log(
1964
1986
  "ACTION (SCROLLING_TO_BOTTOM): Executing scroll."
@@ -1988,84 +2010,51 @@ function createProxyHandler<T>(
1988
2010
  return () => clearTimeout(timeoutId);
1989
2011
  }
1990
2012
 
1991
- // If status is IDLE_NOT_AT_BOTTOM or LOCKED_AT_BOTTOM, we do nothing here.
1992
- // The scroll has either finished or been disabled by the user.
1993
-
1994
2013
  return () => {
1995
2014
  if (intervalId) clearInterval(intervalId);
1996
2015
  };
1997
2016
  }, [status, totalCount, positions]);
1998
2017
 
1999
- // --- 3. USER INTERACTION & RANGE UPDATER ---
2000
2018
  useEffect(() => {
2001
2019
  const container = containerRef.current;
2002
2020
  if (!container) return;
2003
2021
 
2004
- // We no longer need a manual threshold. The `positions` array is our threshold.
2022
+ const scrollThreshold = itemHeight;
2005
2023
 
2006
2024
  const handleUserScroll = () => {
2007
- // Essential check to ignore our own programmatic scrolls.
2008
2025
  if (isProgrammaticScroll.current) {
2009
2026
  return;
2010
2027
  }
2011
2028
 
2012
- const { scrollTop, clientHeight } = container;
2013
-
2014
- // --- Part 1: Quick state update logic (still important) ---
2015
- const isAtBottom =
2016
- container.scrollHeight -
2017
- scrollTop -
2018
- container.clientHeight <
2019
- 1;
2020
-
2021
- if (!isAtBottom) {
2022
- if (status !== "IDLE_NOT_AT_BOTTOM") {
2023
- setStatus("IDLE_NOT_AT_BOTTOM");
2024
- }
2025
- } else {
2026
- if (status === "IDLE_NOT_AT_BOTTOM") {
2027
- setStatus("LOCKED_AT_BOTTOM");
2028
- }
2029
+ const scrollTop = container.scrollTop;
2030
+ if (
2031
+ Math.abs(scrollTop - lastUpdateAtScrollTop.current) <
2032
+ scrollThreshold
2033
+ ) {
2034
+ return;
2029
2035
  }
2030
- // NOTE: We've removed `shouldNotScroll` as its purpose is now better
2031
- // served by the state machine itself. The state `IDLE_NOT_AT_BOTTOM`
2032
- // already tells us the user has scrolled up.
2033
2036
 
2034
- // --- Part 2: Index-Based Invalidation (The real optimization) ---
2037
+ console.log(
2038
+ `Threshold passed at ${scrollTop}px. Recalculating range...`
2039
+ );
2035
2040
 
2036
- // Find the index of the item at the top of the viewport.
2037
- // This binary search is extremely fast.
2041
+ // NOW we do the expensive work.
2042
+ const { clientHeight } = container;
2038
2043
  let high = totalCount - 1;
2039
2044
  let low = 0;
2040
- let potentialTopIndex = 0;
2045
+ let topItemIndex = 0;
2041
2046
  while (low <= high) {
2042
2047
  const mid = Math.floor((low + high) / 2);
2043
2048
  if (positions[mid]! < scrollTop) {
2044
- potentialTopIndex = mid;
2049
+ topItemIndex = mid;
2045
2050
  low = mid + 1;
2046
2051
  } else {
2047
2052
  high = mid - 1;
2048
2053
  }
2049
2054
  }
2050
2055
 
2051
- // Compare the potential new start index with the current one.
2052
- // Remember to account for the overscan!
2053
- const potentialStartIndex = Math.max(
2054
- 0,
2055
- potentialTopIndex - overscan
2056
- );
2057
- if (potentialStartIndex === range.startIndex) {
2058
- // The visible items haven't changed, so we do nothing.
2059
- // This is the core of the optimization.
2060
- return;
2061
- }
2062
-
2063
- // --- Part 3: If we're here, we MUST update the range ---
2064
- console.log(
2065
- `Index changed from ${range.startIndex} to ${potentialStartIndex}. Updating range.`
2066
- );
2067
-
2068
- let endIndex = potentialStartIndex;
2056
+ const startIndex = Math.max(0, topItemIndex - overscan);
2057
+ let endIndex = startIndex;
2069
2058
  const visibleEnd = scrollTop + clientHeight;
2070
2059
  while (
2071
2060
  endIndex < totalCount &&
@@ -2075,9 +2064,12 @@ function createProxyHandler<T>(
2075
2064
  }
2076
2065
 
2077
2066
  setRange({
2078
- startIndex: potentialStartIndex,
2067
+ startIndex,
2079
2068
  endIndex: Math.min(totalCount, endIndex + overscan),
2080
2069
  });
2070
+
2071
+ // Finally, we record that we did the work at THIS scroll position.
2072
+ lastUpdateAtScrollTop.current = scrollTop;
2081
2073
  };
2082
2074
 
2083
2075
  container.addEventListener("scroll", handleUserScroll, {
@@ -2085,7 +2077,7 @@ function createProxyHandler<T>(
2085
2077
  });
2086
2078
  return () =>
2087
2079
  container.removeEventListener("scroll", handleUserScroll);
2088
- }, [totalCount, positions, status, range.startIndex]); // Add range.startIndex!
2080
+ }, [totalCount, positions, itemHeight, overscan, status]);
2089
2081
 
2090
2082
  const scrollToBottom = useCallback(() => {
2091
2083
  console.log(