UNPKG

@gechiui/compose

Version:
164 lines (141 loc) 7.91 kB
/** * External dependencies */ import { debounce } from 'lodash'; /** * GeChiUI dependencies */ import { useState, useLayoutEffect } from '@gechiui/element'; import { getScrollContainer } from '@gechiui/dom'; import { PAGEUP, PAGEDOWN, HOME, END } from '@gechiui/keycodes'; const DEFAULT_INIT_WINDOW_SIZE = 30; /** * @typedef {Object} GCFixedWindowList * * @property {number} visibleItems Items visible in the current viewport * @property {number} start Start index of the window * @property {number} end End index of the window * @property {(index:number)=>boolean} itemInView Returns true if item is in the window */ /** * @typedef {Object} GCFixedWindowListOptions * * @property {number} [windowOverscan] Renders windowOverscan number of items before and after the calculated visible window. * @property {boolean} [useWindowing] When false avoids calculating the window size * @property {number} [initWindowSize] Initial window size to use on first render before we can calculate the window size. */ /** * * @param {import('react').RefObject<HTMLElement>} elementRef Used to find the closest scroll container that contains element. * @param { number } itemHeight Fixed item height in pixels * @param { number } totalItems Total items in list * @param { GCFixedWindowListOptions } [options] Options object * @return {[ GCFixedWindowList, setFixedListWindow:(nextWindow:GCFixedWindowList)=>void]} Array with the fixed window list and setter */ export default function useFixedWindowList(elementRef, itemHeight, totalItems, options) { var _options$initWindowSi, _options$useWindowing; const initWindowSize = (_options$initWindowSi = options === null || options === void 0 ? void 0 : options.initWindowSize) !== null && _options$initWindowSi !== void 0 ? _options$initWindowSi : DEFAULT_INIT_WINDOW_SIZE; const useWindowing = (_options$useWindowing = options === null || options === void 0 ? void 0 : options.useWindowing) !== null && _options$useWindowing !== void 0 ? _options$useWindowing : true; const [fixedListWindow, setFixedListWindow] = useState({ visibleItems: initWindowSize, start: 0, end: initWindowSize, itemInView: ( /** @type {number} */ index) => { return index >= 0 && index <= initWindowSize; } }); useLayoutEffect(() => { var _scrollContainer$owne, _scrollContainer$owne2, _scrollContainer$owne3, _scrollContainer$owne4; if (!useWindowing) { return; } const scrollContainer = getScrollContainer(elementRef.current); const measureWindow = ( /** @type {boolean | undefined} */ initRender) => { var _options$windowOversc; if (!scrollContainer) { return; } const visibleItems = Math.ceil(scrollContainer.clientHeight / itemHeight); // Aim to keep opening list view fast, afterward we can optimize for scrolling const windowOverscan = initRender ? visibleItems : (_options$windowOversc = options === null || options === void 0 ? void 0 : options.windowOverscan) !== null && _options$windowOversc !== void 0 ? _options$windowOversc : visibleItems; const firstViewableIndex = Math.floor(scrollContainer.scrollTop / itemHeight); const start = Math.max(0, firstViewableIndex - windowOverscan); const end = Math.min(totalItems - 1, firstViewableIndex + visibleItems + windowOverscan); setFixedListWindow(lastWindow => { const nextWindow = { visibleItems, start, end, itemInView: ( /** @type {number} */ index) => { return start <= index && index <= end; } }; if (lastWindow.start !== nextWindow.start || lastWindow.end !== nextWindow.end || lastWindow.visibleItems !== nextWindow.visibleItems) { return nextWindow; } return lastWindow; }); }; measureWindow(true); const debounceMeasureList = debounce(() => { measureWindow(); }, 16); scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.addEventListener('scroll', debounceMeasureList); scrollContainer === null || scrollContainer === void 0 ? void 0 : (_scrollContainer$owne = scrollContainer.ownerDocument) === null || _scrollContainer$owne === void 0 ? void 0 : (_scrollContainer$owne2 = _scrollContainer$owne.defaultView) === null || _scrollContainer$owne2 === void 0 ? void 0 : _scrollContainer$owne2.addEventListener('resize', debounceMeasureList); scrollContainer === null || scrollContainer === void 0 ? void 0 : (_scrollContainer$owne3 = scrollContainer.ownerDocument) === null || _scrollContainer$owne3 === void 0 ? void 0 : (_scrollContainer$owne4 = _scrollContainer$owne3.defaultView) === null || _scrollContainer$owne4 === void 0 ? void 0 : _scrollContainer$owne4.addEventListener('resize', debounceMeasureList); return () => { var _scrollContainer$owne5, _scrollContainer$owne6; scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.removeEventListener('scroll', debounceMeasureList); scrollContainer === null || scrollContainer === void 0 ? void 0 : (_scrollContainer$owne5 = scrollContainer.ownerDocument) === null || _scrollContainer$owne5 === void 0 ? void 0 : (_scrollContainer$owne6 = _scrollContainer$owne5.defaultView) === null || _scrollContainer$owne6 === void 0 ? void 0 : _scrollContainer$owne6.removeEventListener('resize', debounceMeasureList); }; }, [itemHeight, elementRef, totalItems]); useLayoutEffect(() => { var _scrollContainer$owne7, _scrollContainer$owne8; if (!useWindowing) { return; } const scrollContainer = getScrollContainer(elementRef.current); const handleKeyDown = ( /** @type {KeyboardEvent} */ event) => { switch (event.keyCode) { case HOME: { return scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.scrollTo({ top: 0 }); } case END: { return scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.scrollTo({ top: totalItems * itemHeight }); } case PAGEUP: { return scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.scrollTo({ top: scrollContainer.scrollTop - fixedListWindow.visibleItems * itemHeight }); } case PAGEDOWN: { return scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.scrollTo({ top: scrollContainer.scrollTop + fixedListWindow.visibleItems * itemHeight }); } } }; scrollContainer === null || scrollContainer === void 0 ? void 0 : (_scrollContainer$owne7 = scrollContainer.ownerDocument) === null || _scrollContainer$owne7 === void 0 ? void 0 : (_scrollContainer$owne8 = _scrollContainer$owne7.defaultView) === null || _scrollContainer$owne8 === void 0 ? void 0 : _scrollContainer$owne8.addEventListener('keydown', handleKeyDown); return () => { var _scrollContainer$owne9, _scrollContainer$owne10; scrollContainer === null || scrollContainer === void 0 ? void 0 : (_scrollContainer$owne9 = scrollContainer.ownerDocument) === null || _scrollContainer$owne9 === void 0 ? void 0 : (_scrollContainer$owne10 = _scrollContainer$owne9.defaultView) === null || _scrollContainer$owne10 === void 0 ? void 0 : _scrollContainer$owne10.removeEventListener('keydown', handleKeyDown); }; }, [totalItems, itemHeight, elementRef, fixedListWindow.visibleItems]); return [fixedListWindow, setFixedListWindow]; } //# sourceMappingURL=index.js.map