UNPKG

wix-style-react

Version:
233 lines • 10.1 kB
export const getId = (idPrefix = '', name = '') => idPrefix ? idPrefix + name : undefined; export const calculateWidth = (totalPages) => `${totalPages.toString().length}em`; export const measureAndSetRootMinWidth = (compNode, paginationMode, idPrefix = '') => { compNode.style.minWidth = ''; compNode.style.minHeight = ''; const getById = (id) => compNode.querySelector(`#${getId(idPrefix, id)}`); const getMinWidth = (elmnt) => elmnt ? parseInt(window.getComputedStyle(elmnt).minWidth, 10) : 0; const getWidthWithMargins = (element) => { if (!element) { return 0; } const style = window.getComputedStyle(element); return (parseInt(style.marginRight, 10) + element.offsetWidth + parseInt(style.marginLeft, 10)); }; const getHeightWithMargins = (element) => { if (!element) { return 0; } const style = window.getComputedStyle(element); return (parseInt(style.marginBottom, 10) + element.offsetHeight + parseInt(style.marginTop, 10)); }; const navButtonsMinWidth = getWidthWithMargins(getById('navButtonNext')) + getWidthWithMargins(getById('navButtonPrevious')) + getWidthWithMargins(getById('navButtonFirst')) + getWidthWithMargins(getById('navButtonLast')); let selectionMinWidth = 0; let minHeight = 0; if (paginationMode === 'pages') { // means we're in "pages" pagination mode selectionMinWidth = getMinWidth(getById('pageStrip')); minHeight = Math.max(getHeightWithMargins(getById('pageStrip')), getHeightWithMargins(getById('navButtonNext'))); } else if (paginationMode === 'input') { // means we're in "input" pagination mode selectionMinWidth = getWidthWithMargins(getById('totalPages')) + getWidthWithMargins(getById('slash')) + getWidthWithMargins(getById('pageInput')); minHeight = Math.max(getHeightWithMargins(getById('pageInput')), getHeightWithMargins(getById('navButtonNext'))); } else if (paginationMode === 'compact') { // means we're in "compact" pagination mode selectionMinWidth = getWidthWithMargins(getById('totalPages')) + getWidthWithMargins(getById('slash')) + getWidthWithMargins(getById('currentPage')); minHeight = Math.max(getHeightWithMargins(getById('currentPage')), getHeightWithMargins(getById('navButtonNext'))); } compNode.style.minWidth = navButtonsMinWidth + selectionMinWidth + 'px'; compNode.style.minHeight = minHeight + 'px'; }; export function createStaticLayout({ totalPages, currentPage, maxPagesToShow, showFirstPage, showLastPage, }) { return createLayout({ totalPages, currentPage, lowerBound: 1, upperBound: totalPages, pageRangeCost: (a, b) => b - a + 1, showFirstPage, showLastPage, rewindToFirstCost: 2, rewindToLastCost: 2, budget: maxPagesToShow, }); } function rangeToPreRenderForResponsiveLayout(totalPages, currentPage, maxPagesToShow) { return [ Math.max(currentPage - maxPagesToShow, 1), Math.min(currentPage + maxPagesToShow, totalPages), ]; } export function createResponsiveLayoutTemplate({ totalPages, currentPage, maxPagesToShow, }) { const [lowerBound, upperBound] = rangeToPreRenderForResponsiveLayout(totalPages, currentPage, maxPagesToShow); return [1, 0, ...closedRange(lowerBound, upperBound), 0, totalPages]; } // Takes a container with children rendered using createResponsiveLayoutTemplate, // measures the children, and decides how many can be shown without overflowing the container. // For measurements to work correctly the pages must not have any dynamic spacing between them // such as justify-content: space-evenly, but they can have static spacing such as margins. // As long as we're using flexbox with centered pages we don't need to worry about the outer // margins of the first and last page, they will be trimmed by flexbox automatically. // maxPagesToShow is not really taken into account, it's used to derive the range of pages // that was pre-rendered by createResponsiveLayoutTemplate(). export function createResponsiveLayout({ container, totalPages, currentPage, maxPagesToShow, showFirstPage, showLastPage, }) { const children = Array.from(container.children); const pages = children.slice(2, -2); const containerWidth = container.getBoundingClientRect().width; const firstRect = children[0].getBoundingClientRect(); const lastRect = children[children.length - 1].getBoundingClientRect(); const lowerRect = pages[0].getBoundingClientRect(); const upperRect = pages[pages.length - 1].getBoundingClientRect(); const rewindToFirstCost = mergeBoundingRects(firstRect, lowerRect).width - lowerRect.width; const rewindToLastCost = mergeBoundingRects(lastRect, upperRect).width - upperRect.width; const [lowerBound, upperBound] = rangeToPreRenderForResponsiveLayout(totalPages, currentPage, maxPagesToShow); const pageRangeCost = (a, b) => { const aRect = pages[a - lowerBound].getBoundingClientRect(); const bRect = pages[b - lowerBound].getBoundingClientRect(); return mergeBoundingRects(aRect, bRect).width; }; return createLayout({ totalPages, currentPage, lowerBound, upperBound, pageRangeCost, showFirstPage, showLastPage, rewindToFirstCost, rewindToLastCost, budget: containerWidth, }); } function createLayout({ totalPages, currentPage, lowerBound, upperBound, pageRangeCost, showFirstPage, showLastPage, rewindToFirstCost, rewindToLastCost, budget, }) { const prevOrLowerBound = Math.max(currentPage - 1, lowerBound); const nextOrUpperBound = Math.min(currentPage + 1, upperBound); const expand = (low, high, showRewindToFirst, showRewindToLast) => createLayoutByExpandingPageRange({ totalPages, low, high, lowerBound, upperBound, pageRangeCost, budget, showRewindToFirst, showRewindToLast, rewindToFirstCost, rewindToLastCost, }); return ( // Try to show the entire range. ((lowerBound === 1 || !showFirstPage) && (upperBound === totalPages || !showLastPage) && expand(lowerBound, upperBound, false, false)) || // Ellipsis only in the end. Show at least one page after the current. (showLastPage && lowerBound === 1 && expand(lowerBound, nextOrUpperBound, false, true)) || // Ellipsis only in the beginning. Show at least one page before the current. (showFirstPage && upperBound === totalPages && expand(prevOrLowerBound, upperBound, true, false)) || // Ellipses on both sides. Show at least one page before the current and one after. (showFirstPage && showLastPage && expand(prevOrLowerBound, nextOrUpperBound, true, true)) || // Ellipsis only in the end. Don't try to include the next page. (showLastPage && lowerBound === 1 && expand(lowerBound, currentPage, false, true)) || // Ellipsis only in the beginning. Don't try to include the previous page. (showFirstPage && upperBound === totalPages && expand(currentPage, upperBound, true, false)) || // Ellipses on both sides. Don't try to include the previous and the next page. (showFirstPage && showLastPage && expand(currentPage, currentPage, true, true)) || // Cut off both sides without adding ellipses. expand(currentPage, currentPage, false, false) || [currentPage] // If there's not enough space even for the current page, still show it. ); } function createLayoutByExpandingPageRange({ totalPages, low, high, lowerBound, upperBound, pageRangeCost, budget, showRewindToFirst, showRewindToLast, rewindToFirstCost, rewindToLastCost, }) { const safeLowerBound = showRewindToFirst ? Math.max(lowerBound, 4) : lowerBound; const safeUpperBound = showRewindToLast ? Math.min(upperBound, totalPages - 3) : upperBound; if (!isNondecreasing([ lowerBound, safeLowerBound, low, high, safeUpperBound, upperBound, ])) { return null; } lowerBound = safeLowerBound; upperBound = safeUpperBound; budget -= (showRewindToFirst ? rewindToFirstCost : 0) + (showRewindToLast ? rewindToLastCost : 0); let acceptableLow = 0; let acceptableHigh = 0; while (lowerBound <= low && high <= upperBound && pageRangeCost(low, high) <= budget) { acceptableLow = low; acceptableHigh = high; if (low === lowerBound && high === upperBound) { break; } low = Math.max(low - 1, lowerBound); high = Math.min(high + 1, upperBound); } return acceptableLow && acceptableHigh ? [ ...(showRewindToFirst ? [1, 0] : []), ...closedRange(acceptableLow, acceptableHigh), ...(showRewindToLast ? [0, totalPages] : []), ] : null; } function closedRange(start, stop, step = 1) { const result = []; for (let i = start; i <= stop; i += step) { result.push(i); } return result; } function isNondecreasing(sequence) { for (let i = 1; i < sequence.length; i++) { if (sequence[i] < sequence[i - 1]) { return false; } } return true; } function mergeBoundingRects(a, b) { const top = Math.min(a.top, b.top); const right = Math.max(a.right, b.right); const bottom = Math.max(a.bottom, b.bottom); const left = Math.min(a.left, b.left); const width = right - left; const height = bottom - top; return { top, right, bottom, left, width, height }; } //# sourceMappingURL=utils.js.map