@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
417 lines (416 loc) • 13.8 kB
JavaScript
"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