cogsbox-state 0.5.374 → 0.5.376

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.374",
3
+ "version": "0.5.376",
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,6 +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);
1830
1831
 
1831
1832
  const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
1832
1833
 
@@ -2000,39 +2001,71 @@ function createProxyHandler<T>(
2000
2001
  const container = containerRef.current;
2001
2002
  if (!container) return;
2002
2003
 
2004
+ // We no longer need a manual threshold. The `positions` array is our threshold.
2005
+
2003
2006
  const handleUserScroll = () => {
2004
- // This is the core logic you wanted.
2007
+ // Essential check to ignore our own programmatic scrolls.
2005
2008
  if (isProgrammaticScroll.current) {
2006
2009
  return;
2007
2010
  }
2011
+
2012
+ const { scrollTop, clientHeight } = container;
2013
+
2014
+ // --- Part 1: Quick state update logic (still important) ---
2008
2015
  const isAtBottom =
2009
2016
  container.scrollHeight -
2010
- container.scrollTop -
2017
+ scrollTop -
2011
2018
  container.clientHeight <
2012
2019
  1;
2013
2020
 
2014
2021
  if (!isAtBottom) {
2015
- console.log(
2016
- "USER ACTION: Scrolled up -> IDLE_NOT_AT_BOTTOM"
2017
- );
2018
- shouldNotScroll.current = true;
2019
- setStatus("IDLE_NOT_AT_BOTTOM");
2022
+ if (status !== "IDLE_NOT_AT_BOTTOM") {
2023
+ setStatus("IDLE_NOT_AT_BOTTOM");
2024
+ }
2020
2025
  } else {
2021
- shouldNotScroll.current = false;
2026
+ if (status === "IDLE_NOT_AT_BOTTOM") {
2027
+ setStatus("LOCKED_AT_BOTTOM");
2028
+ }
2022
2029
  }
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.
2023
2033
 
2024
- // We always update the range, regardless of state.
2025
- // This is the full, non-placeholder function.
2026
- const { scrollTop, clientHeight } = container;
2027
- let low = 0,
2028
- high = totalCount - 1;
2034
+ // --- Part 2: Index-Based Invalidation (The real optimization) ---
2035
+
2036
+ // Find the index of the item at the top of the viewport.
2037
+ // This binary search is extremely fast.
2038
+ let high = totalCount - 1;
2039
+ let low = 0;
2040
+ let potentialTopIndex = 0;
2029
2041
  while (low <= high) {
2030
2042
  const mid = Math.floor((low + high) / 2);
2031
- if (positions[mid]! < scrollTop) low = mid + 1;
2032
- else high = mid - 1;
2043
+ if (positions[mid]! < scrollTop) {
2044
+ potentialTopIndex = mid;
2045
+ low = mid + 1;
2046
+ } else {
2047
+ high = mid - 1;
2048
+ }
2049
+ }
2050
+
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;
2033
2061
  }
2034
- const startIndex = Math.max(0, high - overscan);
2035
- let endIndex = startIndex;
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;
2036
2069
  const visibleEnd = scrollTop + clientHeight;
2037
2070
  while (
2038
2071
  endIndex < totalCount &&
@@ -2040,8 +2073,9 @@ function createProxyHandler<T>(
2040
2073
  ) {
2041
2074
  endIndex++;
2042
2075
  }
2076
+
2043
2077
  setRange({
2044
- startIndex,
2078
+ startIndex: potentialStartIndex,
2045
2079
  endIndex: Math.min(totalCount, endIndex + overscan),
2046
2080
  });
2047
2081
  };
@@ -2051,7 +2085,7 @@ function createProxyHandler<T>(
2051
2085
  });
2052
2086
  return () =>
2053
2087
  container.removeEventListener("scroll", handleUserScroll);
2054
- }, [totalCount, positions, status]); // Depends on status to know if it should break the lock
2088
+ }, [totalCount, positions, status, range.startIndex]); // Add range.startIndex!
2055
2089
 
2056
2090
  const scrollToBottom = useCallback(() => {
2057
2091
  console.log(