UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

461 lines (460 loc) 15 kB
"use client"; import _extends from "@babel/runtime/helpers/esm/extends"; import React from 'react'; import PropTypes from 'prop-types'; import { warn, isTrue, dispatchCustomElementEvent, getClosestParent } from "../../shared/component-helper.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"; export default class InfinityScroller extends React.PureComponent { static contextType = PaginationContext; static defaultProps = { children: null }; constructor(props, context) { super(props); this.hideIndicator = isTrue(context.pagination.hide_progress_indicator); this.useLoadButton = isTrue(context.pagination.use_load_button); this.lastElement = React.createRef(); this.callOnUnmount = []; } componentWillUnmount() { clearTimeout(this._startupTimeout); clearTimeout(this._bufferTimeout); this.callOnUnmount.forEach(f => typeof f === 'function' && f()); } startup = () => { const { startupPage, startup_count } = this.context.pagination; const startupCount = parseFloat(startup_count); let newPageNo, skipObserver, callStartupEvent, preventWaitForDelay; for (let i = 0; i < startupCount; ++i) { newPageNo = startupPage + i; skipObserver = newPageNo < startupCount; callStartupEvent = i === 0; preventWaitForDelay = i <= startupCount - 1; this.getNewContent(newPageNo, { position: 'after', skipObserver }, { callStartupEvent, preventWaitForDelay }); } }; getNewContent = (newPageNo, props = {}, { callStartupEvent = false, preventWaitForDelay = false } = {}) => { const { pageCount, endInfinity } = this.context.pagination; if (newPageNo > pageCount) { return endInfinity(); } const exists = this.context.pagination.items.findIndex(obj => { return obj.pageNumber === newPageNo; }) > -1; if (exists) { return; } const items = this.context.pagination.prefillItems(newPageNo, props); this.context.pagination.setItems(items, () => { this.callEventHandler(newPageNo, { callStartupEvent, preventWaitForDelay }); }); }; waitForReachedTime(fn, params) { this.callbackBuffer = this.callbackBuffer || []; this.callbackBuffer.push({ fn, params }); this.callBuffer({ minTime: params.preventWaitForDelay ? -1 : this.context.pagination.minTime }); } callBuffer({ minTime = this.context.pagination.minTime } = {}) { if (this.callbackBuffer.length === 0) { return; } const diff = this._lastCall > 0 ? new Date().getTime() - this._lastCall : 0; const waitTime = diff < minTime ? minTime : 0; const nextTick = () => { if (this.callbackBuffer.length > 0) { this._lastCall = new Date().getTime(); const { fn, params } = this.callbackBuffer.shift(); fn(params); this.callBuffer({ minTime: params.preventWaitForDelay ? -1 : minTime }); } }; if (minTime > 0) { clearTimeout(this._bufferTimeout); this._bufferTimeout = setTimeout(nextTick, waitTime); } else { nextTick(); } } callEventHandler(pageNumber, { callStartupEvent = false, preventWaitForDelay = false, callOnEnd = false, onDispatch = null } = {}) { this.waitForReachedTime(({ pageNumber, callStartupEvent }) => { const context = this.context.pagination; const createEvent = eventName => { if (isNaN(pageNumber)) { pageNumber = 1; } const ret = dispatchCustomElementEvent(context, eventName, { pageNumber, ...context }); if (typeof onDispatch === 'function') { onDispatch(); } if (typeof ret === 'function') { this.callOnUnmount.push(ret); } }; if (callOnEnd) { createEvent('on_end'); } else { if (callStartupEvent) { createEvent('on_startup'); } else { createEvent('on_change'); } createEvent('on_load'); } }, { pageNumber, callStartupEvent, preventWaitForDelay }); } handleInfinityMarker() { const { children } = this.props; const { lowerPage, upperPage, pageCount, hasEndedInfinity, parallelLoadCount, current_page, fallback_element, marker_element, indicator_element } = this.context.pagination; const Marker = () => React.createElement(InteractionMarker, { pageNumber: upperPage, markerElement: marker_element || fallback_element, onVisible: pageNumber => { let newPageNo; for (let i = 0; i < parallelLoadCount; ++i) { newPageNo = pageNumber + 1 + i; this.context.pagination.onPageUpdate(() => { this.context.pagination.setState({ upperPage: newPageNo, skipObserver: i + 1 < parallelLoadCount }); }); this.callEventHandler(newPageNo); } } }); const LoadButton = () => React.createElement(InfinityLoadButton, { icon: "arrow_up", element: fallback_element, pressed_element: React.createElement(PaginationIndicator, { indicator_element: indicator_element || fallback_element }), on_click: () => { const newPageNo = lowerPage - 1; this.context.pagination.onPageUpdate(() => { this.context.pagination.setState({ lowerPage: newPageNo }); }); this.callEventHandler(newPageNo); } }); return React.createElement(React.Fragment, null, parseFloat(current_page) > 0 && lowerPage > 1 && React.createElement(LoadButton, null), children, !hasEndedInfinity && parseFloat(current_page) > 0 && (typeof pageCount === 'undefined' || upperPage < pageCount) && React.createElement(Marker, null), !hasEndedInfinity && !this.hideIndicator && (typeof pageCount === 'undefined' || upperPage < pageCount) && React.createElement(PaginationIndicator, { indicator_element: indicator_element || fallback_element })); } render() { const { items, pageCount, startupPage, hasEndedInfinity, parallelLoadCount, placeMakerBeforeContent, page_element, fallback_element, marker_element, indicator_element, load_button_text, loadButton } = this.context.pagination; if (!(items && items.length > 0)) { clearTimeout(this._startupTimeout); this._startupTimeout = setTimeout(this.startup, 1); return null; } if (this.context.pagination.useMarkerOnly) { return this.handleInfinityMarker(); } const Element = preparePageElement(page_element || React.Fragment); return items.map(({ pageNumber, hasContent, content, ref, skipObserver, ScrollElement }, idx) => { const isLastItem = idx === items.length - 1; const Elem = hasContent && ScrollElement || Element; const marker = hasContent && !this.useLoadButton && !skipObserver && !hasEndedInfinity && (typeof pageCount === 'undefined' || pageNumber <= pageCount) && React.createElement(InteractionMarker, { pageNumber: pageNumber, markerElement: marker_element || fallback_element, onVisible: pageNumber => { let newPageNo; for (let i = 0; i < parallelLoadCount; ++i) { newPageNo = pageNumber + 1 + i; this.getNewContent(newPageNo, { position: 'after', skipObserver: i + 1 < parallelLoadCount }); } } }); const showIndicator = (parallelLoadCount > 1 && idx > 0 ? isLastItem : true) && !hasContent && !this.hideIndicator; return React.createElement(Elem, { key: pageNumber, ref: ref }, hasContent && startupPage > 1 && pageNumber > 1 && pageNumber <= startupPage && React.createElement(InfinityLoadButton, { element: typeof loadButton === 'function' ? loadButton : fallback_element, icon: "arrow_up", text: load_button_text !== null && load_button_text !== void 0 ? load_button_text : loadButton?.text, icon_position: loadButton?.iconPosition, on_click: event => this.getNewContent(pageNumber - 1, { position: 'before', skipObserver: true, event }) }), placeMakerBeforeContent && marker, content, !placeMakerBeforeContent && marker, showIndicator && React.createElement(PaginationIndicator, { indicator_element: indicator_element || fallback_element }), hasContent && this.useLoadButton && isLastItem && (typeof pageCount === 'undefined' || pageNumber < pageCount) && React.createElement(InfinityLoadButton, { element: typeof loadButton === 'function' ? loadButton : fallback_element, text: load_button_text !== null && load_button_text !== void 0 ? load_button_text : loadButton?.text, icon_position: loadButton?.iconPosition, icon: "arrow_down", on_click: event => this.getNewContent(pageNumber + 1, { position: 'after', skipObserver: true, ScrollElement: props => hasContent && React.createElement(ScrollToElement, _extends({ page_element: page_element }, props)), event }) })); }); } } process.env.NODE_ENV !== "production" ? InfinityScroller.propTypes = { children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]) } : void 0; class InteractionMarker extends React.PureComponent { static defaultProps = { markerElement: null }; state = { isConnected: false }; constructor(props) { super(props); if (typeof props.markerElement === 'function') { warn('Pagination: Please use a string or React element e.g. marker_element="tr"'); } this._ref = React.createRef(); if (typeof IntersectionObserver !== 'undefined') { this.intersectionObserver = new IntersectionObserver(entries => { const [{ isIntersecting }] = entries; if (isIntersecting) { this.callReady(); } }); } else { warn('Pagination is missing IntersectionObserver supported!'); } } componentDidMount() { if (this._ref.current) { this._isMounted = true; this.intersectionObserver?.observe(this._ref.current); } } componentWillUnmount() { this._isMounted = false; if (this.intersectionObserver) { clearTimeout(this._readyTimeout); this.intersectionObserver.disconnect(); } } callReady = () => { this.intersectionObserver?.disconnect(); this.intersectionObserver = null; clearTimeout(this._readyTimeout); this._readyTimeout = setTimeout(() => { if (this._isMounted) { this.setState({ isConnected: true }); } this.props.onVisible(this.props.pageNumber); }, 1); }; getContentHeight() { let height = 0; try { const sibling = getClosestParent('dnb-table', this._ref.current); height = parseFloat(window.getComputedStyle(sibling.querySelector('tbody')).height); } catch (e) {} return height; } render() { const { markerElement } = this.props; if (this.state.isConnected || !this.intersectionObserver) { return null; } const Element = markerElement && isTrElement(markerElement) ? 'tr' : 'div'; const ElementChild = markerElement && isTrElement(markerElement) ? 'td' : 'div'; return React.createElement(Element, { className: "dnb-pagination__marker dnb-table--ignore" }, React.createElement(ElementChild, { className: "dnb-pagination__marker__inner", ref: this._ref })); } } process.env.NODE_ENV !== "production" ? InteractionMarker.propTypes = { pageNumber: PropTypes.number.isRequired, onVisible: PropTypes.func.isRequired, markerElement: PropTypes.oneOfType([PropTypes.object, PropTypes.node, PropTypes.func, PropTypes.string]) } : void 0; export class InfinityLoadButton extends React.PureComponent { static contextType = Context; static defaultProps = { element: 'div', pressed_element: null, icon: 'arrow_down', text: null, icon_position: 'left' }; state = { isPressed: false }; onClickHandler = e => { this.setState({ isPressed: true }); if (typeof this.props.on_click === 'function') { this.props.on_click(e); } }; render() { const { element, icon, text, icon_position } = this.props; const Element = element; const ElementChild = isTrElement(Element) ? 'td' : 'div'; return this.state.isPressed ? this.props.pressed_element : React.createElement(Element, null, React.createElement(ElementChild, { className: "dnb-pagination__loadbar" }, React.createElement(Button, { size: "medium", icon: icon, icon_position: icon_position, text: text || this.context.translation.Pagination.load_button_text, variant: "secondary", on_click: this.onClickHandler }))); } } process.env.NODE_ENV !== "production" ? InfinityLoadButton.propTypes = { element: PropTypes.oneOfType([PropTypes.object, PropTypes.node, PropTypes.func, PropTypes.string]), pressed_element: PropTypes.oneOfType([PropTypes.object, PropTypes.node, PropTypes.func]), icon: PropTypes.string.isRequired, on_click: PropTypes.func.isRequired, text: PropTypes.string, icon_position: PropTypes.string } : void 0; class ScrollToElement extends React.PureComponent { static defaultProps = { page_element: null }; constructor(props) { super(props); this._elementRef = React.createRef(); } componentDidMount() { const elem = this._elementRef.current; this.scrollToPage(elem); } scrollToPage(element) { if (element && typeof element.scrollIntoView === 'function') { element.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } } render() { const { page_element, ...props } = this.props; const Element = preparePageElement(page_element || React.Fragment); if (Element === React.Fragment) { return React.createElement("div", _extends({ ref: this._elementRef }, props)); } return React.createElement(Element, _extends({ ref: this._elementRef }, props)); } } process.env.NODE_ENV !== "production" ? ScrollToElement.propTypes = { page_element: PropTypes.oneOfType([PropTypes.object, PropTypes.node, PropTypes.func, PropTypes.string]) } : void 0; InfinityScroller._supportsSpacingProps = true; //# sourceMappingURL=PaginationInfinity.js.map