UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

417 lines (416 loc) 13.8 kB
"use client"; import _pushInstanceProperty from "core-js-pure/stable/instance/push.js"; import React, { useContext, useEffect, useRef, useState } from 'react'; import useMountEffect from "../../shared/helpers/useMountEffect.js"; import { warn, dispatchCustomElementEvent } from "../../shared/component-helper.js"; import withComponentMarkers from "../../shared/helpers/withComponentMarkers.js"; import Context from "../../shared/Context.js"; import Button from "../button/Button.js"; import { preparePageElement, isTrElement, PaginationIndicator } from "./PaginationHelpers.js"; import PaginationContext from "./PaginationContext.js"; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; export default function InfinityScroller({ children = null }) { const { pagination } = useContext(PaginationContext); const hideIndicator = pagination.hideProgressIndicator; const useLoadButton = pagination.useLoadButton; const paginationRef = useRef(pagination); paginationRef.current = pagination; const callOnUnmountRef = useRef([]); const startupTimeoutRef = useRef(null); const bufferTimeoutRef = useRef(null); const callbackBufferRef = useRef([]); const lastCallRef = useRef(0); useEffect(() => { return () => { clearTimeout(startupTimeoutRef.current); clearTimeout(bufferTimeoutRef.current); callOnUnmountRef.current.forEach(f => typeof f === 'function' && f()); }; }, []); const callBuffer = (minTime = paginationRef.current.minTime) => { if (callbackBufferRef.current.length === 0) { return; } const diff = lastCallRef.current > 0 ? new Date().getTime() - lastCallRef.current : 0; const waitTime = diff < minTime ? minTime : 0; const nextTick = () => { if (callbackBufferRef.current.length > 0) { lastCallRef.current = new Date().getTime(); const { fn, params } = callbackBufferRef.current.shift(); fn(params); callBuffer(params.preventWaitForDelay ? -1 : minTime); } }; if (minTime > 0) { clearTimeout(bufferTimeoutRef.current); bufferTimeoutRef.current = setTimeout(nextTick, waitTime); } else { nextTick(); } }; const waitForReachedTime = (fn, params) => { var _context; _pushInstanceProperty(_context = callbackBufferRef.current).call(_context, { fn, params }); callBuffer(params.preventWaitForDelay ? -1 : paginationRef.current.minTime); }; const callEventHandler = (pageNumber, { callStartupEvent = false, preventWaitForDelay = false, callOnEnd = false, onDispatch = null } = {}) => { waitForReachedTime(({ pageNumber: pn, callStartupEvent: isStartup }) => { const ctx = paginationRef.current; let resolvedPageNumber = pn; const createEvent = eventName => { if (isNaN(resolvedPageNumber)) { resolvedPageNumber = 1; } const ret = dispatchCustomElementEvent(ctx, eventName, { pageNumber: resolvedPageNumber, ...ctx }); if (typeof onDispatch === 'function') { onDispatch(); } if (typeof ret === 'function') { var _context2; _pushInstanceProperty(_context2 = callOnUnmountRef.current).call(_context2, ret); } }; if (callOnEnd) { createEvent('onEnd'); } else { if (isStartup) { createEvent('onStartup'); } else { createEvent('onChange'); } createEvent('onLoad'); } }, { pageNumber, callStartupEvent, preventWaitForDelay }); }; const getNewContent = (newPageNo, props = {}, { callStartupEvent = false, preventWaitForDelay = false } = {}) => { const { pageCountInternal, endInfinity } = paginationRef.current; if (newPageNo > pageCountInternal) { return endInfinity(); } const exists = paginationRef.current.items.findIndex(obj => obj.pageNumber === newPageNo) > -1; if (exists) { return; } const items = paginationRef.current.prefillItems(newPageNo, props); paginationRef.current.setItems(items, () => { callEventHandler(newPageNo, { callStartupEvent, preventWaitForDelay }); }); }; const startup = () => { const { startupPage, startupCount } = paginationRef.current; const usedStartupCount = parseFloat(startupCount); for (let i = 0; i < usedStartupCount; ++i) { const newPageNo = startupPage + i; const skipObserver = newPageNo < usedStartupCount; const callStartupEvent = i === 0; const preventWaitForDelay = i <= usedStartupCount - 1; getNewContent(newPageNo, { position: 'after', skipObserver }, { callStartupEvent, preventWaitForDelay }); } }; const handleInfinityMarker = () => { const { lowerPage, upperPage, pageCountInternal, hasEndedInfinity, parallelLoadCount, currentPage, fallbackElement, markerElement, indicatorElement } = paginationRef.current; const Marker = () => _jsx(InteractionMarker, { pageNumber: upperPage, markerElement: markerElement || fallbackElement, onVisible: pageNumber => { let newPageNo; for (let i = 0; i < parallelLoadCount; ++i) { newPageNo = pageNumber + 1 + i; paginationRef.current.onPageUpdate(() => { paginationRef.current.setState({ upperPage: newPageNo, skipObserver: i + 1 < parallelLoadCount }); }); callEventHandler(newPageNo); } } }); const LoadButton = () => _jsx(InfinityLoadButton, { icon: "arrow_up", element: fallbackElement, pressedElement: _jsx(PaginationIndicator, { indicatorElement: indicatorElement || fallbackElement }), onClick: () => { const newPageNo = lowerPage - 1; paginationRef.current.onPageUpdate(() => { paginationRef.current.setState({ lowerPage: newPageNo }); }); callEventHandler(newPageNo); } }); return _jsxs(_Fragment, { children: [parseFloat(currentPage) > 0 && lowerPage > 1 && _jsx(LoadButton, {}), children, !hasEndedInfinity && parseFloat(currentPage) > 0 && (typeof pageCountInternal === 'undefined' || upperPage < pageCountInternal) && _jsx(Marker, {}), !hasEndedInfinity && !hideIndicator && (typeof pageCountInternal === 'undefined' || upperPage < pageCountInternal) && _jsx(PaginationIndicator, { indicatorElement: indicatorElement || fallbackElement })] }); }; const { items, pageCountInternal, startupPage, hasEndedInfinity, parallelLoadCount, placeMakerBeforeContent, pageElement, fallbackElement, markerElement, indicatorElement, loadButton } = pagination; if (!(items && items.length > 0)) { clearTimeout(startupTimeoutRef.current); startupTimeoutRef.current = setTimeout(startup, 1); return null; } if (pagination.useMarkerOnly) { return handleInfinityMarker(); } const Element = preparePageElement(pageElement || React.Fragment); return items.map(({ pageNumber, hasContent, content, ref, skipObserver, ScrollElement }, idx) => { const isLastItem = idx === items.length - 1; const Elem = hasContent && ScrollElement || Element; const isFragment = Elem === React.Fragment; const marker = hasContent && !useLoadButton && !skipObserver && !hasEndedInfinity && (typeof pageCountInternal === 'undefined' || pageNumber <= pageCountInternal) && _jsx(InteractionMarker, { pageNumber: pageNumber, markerElement: markerElement || fallbackElement, onVisible: pageNumber => { let newPageNo; for (let i = 0; i < parallelLoadCount; ++i) { newPageNo = pageNumber + 1 + i; getNewContent(newPageNo, { position: 'after', skipObserver: i + 1 < parallelLoadCount }); } } }); const showIndicator = (parallelLoadCount > 1 && idx > 0 ? isLastItem : true) && !hasContent && !hideIndicator; return _jsxs(Elem, { ...(!isFragment && { ref }), children: [hasContent && startupPage > 1 && pageNumber > 1 && pageNumber <= startupPage && _jsx(InfinityLoadButton, { element: typeof loadButton === 'function' ? loadButton : fallbackElement, icon: "arrow_up", text: loadButton === null || loadButton === void 0 ? void 0 : loadButton.text, iconPosition: loadButton === null || loadButton === void 0 ? void 0 : loadButton.iconPosition, onClick: event => getNewContent(pageNumber - 1, { position: 'before', skipObserver: true, event }) }), placeMakerBeforeContent && marker, content, !placeMakerBeforeContent && marker, showIndicator && _jsx(PaginationIndicator, { indicatorElement: indicatorElement || fallbackElement }), hasContent && useLoadButton && isLastItem && (typeof pageCountInternal === 'undefined' || pageNumber < pageCountInternal) && _jsx(InfinityLoadButton, { element: typeof loadButton === 'function' ? loadButton : fallbackElement, text: loadButton === null || loadButton === void 0 ? void 0 : loadButton.text, iconPosition: loadButton === null || loadButton === void 0 ? void 0 : loadButton.iconPosition, icon: "arrow_down", onClick: event => getNewContent(pageNumber + 1, { position: 'after', skipObserver: true, ScrollElement: props => hasContent && _jsx(ScrollToElement, { pageElement: pageElement, ...props }), event }) })] }, pageNumber); }); } function InteractionMarker({ pageNumber, markerElement = null, onVisible }) { const [isConnected, setIsConnected] = useState(false); const markerRef = useRef(null); const observerRef = useRef(null); const hasObserverSupport = useRef(typeof IntersectionObserver !== 'undefined'); const isMountedRef = useRef(false); const readyTimeoutRef = useRef(null); useMountEffect(() => { if (typeof markerElement === 'function') { warn('Pagination: Please use a string or React element e.g. markerElement="tr"'); } if (hasObserverSupport.current) { observerRef.current = new IntersectionObserver(entries => { const [{ isIntersecting }] = entries; if (isIntersecting) { callReady(); } }); } else { warn('Pagination is missing IntersectionObserver supported!'); } isMountedRef.current = true; if (markerRef.current) { var _observerRef$current; (_observerRef$current = observerRef.current) === null || _observerRef$current === void 0 || _observerRef$current.observe(markerRef.current); } return () => { var _observerRef$current2; isMountedRef.current = false; clearTimeout(readyTimeoutRef.current); (_observerRef$current2 = observerRef.current) === null || _observerRef$current2 === void 0 || _observerRef$current2.disconnect(); }; }); const callReady = () => { var _observerRef$current3; (_observerRef$current3 = observerRef.current) === null || _observerRef$current3 === void 0 || _observerRef$current3.disconnect(); observerRef.current = null; clearTimeout(readyTimeoutRef.current); readyTimeoutRef.current = setTimeout(() => { if (isMountedRef.current) { setIsConnected(true); } onVisible(pageNumber); }, 1); }; if (isConnected || !hasObserverSupport.current) { return null; } const Element = markerElement && isTrElement(markerElement) ? 'tr' : 'div'; const ElementChild = markerElement && isTrElement(markerElement) ? 'td' : 'div'; return _jsx(Element, { className: "dnb-pagination__marker dnb-table--ignore", children: _jsx(ElementChild, { className: "dnb-pagination__marker__inner", ref: markerRef }) }); } export function InfinityLoadButton({ element = 'div', pressedElement = null, icon = 'arrow_down', text = null, iconPosition = 'left', onClick }) { const context = React.useContext(Context); const [isPressed, setIsPressed] = React.useState(false); const handleClick = e => { setIsPressed(true); if (typeof onClick === 'function') { onClick(e); } }; const Element = element; const ElementChild = isTrElement(Element) ? 'td' : 'div'; if (isPressed) { return _jsx(_Fragment, { children: pressedElement }); } return _jsx(Element, { children: _jsx(ElementChild, { className: "dnb-pagination__loadbar", children: _jsx(Button, { size: "medium", icon: icon, iconPosition: iconPosition, text: text || context.translation.Pagination.loadButtonText, variant: "secondary", onClick: handleClick }) }) }); } function ScrollToElement({ pageElement = null, ...props }) { const elementRef = React.useRef(null); useMountEffect(() => { const elem = elementRef.current; if (elem && typeof elem.scrollIntoView === 'function') { elem.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } }); const Element = preparePageElement(pageElement || React.Fragment); if (Element === React.Fragment) { return _jsx("div", { ref: elementRef, ...props }); } return _jsx(Element, { ref: elementRef, ...props }); } withComponentMarkers(InfinityScroller, { _supportsSpacingProps: true }); //# sourceMappingURL=PaginationInfinity.js.map