UNPKG

vfrag

Version:

Paginate a document by breaking one or more containers vertically into multiple fragments.

118 lines (93 loc) 3.56 kB
import paginate from "./paginate.js"; import * as util from "./util.js"; /** * @typedef {Object} PaginationStats * @property {number} pages - Number of pages * @property {number} time - Time taken in milliseconds */ const DEFAULT_OPTIONS = { startAt: 1, aspectRatio: 8.5 / 11, root: document.documentElement, sections: ".page, .vfrag-page", askEvery: 200, renderEvery: 20, running: { headers: { maxLevels: 2, separator: "&raquo;", }, pageNumbers: true }, }; /** * Paginate multiple containers in a continuous sequence, with styling hooks on the root. * @param { string} selector * @param { object | string } options * @param { string | Array<Element> } [options.sections=".page"] * @param { Element } [options.root=document.documentElement] * @param { boolean } [options.sync=false] * @returns {PaginationStats | Promise<PaginationStats>} */ export default async function paginateAll (options = {}) { if (typeof options === "string") { options = {sections: options}; } for (let option in DEFAULT_OPTIONS) { options[option] ??= DEFAULT_OPTIONS[option]; } options.renderEvery = Math.min(options.askEvery, options.renderEvery); let timer = util.timer(); timer.start(); options.root.classList.add("pagination-root", "paginated", "paginating"); if (options.startAt > 1) { options.root.style.setProperty("--first-page", options.startAt); } if (options.debug) { options.root.classList.add("debug-pagination"); options.root.style.setProperty("--page-aspect-ratio-image", `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${ options.aspectRatio } 1"><rect width="100%" height="100%" fill="white" /></svg>')`); } let sections = options.root.querySelectorAll(options.sections); await util.nextFrame(); options.root.addEventListener("fragmented", event => { if (event.target.matches?.(options.sections)) { let info = event.detail; options.root.style.setProperty("--pages", options.totals.pages); if (info.pagesLeft > 0) { options.root.style.setProperty("--pages-left", info.pagesLeft); } else { options.root.style.removeProperty("--pages-left"); } } }); let done = []; for (let section of sections) { if (!section.matches(DEFAULT_OPTIONS.sections)) { // We need some kind of styling hook here section.classList.add("vfrag-page"); } let sectionDone = paginate(section, options); done.push(sectionDone); } let sectionInfo = await Promise.all(done); let timers = options.timers; let emptyLines = sectionInfo.flatMap(info => info.pageDetails.map(consumed => consumed.emptyLines)); // Pagination finished, assign page numbers timers.DOM.start(); let pages = options.root.querySelectorAll(options.sections); for (let i = 0; i < pages.length; i++) { let pageNumber = i + options.startAt; let page = pages[i]; page.style.setProperty("--page-number", pageNumber); page.dataset.page = pageNumber; } timers.DOM.pause(); let totalTime = new util.Timer(timers.consume + timers.DOM); console.info(`Paginated ${ sections.length } sections into ${ options.totals.pages } pages in ${ totalTime } (total: ${ timer.end() }, consume: ${ timers.consume }, DOM: ${ timers.DOM }).` + ` Empty lines: ${ util.average(emptyLines)?.toLocaleString() } avg, ${ Math.max(...emptyLines).toLocaleString() } max.`); options.root.classList.remove("paginating"); options.root.classList.add("done"); options.root.dispatchEvent(new CustomEvent("paginated",{ bubbles: true, detail: options.totals })); return options.totals; }