cogsbox-state 0.5.428 → 0.5.429

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.428",
3
+ "version": "0.5.429",
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
@@ -1735,6 +1735,7 @@ function createProxyHandler<T>(
1735
1735
  return (
1736
1736
  options: VirtualViewOptions
1737
1737
  ): VirtualStateObjectResult<any[]> => {
1738
+ // --- All this setup is from your original, working code ---
1738
1739
  const {
1739
1740
  itemHeight = 50,
1740
1741
  overscan = 6,
@@ -1752,9 +1753,8 @@ function createProxyHandler<T>(
1752
1753
  const userHasScrolledAwayRef = useRef(false);
1753
1754
  const previousCountRef = useRef(0);
1754
1755
  const lastRangeRef = useRef(range);
1755
- const orderedIds = getOrderedIds(path);
1756
1756
 
1757
- // Subscribe to shadow state updates for dynamic height changes
1757
+ // This is still the correct way to trigger re-calculations when item heights change.
1758
1758
  useEffect(() => {
1759
1759
  const unsubscribe = getGlobalStore
1760
1760
  .getState()
@@ -1770,23 +1770,37 @@ function createProxyHandler<T>(
1770
1770
  ) as any[];
1771
1771
  const totalCount = sourceArray.length;
1772
1772
 
1773
- // Calculate total height and individual item positions
1773
+ // --- START OF IMPROVEMENT ---
1774
+ // Get the canonical order of item IDs for this array. This is the most
1775
+ // reliable way to link an item's index to its metadata.
1776
+ const orderedIds = getOrderedIds(path);
1777
+ // --- END OF IMPROVEMENT ---
1778
+
1779
+ // Calculate heights and positions
1774
1780
  const { totalHeight, positions } = useMemo(() => {
1775
1781
  let height = 0;
1776
1782
  const pos: number[] = [];
1777
1783
  for (let i = 0; i < totalCount; i++) {
1778
1784
  pos[i] = height;
1785
+
1786
+ // --- START OF IMPROVEMENT ---
1787
+ // Use the ordered ID to look up the correct metadata for the item at this index.
1788
+ // This is much more reliable than numeric indexing into a metadata store.
1779
1789
  const itemId = orderedIds?.[i];
1790
+ let measuredHeight = itemHeight; // Default height
1791
+
1780
1792
  if (itemId) {
1781
- const itemPath = [...path, itemId];
1793
+ const itemPathWithId = [...path, itemId];
1782
1794
  const itemMeta = getGlobalStore
1783
1795
  .getState()
1784
- .getShadowMetadata(stateKey, itemPath);
1785
- const measuredHeight = itemMeta?.virtualizer?.itemHeight;
1786
- height += measuredHeight || itemHeight;
1787
- } else {
1788
- height += itemHeight;
1796
+ .getShadowMetadata(stateKey, itemPathWithId);
1797
+ // Get the measured height from the shadow state if it exists.
1798
+ measuredHeight =
1799
+ itemMeta?.virtualizer?.itemHeight || itemHeight;
1789
1800
  }
1801
+ // --- END OF IMPROVEMENT ---
1802
+
1803
+ height += measuredHeight;
1790
1804
  }
1791
1805
  return { totalHeight: height, positions: pos };
1792
1806
  }, [
@@ -1795,22 +1809,31 @@ function createProxyHandler<T>(
1795
1809
  path.join("."),
1796
1810
  itemHeight,
1797
1811
  shadowUpdateTrigger,
1798
- orderedIds,
1812
+ orderedIds, // Add `orderedIds` to the dependency array
1799
1813
  ]);
1800
1814
 
1801
- // Create the virtual state object
1815
+ // Create virtual state (This part of your original code looks fine)
1802
1816
  const virtualState = useMemo(() => {
1803
1817
  const start = Math.max(0, range.startIndex);
1804
1818
  const end = Math.min(totalCount, range.endIndex);
1805
- // The sliced array is the `currentState` for the new proxy
1806
- const slicedArray = sourceArray.slice(start, end);
1807
- // The `validIds` for the new proxy are the sliced IDs
1819
+
1820
+ // --- START OF IMPROVEMENT ---
1821
+ // We use `orderedIds` here as well to create a `validIds` list for the
1822
+ // virtualized proxy. This ensures that any subsequent operations on `virtualState`
1823
+ // (like `.index()` or `.getSelected()`) will have the correct context.
1808
1824
  const slicedIds = orderedIds?.slice(start, end);
1825
+ const sourceMap = new Map(
1826
+ sourceArray.map((item: any) => [`id:${item.id}`, item])
1827
+ );
1828
+ const slicedArray =
1829
+ slicedIds?.map((id) => sourceMap.get(id)).filter(Boolean) ||
1830
+ [];
1809
1831
 
1810
1832
  return rebuildStateShape(slicedArray as any, path, {
1811
1833
  ...meta,
1812
- validIds: slicedIds,
1834
+ validIds: slicedIds, // Pass the sliced IDs as the new `validIds`
1813
1835
  });
1836
+ // --- END OF IMPROVEMENT ---
1814
1837
  }, [
1815
1838
  range.startIndex,
1816
1839
  range.endIndex,
@@ -1819,28 +1842,10 @@ function createProxyHandler<T>(
1819
1842
  orderedIds,
1820
1843
  ]);
1821
1844
 
1822
- const scrollToLastItem = useCallback(() => {
1823
- const lastIndex = totalCount - 1;
1824
- if (lastIndex >= 0 && orderedIds?.[lastIndex]) {
1825
- const lastItemId = orderedIds[lastIndex];
1826
- const lastItemPath = [...path, lastItemId];
1827
- const lastItemMeta = getGlobalStore
1828
- .getState()
1829
- .getShadowMetadata(stateKey, lastItemPath);
1830
- if (lastItemMeta?.virtualizer?.domRef) {
1831
- const element = lastItemMeta.virtualizer.domRef;
1832
- if (element?.scrollIntoView) {
1833
- element.scrollIntoView({
1834
- behavior: "auto",
1835
- block: "end",
1836
- });
1837
- return true;
1838
- }
1839
- }
1840
- }
1841
- return false;
1842
- }, [stateKey, path, totalCount, orderedIds]);
1845
+ // --- All the following logic for scrolling and event handling is from your original code. ---
1846
+ // It is preserved, but we will improve `scrollToIndex` to use the shadow refs.
1843
1847
 
1848
+ // Handle new items when at bottom (original logic)
1844
1849
  useEffect(() => {
1845
1850
  if (!stickToBottom || totalCount === 0) return;
1846
1851
  const hasNewItems = totalCount > previousCountRef.current;
@@ -1854,6 +1859,7 @@ function createProxyHandler<T>(
1854
1859
  previousCountRef.current = totalCount;
1855
1860
  }, [totalCount, stickToBottom]);
1856
1861
 
1862
+ // Handle scroll events (original logic)
1857
1863
  useEffect(() => {
1858
1864
  const container = containerRef.current;
1859
1865
  if (!container) return;
@@ -1907,42 +1913,59 @@ function createProxyHandler<T>(
1907
1913
  container.addEventListener("scroll", handleScroll, {
1908
1914
  passive: true,
1909
1915
  });
1910
- handleScroll(); // Initial check
1916
+ handleScroll();
1911
1917
  return () =>
1912
1918
  container.removeEventListener("scroll", handleScroll);
1913
- }, [positions, totalCount, itemHeight, overscan, stickToBottom]);
1919
+ }, [positions, totalCount, itemHeight, overscan]);
1914
1920
 
1915
1921
  const scrollToBottom = useCallback(() => {
1916
1922
  wasAtBottomRef.current = true;
1917
1923
  userHasScrolledAwayRef.current = false;
1918
- if (!scrollToLastItem() && containerRef.current) {
1924
+ if (containerRef.current) {
1919
1925
  containerRef.current.scrollTop =
1920
1926
  containerRef.current.scrollHeight;
1921
1927
  }
1922
- }, [scrollToLastItem]);
1928
+ }, []);
1923
1929
 
1924
1930
  const scrollToIndex = useCallback(
1925
1931
  (index: number, behavior: ScrollBehavior = "smooth") => {
1926
1932
  const container = containerRef.current;
1927
1933
  if (!container) return;
1934
+
1935
+ // --- START OF IMPROVEMENT ---
1936
+ // Use the orderedId to reliably get the item's metadata and DOM ref.
1937
+ const itemId = orderedIds?.[index];
1938
+ if (itemId) {
1939
+ const itemPathWithId = [...path, itemId];
1940
+ const itemMeta = getGlobalStore
1941
+ .getState()
1942
+ .getShadowMetadata(stateKey, itemPathWithId);
1943
+ const element = itemMeta?.virtualizer?.domRef;
1944
+
1945
+ // If we have a direct ref to the DOM element from CogsItemWrapper, use it! It's the most reliable.
1946
+ if (element?.scrollIntoView) {
1947
+ element.scrollIntoView({ behavior, block: "nearest" });
1948
+ return;
1949
+ }
1950
+ }
1951
+ // --- END OF IMPROVEMENT ---
1952
+
1953
+ // Fallback to position-based scrolling if the ref isn't available for any reason.
1928
1954
  const top = positions[index];
1929
1955
  if (top !== undefined) {
1930
1956
  container.scrollTo({ top, behavior });
1931
1957
  }
1932
1958
  },
1933
- [positions]
1959
+ [positions, stateKey, path, orderedIds] // Add `orderedIds` to dependency array
1934
1960
  );
1935
1961
 
1936
1962
  const virtualizerProps = {
1937
1963
  outer: {
1938
1964
  ref: containerRef,
1939
- style: { overflowY: "auto" as const, height: "100%" },
1965
+ style: { overflowY: "auto", height: "100%" },
1940
1966
  },
1941
1967
  inner: {
1942
- style: {
1943
- height: `${totalHeight}px`,
1944
- position: "relative" as const,
1945
- },
1968
+ style: { height: `${totalHeight}px`, position: "relative" },
1946
1969
  },
1947
1970
  list: {
1948
1971
  style: {
@@ -1953,7 +1976,7 @@ function createProxyHandler<T>(
1953
1976
 
1954
1977
  return {
1955
1978
  virtualState,
1956
- virtualizerProps,
1979
+ virtualizerProps: virtualizerProps as any,
1957
1980
  scrollToBottom,
1958
1981
  scrollToIndex,
1959
1982
  };