@wordpress/compose
Version:
WordPress higher-order components (HOCs).
142 lines (136 loc) • 6.06 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useFixedWindowList;
var _element = require("@wordpress/element");
var _dom = require("@wordpress/dom");
var _keycodes = require("@wordpress/keycodes");
var _debounce = require("../../utils/debounce");
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
const DEFAULT_INIT_WINDOW_SIZE = 30;
/**
* @typedef {Object} WPFixedWindowList
*
* @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} WPFixedWindowListOptions
*
* @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.
* @property {any} [expandedState] Used to recalculate the window size when the expanded state of a list changes.
*/
/**
*
* @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 { WPFixedWindowListOptions } [options] Options object
* @return {[ WPFixedWindowList, setFixedListWindow:(nextWindow:WPFixedWindowList)=>void]} Array with the fixed window list and setter
*/
function useFixedWindowList(elementRef, itemHeight, totalItems, options) {
var _options$initWindowSi, _options$useWindowing;
const initWindowSize = (_options$initWindowSi = options?.initWindowSize) !== null && _options$initWindowSi !== void 0 ? _options$initWindowSi : DEFAULT_INIT_WINDOW_SIZE;
const useWindowing = (_options$useWindowing = options?.useWindowing) !== null && _options$useWindowing !== void 0 ? _options$useWindowing : true;
const [fixedListWindow, setFixedListWindow] = (0, _element.useState)({
visibleItems: initWindowSize,
start: 0,
end: initWindowSize,
itemInView: (/** @type {number} */index) => {
return index >= 0 && index <= initWindowSize;
}
});
(0, _element.useLayoutEffect)(() => {
if (!useWindowing) {
return;
}
const scrollContainer = (0, _dom.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?.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 = (0, _debounce.debounce)(() => {
measureWindow();
}, 16);
scrollContainer?.addEventListener('scroll', debounceMeasureList);
scrollContainer?.ownerDocument?.defaultView?.addEventListener('resize', debounceMeasureList);
scrollContainer?.ownerDocument?.defaultView?.addEventListener('resize', debounceMeasureList);
return () => {
scrollContainer?.removeEventListener('scroll', debounceMeasureList);
scrollContainer?.ownerDocument?.defaultView?.removeEventListener('resize', debounceMeasureList);
};
}, [itemHeight, elementRef, totalItems, options?.expandedState, options?.windowOverscan, useWindowing]);
(0, _element.useLayoutEffect)(() => {
if (!useWindowing) {
return;
}
const scrollContainer = (0, _dom.getScrollContainer)(elementRef.current);
const handleKeyDown = (/** @type {KeyboardEvent} */event) => {
switch (event.keyCode) {
case _keycodes.HOME:
{
return scrollContainer?.scrollTo({
top: 0
});
}
case _keycodes.END:
{
return scrollContainer?.scrollTo({
top: totalItems * itemHeight
});
}
case _keycodes.PAGEUP:
{
return scrollContainer?.scrollTo({
top: scrollContainer.scrollTop - fixedListWindow.visibleItems * itemHeight
});
}
case _keycodes.PAGEDOWN:
{
return scrollContainer?.scrollTo({
top: scrollContainer.scrollTop + fixedListWindow.visibleItems * itemHeight
});
}
}
};
scrollContainer?.ownerDocument?.defaultView?.addEventListener('keydown', handleKeyDown);
return () => {
scrollContainer?.ownerDocument?.defaultView?.removeEventListener('keydown', handleKeyDown);
};
}, [totalItems, itemHeight, elementRef, fixedListWindow.visibleItems, useWindowing, options?.expandedState]);
return [fixedListWindow, setFixedListWindow];
}
//# sourceMappingURL=index.js.map