diff --git a/.size-limit.json b/.size-limit.json index 8ff941c58..58bd4d38b 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -3,7 +3,7 @@ "name": "Total", "path": "lib/index.mjs", "import": "*", - "limit": "5.20 kB" + "limit": "5.25 kB" }, { "name": "VList", diff --git a/src/core/scroller.ts b/src/core/scroller.ts index a3a6e7584..973603fd2 100644 --- a/src/core/scroller.ts +++ b/src/core/scroller.ts @@ -7,6 +7,7 @@ import { ACTION_SCROLL_END, UPDATE_SIZE, ACTION_MANUAL_SCROLL, + SCROLL_IDLE, } from "./store"; import { ScrollToIndexAlign } from "./types"; import { debounce, throttle, timeout, clamp } from "./utils"; @@ -19,7 +20,7 @@ const createOnWheel = ( onScrollStopped: () => void ) => { return throttle((e: WheelEvent) => { - if (!store._getIsScrolling()) { + if (store._getScrollDirection() === SCROLL_IDLE) { // Scroll start should be detected with scroll event return; } diff --git a/src/core/store.ts b/src/core/store.ts index 0eae55518..42f0cb918 100644 --- a/src/core/store.ts +++ b/src/core/store.ts @@ -39,7 +39,7 @@ const SUBPIXEL_THRESHOLD = 1.5; // 0.5 * 3 export const SCROLL_IDLE = 0; export const SCROLL_DOWN = 1; export const SCROLL_UP = 2; -type ScrollDirection = +export type ScrollDirection = | typeof SCROLL_IDLE | typeof SCROLL_DOWN | typeof SCROLL_UP; @@ -69,7 +69,7 @@ type Subscriber = () => void; export const UPDATE_SCROLL = 0b00001; export const UPDATE_SIZE = 0b00010; export const UPDATE_JUMP = 0b00100; -export const UPDATE_IS_SCROLLING = 0b01000; +export const UPDATE_SCROLL_DIRECTION = 0b01000; export const UPDATE_SCROLL_WITH_EVENT = 0b10000; export type VirtualStore = { @@ -82,7 +82,7 @@ export type VirtualStore = { _getItemsLength(): number; _getScrollOffset(): number; _getScrollOffsetMax(): number; - _getIsScrolling(): boolean; + _getScrollDirection(): ScrollDirection; _getViewportSize(): number; _getCorrectedScrollSize(): number; _getJumpCount(): number; @@ -122,12 +122,8 @@ export const createVirtualStore = ( const updateScrollDirection = (dir: ScrollDirection): boolean => { const prev = _scrollDirection; _scrollDirection = dir; - // Return true if scrolling is just started or stopped - return ( - _scrollDirection !== prev && - (_scrollDirection === SCROLL_IDLE || prev === SCROLL_IDLE) - ); + return _scrollDirection !== prev; }; return { @@ -183,8 +179,8 @@ export const createVirtualStore = ( return scrollOffset; }, _getScrollOffsetMax: getScrollOffsetMax, - _getIsScrolling() { - return _scrollDirection !== SCROLL_IDLE; + _getScrollDirection() { + return _scrollDirection; }, _getViewportSize() { return viewportSize; @@ -325,7 +321,7 @@ export const createVirtualStore = ( !_isManualScrolling ) { if (updateScrollDirection(delta < 0 ? SCROLL_UP : SCROLL_DOWN)) { - mutated += UPDATE_IS_SCROLLING; + mutated += UPDATE_SCROLL_DIRECTION; } } @@ -348,7 +344,7 @@ export const createVirtualStore = ( } case ACTION_SCROLL_END: { if (updateScrollDirection(SCROLL_IDLE)) { - mutated = UPDATE_IS_SCROLLING; + mutated = UPDATE_SCROLL_DIRECTION; } _isShifting = _isManualScrolling = false; break; diff --git a/src/react/VGrid.tsx b/src/react/VGrid.tsx index 005ce8b10..912722adf 100644 --- a/src/react/VGrid.tsx +++ b/src/react/VGrid.tsx @@ -10,18 +10,24 @@ import { } from "react"; import { ACTION_ITEMS_LENGTH_CHANGE, - UPDATE_IS_SCROLLING, + UPDATE_SCROLL_DIRECTION, UPDATE_JUMP, UPDATE_SCROLL, UPDATE_SIZE, VirtualStore, createVirtualStore, + SCROLL_IDLE, } from "../core/store"; import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect"; import { useSelector } from "./useSelector"; -import { max, min, values } from "../core/utils"; +import { values } from "../core/utils"; import { createScroller } from "../core/scroller"; -import { emptyComponents, refKey } from "./utils"; +import { + clampEndIndex, + clampStartIndex, + emptyComponents, + refKey, +} from "./utils"; import { useStatic } from "./useStatic"; import { CustomViewportComponent, @@ -315,15 +321,15 @@ export const VGrid = forwardRef( hStore._getRange, UPDATE_SCROLL + UPDATE_SIZE ); - const verticalScrolling = useSelector( + const vScrollDirection = useSelector( vStore, - vStore._getIsScrolling, - UPDATE_IS_SCROLLING + vStore._getScrollDirection, + UPDATE_SCROLL_DIRECTION ); - const horizontalScrolling = useSelector( + const hScrollDirection = useSelector( hStore, - hStore._getIsScrolling, - UPDATE_IS_SCROLLING + hStore._getScrollDirection, + UPDATE_SCROLL_DIRECTION ); const vJumpCount = useSelector(vStore, vStore._getJumpCount, UPDATE_JUMP); const hJumpCount = useSelector(hStore, hStore._getJumpCount, UPDATE_JUMP); @@ -340,6 +346,8 @@ export const VGrid = forwardRef( true ); const rootRef = useRef(null); + const vScrolling = vScrollDirection !== SCROLL_IDLE; + const hScrolling = hScrollDirection !== SCROLL_IDLE; useIsomorphicLayoutEffect(() => { const root = rootRef[refKey]!; @@ -413,10 +421,29 @@ export const VGrid = forwardRef( }; }, [children]); - const overscanedStartRowIndex = max(startRowIndex - overscan, 0); - const overscanedEndRowIndex = min(endRowIndex + overscan, rowCount - 1); - const overscanedStartColIndex = max(startColIndex - overscan, 0); - const overscanedEndColIndex = min(endColIndex + overscan, colCount - 1); + const overscanedStartRowIndex = clampStartIndex( + startRowIndex, + overscan, + vScrollDirection + ); + const overscanedEndRowIndex = clampEndIndex( + endRowIndex, + overscan, + vScrollDirection, + rowCount + ); + const overscanedStartColIndex = clampStartIndex( + startColIndex, + overscan, + hScrollDirection + ); + const overscanedEndColIndex = clampEndIndex( + endColIndex, + overscan, + hScrollDirection, + colCount + ); + const items = useMemo(() => { const res: ReactElement[] = []; for (let i = overscanedStartRowIndex; i <= overscanedEndRowIndex; i++) { @@ -451,7 +478,7 @@ export const VGrid = forwardRef( ref={rootRef} width={width} height={height} - scrolling={verticalScrolling || horizontalScrolling} + scrolling={vScrolling || hScrolling} attrs={useMemo( () => ({ ...viewportAttrs, diff --git a/src/react/VList.tsx b/src/react/VList.tsx index 45de91aae..74441a350 100644 --- a/src/react/VList.tsx +++ b/src/react/VList.tsx @@ -13,14 +13,22 @@ import { createVirtualStore, UPDATE_SIZE, UPDATE_JUMP, - UPDATE_IS_SCROLLING, + UPDATE_SCROLL_DIRECTION, UPDATE_SCROLL, + SCROLL_IDLE, } from "../core/store"; import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect"; import { useSelector } from "./useSelector"; -import { exists, max, min, values } from "../core/utils"; +import { exists, values } from "../core/utils"; import { createScroller } from "../core/scroller"; -import { MayHaveKey, emptyComponents, flattenChildren, refKey } from "./utils"; +import { + MayHaveKey, + clampEndIndex, + clampStartIndex, + emptyComponents, + flattenChildren, + refKey, +} from "./utils"; import { useStatic } from "./useStatic"; import { useLatestRef } from "./useLatestRef"; import { createResizer } from "../core/resizer"; @@ -227,10 +235,10 @@ export const VList = forwardRef( store._getRange, UPDATE_SCROLL + UPDATE_SIZE ); - const scrolling = useSelector( + const scrollDirection = useSelector( store, - store._getIsScrolling, - UPDATE_IS_SCROLLING + store._getScrollDirection, + UPDATE_SCROLL_DIRECTION ); const jumpCount = useSelector(store, store._getJumpCount, UPDATE_JUMP); const scrollSize = useSelector( @@ -240,6 +248,7 @@ export const VList = forwardRef( true ); const rootRef = useRef(null); + const scrolling = scrollDirection !== SCROLL_IDLE; useIsomorphicLayoutEffect(() => { const root = rootRef[refKey]!; @@ -298,8 +307,17 @@ export const VList = forwardRef( [] ); - const overscanedStartIndex = max(startIndex - overscan, 0); - const overscanedEndIndex = min(endIndex + overscan, count - 1); + const overscanedStartIndex = clampStartIndex( + startIndex, + overscan, + scrollDirection + ); + const overscanedEndIndex = clampEndIndex( + endIndex, + overscan, + scrollDirection, + count + ); const items = useMemo(() => { const res: ReactElement[] = []; for (let i = overscanedStartIndex; i <= overscanedEndIndex; i++) { diff --git a/src/react/WVList.tsx b/src/react/WVList.tsx index af4a1ec49..18789172b 100644 --- a/src/react/WVList.tsx +++ b/src/react/WVList.tsx @@ -9,17 +9,25 @@ import { } from "react"; import { ACTION_ITEMS_LENGTH_CHANGE, - UPDATE_IS_SCROLLING, + UPDATE_SCROLL_DIRECTION, UPDATE_JUMP, UPDATE_SCROLL, UPDATE_SIZE, createVirtualStore, + SCROLL_IDLE, } from "../core/store"; import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect"; import { useSelector } from "./useSelector"; -import { exists, max, min, values } from "../core/utils"; +import { exists, values } from "../core/utils"; import { createWindowScroller } from "../core/scroller"; -import { MayHaveKey, emptyComponents, flattenChildren, refKey } from "./utils"; +import { + MayHaveKey, + clampEndIndex, + clampStartIndex, + emptyComponents, + flattenChildren, + refKey, +} from "./utils"; import { useStatic } from "./useStatic"; import { useLatestRef } from "./useLatestRef"; import { createWindowResizer } from "../core/resizer"; @@ -173,10 +181,10 @@ export const WVList = forwardRef( store._getRange, UPDATE_SCROLL + UPDATE_SIZE ); - const scrolling = useSelector( + const scrollDirection = useSelector( store, - store._getIsScrolling, - UPDATE_IS_SCROLLING + store._getScrollDirection, + UPDATE_SCROLL_DIRECTION ); const jumpCount = useSelector(store, store._getJumpCount, UPDATE_JUMP); const scrollSize = useSelector( @@ -186,6 +194,7 @@ export const WVList = forwardRef( true ); const rootRef = useRef(null); + const scrolling = scrollDirection !== SCROLL_IDLE; useIsomorphicLayoutEffect(() => { const root = rootRef[refKey]!; @@ -228,8 +237,17 @@ export const WVList = forwardRef( [] ); - const overscanedStartIndex = max(startIndex - overscan, 0); - const overscanedEndIndex = min(endIndex + overscan, count - 1); + const overscanedStartIndex = clampStartIndex( + startIndex, + overscan, + scrollDirection + ); + const overscanedEndIndex = clampEndIndex( + endIndex, + overscan, + scrollDirection, + count + ); const items = useMemo(() => { const res: ReactElement[] = []; for (let i = overscanedStartIndex; i <= overscanedEndIndex; i++) { diff --git a/src/react/utils.ts b/src/react/utils.ts index c6c9d96a0..5e3e8652f 100644 --- a/src/react/utils.ts +++ b/src/react/utils.ts @@ -1,5 +1,6 @@ import { ReactElement, ReactFragment, ReactNode } from "react"; -import { exists, isArray } from "../core/utils"; +import { exists, max, min, isArray } from "../core/utils"; +import { SCROLL_DOWN, SCROLL_UP, ScrollDirection } from "../core/store"; export const refKey = "current"; @@ -35,3 +36,26 @@ export const flattenChildren = (children: ReactNode): ItemElement[] => { }; export type MayHaveKey = { key?: React.Key }; + +export const clampStartIndex = ( + startIndex: number, + overscan: number, + scrollDirection: ScrollDirection +): number => { + return max( + startIndex - (scrollDirection === SCROLL_DOWN ? 1 : max(1, overscan)), + 0 + ); +}; + +export const clampEndIndex = ( + endIndex: number, + overscan: number, + scrollDirection: ScrollDirection, + count: number +): number => { + return min( + endIndex + (scrollDirection === SCROLL_UP ? 1 : max(1, overscan)), + count - 1 + ); +};