cogsbox-state 0.5.375 → 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.375",
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
@@ -2001,19 +2001,17 @@ function createProxyHandler<T>(
2001
2001
  const container = containerRef.current;
2002
2002
  if (!container) return;
2003
2003
 
2004
- // Define our threshold. Updating every half-item feels very smooth.
2005
- // You can adjust this value. Using a full itemHeight also works well.
2006
- const scrollThreshold = itemHeight / 2;
2004
+ // We no longer need a manual threshold. The `positions` array is our threshold.
2007
2005
 
2008
2006
  const handleUserScroll = () => {
2009
- // Exit early for programmatic scrolls. This is still essential.
2007
+ // Essential check to ignore our own programmatic scrolls.
2010
2008
  if (isProgrammaticScroll.current) {
2011
2009
  return;
2012
2010
  }
2013
2011
 
2014
- const scrollTop = container.scrollTop;
2012
+ const { scrollTop, clientHeight } = container;
2015
2013
 
2016
- // --- Part 1: Handle state changes immediately (this is cheap) ---
2014
+ // --- Part 1: Quick state update logic (still important) ---
2017
2015
  const isAtBottom =
2018
2016
  container.scrollHeight -
2019
2017
  scrollTop -
@@ -2022,51 +2020,52 @@ function createProxyHandler<T>(
2022
2020
 
2023
2021
  if (!isAtBottom) {
2024
2022
  if (status !== "IDLE_NOT_AT_BOTTOM") {
2025
- console.log(
2026
- "USER ACTION: Scrolled up -> IDLE_NOT_AT_BOTTOM"
2027
- );
2028
2023
  setStatus("IDLE_NOT_AT_BOTTOM");
2029
2024
  }
2030
- shouldNotScroll.current = true;
2031
2025
  } else {
2032
2026
  if (status === "IDLE_NOT_AT_BOTTOM") {
2033
- console.log(
2034
- "USER ACTION: Scrolled back to bottom -> LOCKED_AT_BOTTOM"
2035
- );
2036
2027
  setStatus("LOCKED_AT_BOTTOM");
2037
2028
  }
2038
- shouldNotScroll.current = false;
2039
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.
2040
2033
 
2041
- // --- Part 2: The "Smarter" Threshold Check ---
2034
+ // --- Part 2: Index-Based Invalidation (The real optimization) ---
2042
2035
 
2043
- // Check if the scroll distance is greater than our threshold.
2044
- // Math.abs() handles both scrolling up and down.
2045
- if (
2046
- Math.abs(scrollTop - lastScrollTopRef.current) <
2047
- scrollThreshold
2048
- ) {
2049
- // Not scrolled far enough, do nothing.
2050
- return;
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;
2041
+ while (low <= high) {
2042
+ const mid = Math.floor((low + high) / 2);
2043
+ if (positions[mid]! < scrollTop) {
2044
+ potentialTopIndex = mid;
2045
+ low = mid + 1;
2046
+ } else {
2047
+ high = mid - 1;
2048
+ }
2051
2049
  }
2052
2050
 
2053
- // If we've passed the threshold, update our reference for the next check.
2054
- lastScrollTopRef.current = scrollTop;
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
+ }
2055
2062
 
2056
- console.log("Threshold passed: Updating virtual range."); // For debugging
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
+ );
2057
2067
 
2058
- // --- Part 3: Run the expensive calculation ---
2059
- // This code now only runs when it's actually needed.
2060
- const { clientHeight } = container;
2061
- let low = 0,
2062
- high = totalCount - 1;
2063
- while (low <= high) {
2064
- const mid = Math.floor((low + high) / 2);
2065
- if (positions[mid]! < scrollTop) low = mid + 1;
2066
- else high = mid - 1;
2067
- }
2068
- const startIndex = Math.max(0, high - overscan);
2069
- let endIndex = startIndex;
2068
+ let endIndex = potentialStartIndex;
2070
2069
  const visibleEnd = scrollTop + clientHeight;
2071
2070
  while (
2072
2071
  endIndex < totalCount &&
@@ -2074,8 +2073,9 @@ function createProxyHandler<T>(
2074
2073
  ) {
2075
2074
  endIndex++;
2076
2075
  }
2076
+
2077
2077
  setRange({
2078
- startIndex,
2078
+ startIndex: potentialStartIndex,
2079
2079
  endIndex: Math.min(totalCount, endIndex + overscan),
2080
2080
  });
2081
2081
  };
@@ -2085,7 +2085,7 @@ function createProxyHandler<T>(
2085
2085
  });
2086
2086
  return () =>
2087
2087
  container.removeEventListener("scroll", handleUserScroll);
2088
- }, [totalCount, positions, status, itemHeight]); // Added itemHeight to deps
2088
+ }, [totalCount, positions, status, range.startIndex]); // Add range.startIndex!
2089
2089
 
2090
2090
  const scrollToBottom = useCallback(() => {
2091
2091
  console.log(