wix-style-react
Version:
wix-style-react
233 lines • 10.1 kB
JavaScript
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