@gechiui/compose
Version:
GeChiUI higher-order components (HOCs).
164 lines (141 loc) • 7.91 kB
JavaScript
/**
* 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