cogsbox-state 0.5.331 → 0.5.333
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/dist/CogsState.jsx +701 -692
- package/dist/CogsState.jsx.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +96 -66
package/package.json
CHANGED
package/src/CogsState.tsx
CHANGED
|
@@ -1813,9 +1813,11 @@ function createProxyHandler<T>(
|
|
|
1813
1813
|
startIndex: 0,
|
|
1814
1814
|
endIndex: 10,
|
|
1815
1815
|
});
|
|
1816
|
+
|
|
1817
|
+
const isLockedToBottomRef = useRef(false); // Always start false
|
|
1816
1818
|
const [shadowUpdateTrigger, setShadowUpdateTrigger] = useState(0);
|
|
1819
|
+
const lastTotalCountRef = useRef(0); // Track previous count
|
|
1817
1820
|
|
|
1818
|
-
// Subscribe to shadow updates
|
|
1819
1821
|
useEffect(() => {
|
|
1820
1822
|
const unsubscribe = getGlobalStore
|
|
1821
1823
|
.getState()
|
|
@@ -1831,18 +1833,19 @@ function createProxyHandler<T>(
|
|
|
1831
1833
|
) as any[];
|
|
1832
1834
|
const totalCount = sourceArray.length;
|
|
1833
1835
|
|
|
1834
|
-
|
|
1835
|
-
const totalHeight = useMemo(() => {
|
|
1836
|
+
const { totalHeight, positions } = useMemo(() => {
|
|
1836
1837
|
const shadowArray =
|
|
1837
1838
|
getGlobalStore.getState().getShadowMetadata(stateKey, path) ||
|
|
1838
1839
|
[];
|
|
1839
1840
|
let height = 0;
|
|
1841
|
+
const pos: number[] = [];
|
|
1840
1842
|
for (let i = 0; i < totalCount; i++) {
|
|
1843
|
+
pos[i] = height;
|
|
1841
1844
|
const measuredHeight =
|
|
1842
1845
|
shadowArray[i]?.virtualizer?.itemHeight;
|
|
1843
1846
|
height += measuredHeight || itemHeight;
|
|
1844
1847
|
}
|
|
1845
|
-
return height;
|
|
1848
|
+
return { totalHeight: height, positions: pos };
|
|
1846
1849
|
}, [
|
|
1847
1850
|
totalCount,
|
|
1848
1851
|
stateKey,
|
|
@@ -1851,7 +1854,6 @@ function createProxyHandler<T>(
|
|
|
1851
1854
|
shadowUpdateTrigger,
|
|
1852
1855
|
]);
|
|
1853
1856
|
|
|
1854
|
-
// Create virtual slice
|
|
1855
1857
|
const virtualState = useMemo(() => {
|
|
1856
1858
|
const start = Math.max(0, range.startIndex);
|
|
1857
1859
|
const end = Math.min(totalCount, range.endIndex);
|
|
@@ -1866,46 +1868,102 @@ function createProxyHandler<T>(
|
|
|
1866
1868
|
});
|
|
1867
1869
|
}, [range.startIndex, range.endIndex, sourceArray, totalCount]);
|
|
1868
1870
|
|
|
1869
|
-
|
|
1870
|
-
useEffect(() => {
|
|
1871
|
+
useLayoutEffect(() => {
|
|
1871
1872
|
const container = containerRef.current;
|
|
1872
1873
|
if (!container) return;
|
|
1873
1874
|
|
|
1874
|
-
const
|
|
1875
|
-
|
|
1876
|
-
const
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1875
|
+
const updateVirtualRange = () => {
|
|
1876
|
+
if (!container) return;
|
|
1877
|
+
const { scrollTop } = container;
|
|
1878
|
+
let low = 0,
|
|
1879
|
+
high = totalCount - 1;
|
|
1880
|
+
while (low <= high) {
|
|
1881
|
+
const mid = Math.floor((low + high) / 2);
|
|
1882
|
+
if (positions[mid]! < scrollTop) low = mid + 1;
|
|
1883
|
+
else high = mid - 1;
|
|
1884
|
+
}
|
|
1885
|
+
const startIndex = Math.max(0, high - overscan);
|
|
1886
|
+
let endIndex = startIndex;
|
|
1887
|
+
const visibleEnd = scrollTop + container.clientHeight;
|
|
1888
|
+
while (
|
|
1889
|
+
endIndex < totalCount &&
|
|
1890
|
+
positions[endIndex]! < visibleEnd
|
|
1891
|
+
) {
|
|
1892
|
+
endIndex++;
|
|
1893
|
+
}
|
|
1894
|
+
endIndex = Math.min(totalCount, endIndex + overscan);
|
|
1895
|
+
setRange({ startIndex, endIndex });
|
|
1886
1896
|
};
|
|
1887
1897
|
|
|
1888
|
-
|
|
1889
|
-
|
|
1898
|
+
const handleUserScroll = () => {
|
|
1899
|
+
isLockedToBottomRef.current =
|
|
1900
|
+
container.scrollHeight -
|
|
1901
|
+
container.scrollTop -
|
|
1902
|
+
container.clientHeight <
|
|
1903
|
+
1;
|
|
1904
|
+
updateVirtualRange();
|
|
1905
|
+
};
|
|
1890
1906
|
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1907
|
+
container.addEventListener("scroll", handleUserScroll, {
|
|
1908
|
+
passive: true,
|
|
1909
|
+
});
|
|
1894
1910
|
|
|
1895
|
-
|
|
1896
|
-
if (
|
|
1897
|
-
|
|
1898
|
-
|
|
1911
|
+
// Handle scrolling
|
|
1912
|
+
if (stickToBottom && totalCount > 0) {
|
|
1913
|
+
const isInitialLoad =
|
|
1914
|
+
lastTotalCountRef.current === 0 && totalCount > 0;
|
|
1915
|
+
const hasNewItems = totalCount > lastTotalCountRef.current;
|
|
1916
|
+
|
|
1917
|
+
if (isInitialLoad) {
|
|
1918
|
+
// First load - always scroll to bottom
|
|
1919
|
+
setTimeout(() => {
|
|
1920
|
+
container.scrollTop = container.scrollHeight;
|
|
1921
|
+
isLockedToBottomRef.current = true;
|
|
1922
|
+
}, 1000); // Longer delay for initial load
|
|
1923
|
+
} else if (hasNewItems && isLockedToBottomRef.current) {
|
|
1924
|
+
// New items added and user is at bottom - smooth scroll
|
|
1925
|
+
setTimeout(() => {
|
|
1926
|
+
container.scrollTo({
|
|
1927
|
+
top: container.scrollHeight,
|
|
1928
|
+
behavior: "smooth",
|
|
1929
|
+
});
|
|
1930
|
+
}, 100);
|
|
1931
|
+
}
|
|
1932
|
+
// If user has scrolled up, don't auto-scroll
|
|
1899
1933
|
}
|
|
1900
|
-
}, []);
|
|
1901
1934
|
|
|
1902
|
-
|
|
1903
|
-
|
|
1935
|
+
updateVirtualRange();
|
|
1936
|
+
lastTotalCountRef.current = totalCount;
|
|
1937
|
+
|
|
1938
|
+
return () => {
|
|
1939
|
+
container.removeEventListener("scroll", handleUserScroll);
|
|
1940
|
+
};
|
|
1941
|
+
}, [totalCount, positions, stickToBottom]);
|
|
1942
|
+
|
|
1943
|
+
const scrollToBottom = useCallback(
|
|
1944
|
+
(behavior: ScrollBehavior = "smooth") => {
|
|
1904
1945
|
if (containerRef.current) {
|
|
1905
|
-
|
|
1946
|
+
isLockedToBottomRef.current = true;
|
|
1947
|
+
containerRef.current.scrollTo({
|
|
1948
|
+
top: containerRef.current.scrollHeight,
|
|
1949
|
+
behavior,
|
|
1950
|
+
});
|
|
1906
1951
|
}
|
|
1907
1952
|
},
|
|
1908
|
-
[
|
|
1953
|
+
[]
|
|
1954
|
+
);
|
|
1955
|
+
|
|
1956
|
+
const scrollToIndex = useCallback(
|
|
1957
|
+
(index: number, behavior: ScrollBehavior = "smooth") => {
|
|
1958
|
+
if (containerRef.current && positions[index] !== undefined) {
|
|
1959
|
+
isLockedToBottomRef.current = false;
|
|
1960
|
+
containerRef.current.scrollTo({
|
|
1961
|
+
top: positions[index],
|
|
1962
|
+
behavior,
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
},
|
|
1966
|
+
[positions]
|
|
1909
1967
|
);
|
|
1910
1968
|
|
|
1911
1969
|
const virtualizerProps = {
|
|
@@ -1921,10 +1979,7 @@ function createProxyHandler<T>(
|
|
|
1921
1979
|
},
|
|
1922
1980
|
list: {
|
|
1923
1981
|
style: {
|
|
1924
|
-
|
|
1925
|
-
top: `${range.startIndex * itemHeight}px`,
|
|
1926
|
-
left: 0,
|
|
1927
|
-
right: 0,
|
|
1982
|
+
transform: `translateY(${positions[range.startIndex] || 0}px)`,
|
|
1928
1983
|
},
|
|
1929
1984
|
},
|
|
1930
1985
|
};
|
|
@@ -2136,24 +2191,21 @@ function createProxyHandler<T>(
|
|
|
2136
2191
|
return null;
|
|
2137
2192
|
}
|
|
2138
2193
|
|
|
2139
|
-
const arrayLength = arrayToMap.length;
|
|
2140
2194
|
const indicesToMap =
|
|
2141
2195
|
meta?.validIndices ||
|
|
2142
|
-
Array.from({ length:
|
|
2196
|
+
Array.from({ length: arrayToMap.length }, (_, i) => i);
|
|
2143
2197
|
|
|
2144
2198
|
return indicesToMap.map((originalIndex, localIndex) => {
|
|
2145
2199
|
const item = arrayToMap[originalIndex];
|
|
2146
2200
|
const finalPath = [...path, originalIndex.toString()];
|
|
2147
2201
|
const setter = rebuildStateShape(item, finalPath, meta);
|
|
2148
2202
|
const itemComponentId = `${componentId}-${path.join(".")}-${originalIndex}`;
|
|
2149
|
-
const isLastItem = originalIndex === arrayLength - 1;
|
|
2150
2203
|
|
|
2151
2204
|
return createElement(CogsItemWrapper, {
|
|
2152
2205
|
key: originalIndex,
|
|
2153
2206
|
stateKey,
|
|
2154
2207
|
itemComponentId,
|
|
2155
2208
|
itemPath: finalPath,
|
|
2156
|
-
isLastItem, // Pass it here!
|
|
2157
2209
|
children: callbackfn(
|
|
2158
2210
|
item,
|
|
2159
2211
|
setter,
|
|
@@ -2883,25 +2935,21 @@ export function $cogsSignalStore(proxy: {
|
|
|
2883
2935
|
);
|
|
2884
2936
|
return createElement("text", {}, String(value));
|
|
2885
2937
|
}
|
|
2886
|
-
|
|
2887
2938
|
function CogsItemWrapper({
|
|
2888
2939
|
stateKey,
|
|
2889
2940
|
itemComponentId,
|
|
2890
2941
|
itemPath,
|
|
2891
|
-
isLastItem,
|
|
2892
2942
|
children,
|
|
2893
2943
|
}: {
|
|
2894
2944
|
stateKey: string;
|
|
2895
2945
|
itemComponentId: string;
|
|
2896
2946
|
itemPath: string[];
|
|
2897
|
-
isLastItem: boolean;
|
|
2898
2947
|
children: React.ReactNode;
|
|
2899
2948
|
}) {
|
|
2900
2949
|
// This hook handles the re-rendering when the item's own data changes.
|
|
2901
2950
|
const [, forceUpdate] = useState({});
|
|
2902
2951
|
// This hook measures the element.
|
|
2903
|
-
const [
|
|
2904
|
-
const scrollRef = useRef<HTMLDivElement | null>(null);
|
|
2952
|
+
const [ref, bounds] = useMeasure();
|
|
2905
2953
|
// This ref prevents sending the same height update repeatedly.
|
|
2906
2954
|
const lastReportedHeight = useRef<number | null>(null);
|
|
2907
2955
|
|
|
@@ -2947,25 +2995,7 @@ function CogsItemWrapper({
|
|
|
2947
2995
|
}
|
|
2948
2996
|
};
|
|
2949
2997
|
}, [stateKey, itemComponentId, itemPath.join(".")]);
|
|
2950
|
-
|
|
2951
|
-
if (isLastItem && scrollRef.current) {
|
|
2952
|
-
setTimeout(() => {
|
|
2953
|
-
scrollRef.current?.scrollIntoView({
|
|
2954
|
-
behavior: "smooth",
|
|
2955
|
-
block: "end",
|
|
2956
|
-
});
|
|
2957
|
-
}, 50);
|
|
2958
|
-
}
|
|
2959
|
-
}, [isLastItem]);
|
|
2998
|
+
|
|
2960
2999
|
// The rendered output is a simple div that gets measured.
|
|
2961
|
-
return
|
|
2962
|
-
<div
|
|
2963
|
-
ref={(el) => {
|
|
2964
|
-
measureRef(el);
|
|
2965
|
-
scrollRef.current = el;
|
|
2966
|
-
}}
|
|
2967
|
-
>
|
|
2968
|
-
{children}
|
|
2969
|
-
</div>
|
|
2970
|
-
);
|
|
3000
|
+
return <div ref={ref}>{children}</div>;
|
|
2971
3001
|
}
|