@zinejs/core 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controls/styles.d.ts +1 -1
- package/dist/controls/types.d.ts +7 -2
- package/dist/{controls-Djjk8Yac.js → controls-DgEXR2zi.js} +69 -57
- package/dist/controls-DgEXR2zi.js.map +1 -0
- package/dist/index.js +188 -175
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +36 -7
- package/dist/index.umd.js.map +1 -1
- package/dist/loading/loading.d.ts +1 -1
- package/dist/{loading-C0qbANt2.js → loading-BBK6YbSW.js} +2 -2
- package/dist/loading-BBK6YbSW.js.map +1 -0
- package/dist/{share-DXAfK9rJ.js → share-WXdscwp3.js} +2 -2
- package/dist/{share-DXAfK9rJ.js.map → share-WXdscwp3.js.map} +1 -1
- package/dist/styles-B8fFoYm_.js +40 -0
- package/dist/styles-B8fFoYm_.js.map +1 -0
- package/package.json +1 -1
- package/dist/controls-Djjk8Yac.js.map +0 -1
- package/dist/loading-C0qbANt2.js.map +0 -1
- package/dist/styles-CgCzjFUk.js +0 -40
- package/dist/styles-CgCzjFUk.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controls-DgEXR2zi.js","names":["#doc","#holder","#pageBox","#place","#armDismiss","#trackPage","#fitToPage","#resizeObserver","#wrap","#close","#scrim","#onKeydown","#zine","#doc","#bar","#build","#markCurrent","#unsubscribe","#rows","#observeRows","#paint","#observer","#zine","#doc","#onDismiss","#bar","#note","#build","#unsubscribe","#markCurrent","#addLevel","#rows","#zine","#doc","#onDismiss","#bar","#input","#timer","#search","#hits","#run","#note","#zine","#device","#prev","#button","#next","#place","#wrap","#unsubscribe","#refresh","#edges","#zine","#container","#doc","#colorScheme","#position","#root","#bar","#onKeyDown","#isolate","#place","#refresh","#unsubscribe","#popover","#closePopover","#wrap","#build","#arrows","#probeOutline","#panel","#panelKind","#context","#buttons","#button","#titleOf","#iconOf","#activate","#openMenu","#setBusy","#showPopover","#placePopover","#hasVisibleChildren","#formFields","#pageInput","#hasOutline"],"sources":["../src/controls/builtins.ts","../src/controls/sidebar.ts","../src/controls/thumbnails.ts","../src/controls/outline.ts","../src/controls/search.ts","../src/controls/arrows.ts","../src/controls/toolbar.ts","../src/controls/controls.ts"],"sourcesContent":["import { ICONS } from './icons';\nimport { defineControl } from './registry';\nimport type { ControlContext } from './types';\n\n\n/** Zoom step per click: two clicks double the scale. */\nconst ZOOM_STEP = Math.SQRT2;\n\nconst isFullscreen = (ctx: ControlContext): boolean => {\n const doc = ctx.zine.container.ownerDocument;\n return doc?.fullscreenElement === ctx.zine.container;\n};\n\n/**\n * Register the stock controls. Called once as the toolbar chunk loads, so the icon set and these\n * definitions stay out of the base bundle.\n *\n * `pageInput` and `search` are not here: both need the toolbar instance itself (one builds an\n * input, the other opens an anchored panel), so the toolbar registers them.\n */\nexport function registerBuiltins(): void {\n defineControl({\n id: 'prev',\n title: 'Previous page',\n icon: ICONS.prev,\n isDisabled: (ctx) => !ctx.zine.canFlipPrev(),\n action: (ctx) => ctx.zine.flipPrev(),\n });\n\n defineControl({\n id: 'next',\n title: 'Next page',\n icon: ICONS.next,\n isDisabled: (ctx) => !ctx.zine.canFlipNext(),\n action: (ctx) => ctx.zine.flipNext(),\n });\n\n // The chevrons point the way the book travels, which reverses in RTL — same as the page\n // arrows flanking the book.\n const rtl = (ctx: ControlContext): boolean => ctx.zine.getDirection() === 'rtl';\n\n defineControl({\n id: 'first',\n title: 'First page',\n icon: (ctx) => (rtl(ctx) ? ICONS.last : ICONS.first),\n isDisabled: (ctx) => !ctx.zine.canFlipPrev(),\n action: (ctx) => {\n ctx.zine.flipTo(0);\n ctx.close();\n },\n });\n\n defineControl({\n id: 'last',\n title: 'Last page',\n icon: (ctx) => (rtl(ctx) ? ICONS.first : ICONS.last),\n isDisabled: (ctx) => !ctx.zine.canFlipNext(),\n action: (ctx) => {\n ctx.zine.flipTo(ctx.zine.getPageCount() - 1);\n ctx.close();\n },\n });\n\n defineControl({\n id: 'zoomIn',\n title: 'Zoom in',\n icon: ICONS.zoomIn,\n isDisabled: (ctx) => ctx.zine.getZoom() >= ctx.zine.getMaxZoom() - 1e-6,\n action: (ctx) => ctx.zine.setZoom(ctx.zine.getZoom() * ZOOM_STEP),\n });\n\n defineControl({\n id: 'zoomOut',\n title: 'Zoom out',\n icon: ICONS.zoomOut,\n isDisabled: (ctx) => ctx.zine.getZoom() <= 1 + 1e-6,\n action: (ctx) => ctx.zine.setZoom(ctx.zine.getZoom() / ZOOM_STEP),\n });\n\n defineControl({\n id: 'fullscreen',\n title: 'Fullscreen',\n icon: ICONS.fullscreen,\n isVisible: (ctx) => typeof ctx.zine.container.requestFullscreen === 'function',\n isActive: isFullscreen,\n action: (ctx) => {\n const doc = ctx.zine.container.ownerDocument;\n if (isFullscreen(ctx)) void doc?.exitFullscreen?.();\n else void ctx.zine.container.requestFullscreen?.();\n ctx.close();\n },\n });\n\n defineControl({\n id: 'share',\n title: 'Share',\n icon: ICONS.share,\n action: (ctx) => {\n ctx.close();\n // The dialog, its brand marks and the QR encoder are a chunk of their own: a book nobody\n // shares never downloads any of it.\n void import('./share')\n .then(({ ShareDialog }) => new ShareDialog(ctx.zine, ctx.zine.container, ctx.colorScheme))\n .catch(() => {\n // No dialog, so fall back to the plain behaviour rather than doing nothing.\n void navigator.clipboard?.writeText(ctx.zine.pageLink()).catch(() => {});\n });\n },\n });\n\n defineControl({\n id: 'download',\n title: 'Download PDF',\n icon: ICONS.download,\n // Hidden for image books and for PDFs opened from a caller-owned pdf.js document, which\n // have no file of their own to save.\n isVisible: (ctx) => ctx.zine.canDownload(),\n // Async: a cross-origin PDF is fetched into a blob first, which takes a beat. Returning the\n // promise lets the toolbar hold the menu open with a spinner until the save starts, then close.\n action: async (ctx) => {\n await ctx.zine.download();\n ctx.close();\n },\n });\n\n defineControl({\n id: 'spread',\n // Says what pressing it will do, not what the book is doing now, and in the reader's terms —\n // \"spread mode\" is the library's word for it, not theirs.\n title: (ctx) => (ctx.zine.isSinglePage() ? 'Show two pages' : 'Show one page'),\n icon: (ctx) => (ctx.zine.isSinglePage() ? ICONS.twoPages : ICONS.onePage),\n // Hidden while a narrow container is forcing one page: pressing it could not honour two.\n isVisible: (ctx) => !ctx.zine.isResponsiveSingle(),\n action: (ctx) => {\n ctx.zine.toggleSpreadMode();\n ctx.close();\n },\n });\n\n defineControl({\n id: 'print',\n title: 'Print',\n icon: ICONS.print,\n // Same reach as download: there has to be an original document to print.\n isVisible: (ctx) => ctx.zine.canPrint(),\n // Async like download: the print frame needs a same-origin blob for a cross-origin file, so\n // hold the menu open with a spinner until the frame is ready, then close.\n action: async (ctx) => {\n await ctx.zine.print();\n ctx.close();\n },\n });\n\n defineControl({\n id: 'menu',\n title: 'More',\n icon: ICONS.menu,\n // Share and fullscreen sit on the bar itself by default, so the overflow holds what is left.\n children: ['first', 'last', 'spread', 'thumbnails', 'outline', 'print', 'download'],\n });\n}\n","import { createIcon, ICONS } from './icons';\n\n/**\n * The rail beside the book that the thumbnail and outline lists both sit in.\n *\n * It exists to solve one problem shared by every such panel: the container's measured box is what\n * the renderer sizes the book from and what pointer hit-testing maps into, so a panel must not\n * live inside it. Instead the book (and its docked toolbar, if any) is wrapped in a flex row with\n * the panel as its sibling, and `destroy` unwinds that again.\n *\n * The rail is absolutely positioned inside a holder that stretches to the book's height, so a\n * long list scrolls internally instead of running past the bottom of the book.\n *\n * On a wide screen the rail flanks the book and the book shrinks to make room (see styles.ts). On\n * a narrow one there is no room to flank, so the rail becomes a drawer over the book and a scrim\n * dims the page behind it; tapping the scrim or pressing Escape dismisses it through `onDismiss`.\n */\n/** Shared options a toolbar threads into every panel constructor. */\nexport interface PanelOptions {\n /** Called when the reader dismisses the narrow-screen drawer (scrim tap or Escape). */\n onDismiss?: () => void;\n}\n\n/** The book's painted box inside the container, in container px (see Zine.getPageBox). */\ntype PageBox = { x: number; y: number; width: number; height: number };\n\nexport class Sidebar {\n /** The scrolling list; panels append their rows here. */\n readonly root: HTMLElement;\n #holder: HTMLElement;\n #wrap: HTMLElement | null = null;\n #scrim: HTMLElement | null = null;\n #close: HTMLElement | null = null;\n #onKeydown: ((e: KeyboardEvent) => void) | null = null;\n #doc: Document;\n #pageBox: (() => PageBox | null) | null = null;\n #resizeObserver: ResizeObserver | null = null;\n\n constructor(\n doc: Document,\n container: HTMLElement,\n options: {\n className: string;\n label: string;\n width: number;\n /** Called when the reader dismisses the drawer (scrim tap or Escape). Omit to disable both. */\n onDismiss?: () => void;\n /** Reading direction; the drawer slides in from the right instead of the left when true. */\n rtl?: boolean;\n /** Float over the book's edge instead of flanking it (so the book never resizes). Panels\n * whose width is fixed and modest — the outline and search rails — prefer this; the wider\n * thumbnail rail flanks so both stay fully visible. Only affects wide screens: below the\n * breakpoint every panel is a drawer regardless. */\n overlay?: boolean;\n /** The book's painted box in container px. The rail stands exactly as tall as the page rather\n * than the whole container, which is taller whenever the page is letterboxed (its aspect not\n * matching the container's) or a docked toolbar padded the wrap. Re-read on every resize. */\n pageBox?: () => PageBox | null;\n },\n ) {\n this.#doc = doc;\n this.#holder = doc.createElement('div');\n this.#holder.className = 'zine-panel-holder';\n this.#holder.style.width = `${options.width}px`;\n this.#pageBox = options.pageBox ?? null;\n\n this.root = doc.createElement('div');\n this.root.className = `zine-panel ${options.className}`;\n this.root.setAttribute('role', 'listbox');\n this.root.setAttribute('aria-label', options.label);\n this.#holder.appendChild(this.root);\n\n // In the overlay fallback the rail sits inside the book, where the book's own gesture\n // handlers would otherwise read a click on a row as a page tap.\n for (const type of ['pointerdown', 'pointerup', 'pointermove', 'click', 'dblclick', 'wheel']) {\n this.root.addEventListener(type, (e) => e.stopPropagation());\n }\n\n this.#place(doc, container, options.rtl ?? false, options.overlay ?? false);\n\n if (options.onDismiss) this.#armDismiss(doc, options.onDismiss);\n\n this.#trackPage(container);\n }\n\n /**\n * Keep the rail sized to the painted page. The page box changes whenever the container resizes\n * (window, flanking shrink) or its aspect-ratio does (spread-mode toggle), and a ResizeObserver\n * on the container fires on all of those, so re-fitting there catches every case with no event\n * wiring. Falls back to the full-height CSS when there is no observer or no box yet (e.g. tests).\n */\n #trackPage(container: HTMLElement): void {\n if (!this.#pageBox) return;\n this.#fitToPage();\n if (typeof ResizeObserver === 'undefined') return;\n this.#resizeObserver = new ResizeObserver(() => this.#fitToPage());\n this.#resizeObserver.observe(container);\n }\n\n #fitToPage(): void {\n const box = this.#pageBox?.();\n if (!box) return;\n // top+height (and bottom:auto so an absolute drawer is not over-constrained back to full height)\n // work whether the holder is a relative flex item (flank) or absolutely placed (overlay/drawer).\n this.#holder.style.top = `${box.y}px`;\n this.#holder.style.bottom = 'auto';\n this.#holder.style.height = `${box.height}px`;\n }\n\n /** Resize the rail, e.g. once a thumbnail list knows whether it needs one column or two. */\n setWidth(px: number): void {\n this.#holder.style.width = `${px}px`;\n }\n\n #place(doc: Document, container: HTMLElement, rtl: boolean, overlay: boolean): void {\n const parent = container.parentNode;\n if (!parent) {\n // Not in a document yet: overlay rather than lose the panel.\n this.#holder.classList.add('zine-panel-overlay');\n container.appendChild(this.#holder);\n return;\n }\n // A docked toolbar already wrapped the book; wrap that whole assembly so the rail sits\n // beside book *and* toolbar rather than between them.\n const existing = container.closest('.zine-controls-wrap');\n const target = (existing ?? container) as HTMLElement;\n const wrap = doc.createElement('div');\n wrap.className = 'zine-panel-wrap';\n if (rtl) wrap.classList.add('zine-panel-wrap-rtl');\n // An overlay panel floats over the book's edge rather than flanking it, so the book keeps its\n // size on a wide screen too — the flex-row shrink is off for this wrap.\n if (overlay) wrap.classList.add('zine-panel-wrap-overlay');\n target.parentNode!.insertBefore(wrap, target);\n wrap.append(this.#holder, target);\n this.#wrap = wrap;\n }\n\n /**\n * Wire every way to dismiss the rail. A close button in the rail's trailing-top corner is the\n * discoverable one, present on every screen — the toolbar button and Escape always toggle too,\n * but neither is obvious, and the flanking thumbnail rail has no scrim to click away on. The\n * scrim (drawer only, under the narrow-screen media query) and the Escape key are the other two.\n *\n * The scrim is a sibling of the holder so it can dim the book area without the flex row reserving\n * a column for it. When there is no wrap (the parentless overlay fallback), there is no book\n * beside it to dim, so the scrim is skipped and only the button and Escape are armed.\n */\n #armDismiss(doc: Document, onDismiss: () => void): void {\n const close = doc.createElement('button');\n close.type = 'button';\n close.className = 'zine-panel-close';\n close.setAttribute('aria-label', 'Close');\n close.appendChild(createIcon(doc, ICONS.close));\n // Floats over the scrolling list (see styles.ts); stop the click reaching the book's tap handler\n // in the overlay fallback, where the rail sits inside the container.\n close.addEventListener('click', (e) => {\n e.stopPropagation();\n onDismiss();\n });\n this.#holder.appendChild(close);\n this.#close = close;\n\n if (this.#wrap) {\n const scrim = doc.createElement('div');\n scrim.className = 'zine-panel-scrim';\n // Before the holder so the drawer paints on top of the dim; z-index in CSS makes it explicit.\n this.#wrap.insertBefore(scrim, this.#holder);\n for (const type of ['pointerdown', 'click']) {\n scrim.addEventListener(type, (e) => {\n e.stopPropagation();\n onDismiss();\n });\n }\n this.#scrim = scrim;\n }\n this.#onKeydown = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') onDismiss();\n };\n doc.addEventListener('keydown', this.#onKeydown);\n }\n\n destroy(): void {\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = null;\n if (this.#onKeydown) {\n this.#doc.removeEventListener('keydown', this.#onKeydown);\n this.#onKeydown = null;\n }\n this.#scrim?.remove();\n this.#scrim = null;\n this.#close = null; // lives inside #holder, removed with it below\n this.#holder.remove();\n const wrap = this.#wrap;\n if (wrap?.parentNode) {\n // The rail is gone, so what is left is the book; put it back where the wrapper was.\n while (wrap.firstChild) wrap.parentNode.insertBefore(wrap.firstChild, wrap);\n wrap.remove();\n }\n this.#wrap = null;\n }\n}\n","import { Sidebar, type PanelOptions } from './sidebar';\nimport type { Zine } from '../zine';\n\n/** Width of one page thumbnail, in px. A paired rail fits two of these side by side. */\nconst THUMB_WIDTH = 78;\n/** Padding inside the rail, per side. */\nconst RAIL_PAD = 6;\n/** Gap between the two pages of a spread. */\nconst CELL_GAP = 2;\n\n/**\n * A rail of page thumbnails beside the book.\n *\n * Rows mirror the book's own spread model, so a `double` book shows two pages per row and a\n * `single` book one — including the responsive fallback that forces single-page on a narrow\n * container. A book that mixes the two (`cover`, `book`) keeps two columns so pairs line up, and\n * centres its lone first/last page between them. Clicking a row turns to that spread.\n *\n * Pages are drawn only as they scroll into view: a long PDF would otherwise decode every page at\n * once the moment the rail opened.\n */\nexport class Thumbnails {\n /** The panel's themed root, so the toolbar can stamp its colour scheme. */\n readonly root: HTMLElement;\n #zine: Zine;\n #doc: Document;\n #bar: Sidebar;\n #rows: { el: HTMLElement; spread: number; pages: number[] }[] = [];\n #observer: IntersectionObserver | null = null;\n #unsubscribe: (() => void)[] = [];\n\n constructor(zine: Zine, container: HTMLElement, options: PanelOptions = {}) {\n this.#zine = zine;\n const doc = container.ownerDocument!;\n this.#doc = doc;\n this.#bar = new Sidebar(doc, container, {\n className: 'zine-thumbs',\n label: 'Pages',\n width: THUMB_WIDTH + RAIL_PAD * 2,\n onDismiss: options.onDismiss,\n rtl: zine.getDirection() === 'rtl',\n pageBox: () => zine.getPageBox(),\n });\n this.root = this.#bar.root;\n\n this.#build();\n\n const onPage = (): void => this.#markCurrent();\n this.#unsubscribe.push(zine.on('pageChanged', onPage));\n this.#markCurrent();\n }\n\n #build(): void {\n const spreads = this.#zine.getSpreads();\n const rtl = this.#zine.getDirection() === 'rtl';\n // Two columns only if the book actually pairs pages somewhere. A wholly single-page book\n // gets a one-page-wide rail rather than a column of half-empty rows.\n const paired = spreads.some((s) => s.left !== null && s.right !== null);\n this.#bar.root.classList.toggle('zine-thumbs-solo', !paired);\n if (paired) this.#bar.setWidth(THUMB_WIDTH * 2 + CELL_GAP + RAIL_PAD * 2);\n\n for (const [index, spread] of spreads.entries()) {\n const row = this.#doc.createElement('button');\n row.type = 'button';\n row.className = 'zine-thumbs-row';\n row.setAttribute('role', 'option');\n if (rtl) row.style.flexDirection = 'row-reverse';\n\n const pages = [spread.left, spread.right].filter((p): p is number => p !== null);\n // A lone page in a book that pairs elsewhere keeps a single page's width and sits in the\n // middle, rather than stretching across both columns or hugging one side.\n if (paired && pages.length === 1) row.classList.add('zine-thumbs-row-lone');\n\n for (const page of pages) {\n const cell = this.#doc.createElement('span');\n cell.className = 'zine-thumbs-cell';\n cell.dataset.page = String(page);\n row.appendChild(cell);\n }\n\n const labels = [...pages].sort((a, b) => a - b).map((p) => p + 1);\n row.setAttribute(\n 'aria-label',\n labels.length > 1 ? `Pages ${labels[0]}–${labels.at(-1)}` : `Page ${labels[0] ?? ''}`,\n );\n\n const caption = this.#doc.createElement('span');\n caption.className = 'zine-thumbs-caption';\n caption.textContent = labels.join('–');\n row.appendChild(caption);\n\n row.addEventListener('click', () => {\n if (pages.length > 0) this.#zine.flipTo(Math.min(...pages));\n });\n this.#bar.root.appendChild(row);\n this.#rows.push({ el: row, spread: index, pages });\n }\n this.#observeRows();\n }\n\n /** Decode a row's pages the first time it comes into view. */\n #observeRows(): void {\n const cells = [...this.#bar.root.querySelectorAll<HTMLElement>('.zine-thumbs-cell[data-page]')];\n if (typeof IntersectionObserver === 'undefined') {\n for (const cell of cells) void this.#paint(cell);\n return;\n }\n this.#observer = new IntersectionObserver(\n (entries, obs) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n obs.unobserve(entry.target);\n void this.#paint(entry.target as HTMLElement);\n }\n },\n { root: this.#bar.root, rootMargin: '200px' },\n );\n for (const cell of cells) this.#observer.observe(cell);\n }\n\n async #paint(cell: HTMLElement): Promise<void> {\n const page = Number(cell.dataset.page);\n const content = await this.#zine.getPageImage(page);\n if (!content || !cell.isConnected) return;\n const w = (content as { width: number }).width;\n const h = (content as { height: number }).height;\n if (!w || !h) return;\n // One page's worth of pixels, whatever the rail's column count, so thumbs stay a consistent\n // size between modes. Scaled by dpr so they are not soft on a retina screen.\n const dpr = Math.min(2, globalThis.devicePixelRatio || 1);\n const canvas = this.#doc.createElement('canvas');\n canvas.width = Math.max(1, Math.round(THUMB_WIDTH * dpr));\n canvas.height = Math.max(1, Math.round(((THUMB_WIDTH * h) / w) * dpr));\n canvas.className = 'zine-thumbs-img';\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n try {\n ctx.drawImage(content as CanvasImageSource, 0, 0, canvas.width, canvas.height);\n } catch {\n return; // an unusable raster is not worth breaking the rail over\n }\n cell.replaceChildren(canvas);\n }\n\n /** Highlight the spread on screen and keep it scrolled into view. */\n #markCurrent(): void {\n // Key off the current page, not getSpreadIndex(): a `pageChanged` fires during the flip's\n // lead, a beat before the landing updates the book's spread index, so reading that index here\n // would highlight the spread we are leaving until the next flip. The page number is already\n // fresh at lead time, so the row that contains it is always the right one.\n const page = this.#zine.getPage();\n for (const { el, pages } of this.#rows) {\n const active = pages.includes(page);\n el.classList.toggle('zine-panel-active', active);\n el.setAttribute('aria-selected', String(active));\n if (active) el.scrollIntoView?.({ block: 'nearest' });\n }\n }\n\n destroy(): void {\n for (const off of this.#unsubscribe) off();\n this.#unsubscribe = [];\n this.#observer?.disconnect();\n this.#observer = null;\n this.#bar.destroy();\n }\n}\n","import { Sidebar, type PanelOptions } from './sidebar';\nimport type { OutlineItem } from '../source/types';\nimport type { Zine } from '../zine';\n\n/** Rail width in px. Wider than the thumbnail rail because it holds headings, not pictures. */\nconst RAIL_WIDTH = 208;\n/** Indent per nesting level, in px. */\nconst INDENT = 12;\n\n/**\n * The document's table of contents beside the book.\n *\n * One row per heading, nested entries indented under their parent. Clicking a row turns to the\n * page it points at; a heading whose destination could not be resolved is still listed, just not\n * clickable. Unlike the thumbnail rail this is a single column — headings are text, and a second\n * column would only make them narrower.\n */\nexport class Outline {\n /** The panel's themed root, so the toolbar can stamp its colour scheme. */\n readonly root: HTMLElement;\n #zine: Zine;\n #doc: Document;\n #bar: Sidebar;\n #rows: { el: HTMLElement; page: number }[] = [];\n #unsubscribe: (() => void)[] = [];\n #onDismiss?: () => void;\n\n constructor(zine: Zine, container: HTMLElement, options: PanelOptions = {}) {\n this.#zine = zine;\n const doc = container.ownerDocument!;\n this.#doc = doc;\n this.#onDismiss = options.onDismiss;\n this.#bar = new Sidebar(doc, container, {\n className: 'zine-outline',\n label: 'Outline',\n width: RAIL_WIDTH,\n onDismiss: options.onDismiss,\n rtl: zine.getDirection() === 'rtl',\n overlay: true, // floats over the book's edge on a wide screen rather than shrinking it\n pageBox: () => zine.getPageBox(),\n });\n this.root = this.#bar.root;\n\n this.#note('Loading…');\n void this.#build();\n\n this.#unsubscribe.push(zine.on('pageChanged', () => this.#markCurrent()));\n }\n\n #note(text: string): void {\n const note = this.#doc.createElement('div');\n note.className = 'zine-panel-note';\n note.textContent = text;\n this.#bar.root.replaceChildren(note);\n }\n\n async #build(): Promise<void> {\n const items = await this.#zine.getOutline();\n if (!this.#bar.root.isConnected) return; // closed while the outline was loading\n if (items.length === 0) {\n // The toolbar hides the control when there is nothing to list, so this is only reachable\n // through a hand-written layout that names 'outline' regardless.\n this.#note('This document has no outline.');\n return;\n }\n this.#bar.root.replaceChildren();\n this.#addLevel(items, 0);\n this.#markCurrent();\n }\n\n #addLevel(items: OutlineItem[], depth: number): void {\n for (const item of items) {\n const row = this.#doc.createElement('button');\n row.type = 'button';\n row.className = 'zine-outline-row';\n row.setAttribute('role', 'option');\n row.style.paddingLeft = `${8 + depth * INDENT}px`;\n row.textContent = item.title;\n row.title = item.title;\n if (item.page === null) {\n // Keep the heading visible but inert: it still tells the reader what is in the document.\n row.disabled = true;\n } else {\n const page = item.page;\n row.setAttribute('aria-label', `${item.title}, page ${page + 1}`);\n row.addEventListener('click', () => {\n this.#zine.flipTo(page);\n this.#onDismiss?.(); // clicking a heading has served its purpose; get the rail out of the way\n });\n this.#rows.push({ el: row, page });\n }\n this.#bar.root.appendChild(row);\n if (item.children.length > 0) this.#addLevel(item.children, depth + 1);\n }\n }\n\n /**\n * Mark the entry the reader is inside — the last one at or before the current page, rather\n * than an exact match, since a section spans many pages and rarely starts on the one shown.\n */\n #markCurrent(): void {\n const current = this.#zine.getPage();\n let best: HTMLElement | null = null;\n let bestPage = -1;\n for (const { el, page } of this.#rows) {\n el.classList.remove('zine-panel-active');\n el.removeAttribute('aria-selected');\n if (page <= current && page >= bestPage) {\n best = el;\n bestPage = page;\n }\n }\n if (!best) return;\n best.classList.add('zine-panel-active');\n best.setAttribute('aria-selected', 'true');\n best.scrollIntoView?.({ block: 'nearest' });\n }\n\n destroy(): void {\n for (const off of this.#unsubscribe) off();\n this.#unsubscribe = [];\n this.#bar.destroy();\n }\n}\n","import { Sidebar, type PanelOptions } from './sidebar';\nimport type { Zine } from '../zine';\n\n/** Rail width in px. Matches the outline: both hold text, not pictures. */\nconst RAIL_WIDTH = 208;\n/** Wait after the last keystroke before searching, so typing does not scan per character. */\nconst DEBOUNCE_MS = 180;\n/** Shorter queries match too much to be useful. */\nconst MIN_QUERY = 2;\n\n/**\n * Full-text search beside the book: a query field over its results.\n *\n * It lives in the same rail as the thumbnails and outline rather than a popover, because results\n * are an open-ended list — a floating panel over the book would either cover the page being\n * searched or clip the very results it found.\n */\nexport class Search {\n /** The panel's themed root, so the toolbar can stamp its colour scheme. */\n readonly root: HTMLElement;\n #zine: Zine;\n #doc: Document;\n #bar: Sidebar;\n #input: HTMLInputElement;\n #hits: HTMLElement;\n /** Increments per query so a slow search that resolves late cannot overwrite a newer one. */\n #run = 0;\n #timer: ReturnType<typeof setTimeout> | null = null;\n #onDismiss?: () => void;\n\n constructor(zine: Zine, container: HTMLElement, options: PanelOptions = {}) {\n this.#zine = zine;\n const doc = container.ownerDocument!;\n this.#doc = doc;\n this.#onDismiss = options.onDismiss;\n this.#bar = new Sidebar(doc, container, {\n className: 'zine-search',\n label: 'Search results',\n width: RAIL_WIDTH,\n onDismiss: options.onDismiss,\n rtl: zine.getDirection() === 'rtl',\n overlay: true, // floats over the book's edge on a wide screen rather than shrinking it\n pageBox: () => zine.getPageBox(),\n });\n this.root = this.#bar.root;\n\n this.#input = doc.createElement('input');\n this.#input.type = 'search';\n this.#input.className = 'zine-search-input';\n this.#input.placeholder = 'Search…';\n this.#input.setAttribute('aria-label', 'Search the document');\n this.#input.addEventListener('input', () => {\n if (this.#timer) clearTimeout(this.#timer);\n this.#timer = setTimeout(() => void this.#search(), DEBOUNCE_MS);\n });\n // Enter searches at once rather than waiting out the debounce.\n this.#input.addEventListener('keydown', (e) => {\n if ((e as KeyboardEvent).key !== 'Enter') return;\n if (this.#timer) clearTimeout(this.#timer);\n void this.#search();\n });\n\n this.#hits = doc.createElement('div');\n this.#hits.className = 'zine-search-hits';\n\n this.#bar.root.append(this.#input, this.#hits);\n this.#input.focus();\n }\n\n #note(text: string): void {\n const note = this.#doc.createElement('div');\n note.className = 'zine-panel-note';\n note.textContent = text;\n this.#hits.replaceChildren(note);\n }\n\n async #search(): Promise<void> {\n const query = this.#input.value.trim();\n const mine = ++this.#run;\n if (query.length < MIN_QUERY) {\n this.#hits.replaceChildren();\n return;\n }\n this.#note('Searching…');\n const results = await this.#zine.search(query);\n if (mine !== this.#run || !this.#bar.root.isConnected) return; // superseded, or closed\n if (results.length === 0) {\n this.#note(`No matches for “${query}”`);\n return;\n }\n this.#hits.replaceChildren(\n ...results.map((hit) => {\n const row = this.#doc.createElement('button');\n row.type = 'button';\n row.className = 'zine-search-hit';\n row.setAttribute('role', 'option');\n row.setAttribute('aria-label', `Page ${hit.page + 1}: ${hit.excerpt}`);\n const label = this.#doc.createElement('span');\n label.className = 'zine-search-page';\n label.textContent = `Page ${hit.page + 1}`;\n const excerpt = this.#doc.createElement('small');\n excerpt.textContent = hit.excerpt;\n row.append(label, excerpt);\n row.addEventListener('click', () => {\n this.#zine.flipTo(hit.page);\n this.#onDismiss?.(); // jump to the hit, then get the rail out of the way\n });\n return row;\n }),\n );\n }\n\n destroy(): void {\n if (this.#timer) clearTimeout(this.#timer);\n this.#timer = null;\n this.#run++; // any search still in flight is now stale\n this.#bar.destroy();\n }\n}\n","import { createIcon, ICONS } from './icons';\nimport { applyColorScheme } from './styles';\nimport type { ControlsColorScheme } from './types';\nimport type { Zine } from '../zine';\n\n/**\n * Big page-turn arrows flanking the book.\n *\n * They sit *beside* the book rather than over it, for the same reason the docked toolbar does:\n * the container's measured box is what the renderer sizes pages from and what pointer\n * hit-testing maps into, so anything inside it would shrink the book and skew every tap. The\n * book (with whatever the toolbar already wrapped around it) goes in a flex row between them.\n *\n * An arrow disables itself at the end of the book rather than disappearing, so the layout does\n * not jump as the reader reaches the covers.\n */\nexport class Arrows {\n #zine: Zine;\n #prev: HTMLButtonElement;\n #next: HTMLButtonElement;\n #wrap: HTMLElement | null = null;\n #device: 'desktop' | 'mobile' | null;\n #unsubscribe: (() => void)[] = [];\n\n constructor(\n zine: Zine,\n container: HTMLElement,\n colorScheme: ControlsColorScheme = 'auto',\n // null: show everywhere. 'desktop'/'mobile': show only on that device; a class on the wrap\n // lets CSS hide the arrows on the other side of the layout breakpoint (see styles.ts).\n device: 'desktop' | 'mobile' | null = null,\n ) {\n this.#zine = zine;\n this.#device = device;\n const doc = container.ownerDocument!;\n\n const rtl = zine.getDirection() === 'rtl';\n // The arrows point where the page goes, so in RTL they swap: \"back\" is to the right.\n this.#prev = this.#button(doc, rtl ? ICONS.next : ICONS.prev, 'Previous page', () =>\n zine.flipPrev(),\n );\n this.#next = this.#button(doc, rtl ? ICONS.prev : ICONS.next, 'Next page', () =>\n zine.flipNext(),\n );\n\n this.#place(doc, container);\n // The wrap holds both arrow buttons; color-scheme inherits, so stamping it themes both. In\n // the parentless fallback there is no wrap, so stamp the buttons directly instead.\n if (this.#wrap) applyColorScheme(this.#wrap, colorScheme);\n else for (const btn of [this.#prev, this.#next]) applyColorScheme(btn, colorScheme);\n\n // pageChanged fires at the animation lead and carries the landed page; flipEnd is the safety\n // net for a landing that never led (reduced motion, a canceled lead). See #refresh for why the\n // page matters.\n this.#unsubscribe.push(zine.on('pageChanged', ({ page }) => this.#refresh(page)));\n this.#unsubscribe.push(zine.on('flipEnd', () => this.#refresh()));\n this.#refresh();\n }\n\n #button(\n doc: Document,\n icon: string,\n label: string,\n onClick: () => void,\n ): HTMLButtonElement {\n const btn = doc.createElement('button');\n btn.type = 'button';\n btn.className = 'zine-arrow';\n btn.title = label;\n btn.setAttribute('aria-label', label);\n btn.appendChild(createIcon(doc, icon));\n btn.addEventListener('click', onClick);\n // The book's gesture handlers sit on the container; an arrow press is not a page tap.\n for (const type of ['pointerdown', 'pointerup', 'click']) {\n btn.addEventListener(type, (e) => e.stopPropagation());\n }\n return btn;\n }\n\n #place(doc: Document, container: HTMLElement): void {\n // Wrap the outermost thing the library has already built around the book — a docked toolbar\n // or a side panel — so the arrows flank the whole assembly, not just the pages.\n const outer =\n container.closest('.zine-panel-wrap') ??\n container.closest('.zine-controls-wrap') ??\n container;\n const parent = outer.parentNode;\n if (!parent) return; // not in a document; nothing sensible to flank\n const wrap = doc.createElement('div');\n wrap.className = 'zine-arrows-wrap';\n if (this.#device) wrap.classList.add(`zine-arrows-${this.#device}`);\n parent.insertBefore(wrap, outer);\n wrap.append(this.#prev, outer, this.#next);\n this.#wrap = wrap;\n }\n\n /**\n * Hide an arrow that has nowhere to go — no back arrow on the first page.\n *\n * Hidden rather than removed so its slot stays reserved: dropping it from the flow would slide\n * the book sideways every time the reader reached a cover. `disabled` comes along for the ride\n * so the button is inert even if a consumer's own CSS makes it visible again.\n */\n #refresh(page?: number): void {\n const { prev, next } = this.#edges(page);\n for (const [btn, canTurn] of [\n [this.#prev, prev],\n [this.#next, next],\n ] as const) {\n btn.disabled = !canTurn;\n btn.classList.toggle('zine-arrow-hidden', !canTurn);\n }\n }\n\n /**\n * Which arrows have somewhere to go.\n *\n * `canFlipPrev`/`canFlipNext` read the committed spread index, which only updates once a flip\n * lands — so refreshing off them at the animation lead (when `pageChanged` fires, a few ms\n * before the commit) leaves the arrow a step behind until the fold fully settles. When the\n * event hands us the page that just landed, resolve its spread from the public spread model so\n * the arrow corrects the instant the fold reads as landed. Fall back to the committed edge state\n * when there is no page in hand (initial paint, `flipEnd`) or the page is not in any spread.\n */\n #edges(page?: number): { prev: boolean; next: boolean } {\n if (page !== undefined) {\n const spreads = this.#zine.getSpreads();\n const index = spreads.findIndex((s) => s.left === page || s.right === page);\n if (index >= 0) return { prev: index > 0, next: index + 1 < spreads.length };\n }\n return { prev: this.#zine.canFlipPrev(), next: this.#zine.canFlipNext() };\n }\n\n destroy(): void {\n for (const off of this.#unsubscribe) off();\n this.#unsubscribe = [];\n this.#prev.remove();\n this.#next.remove();\n const wrap = this.#wrap;\n if (wrap?.parentNode) {\n // The arrows are gone, so what is left is the book; put it back where the wrapper was.\n while (wrap.firstChild) wrap.parentNode.insertBefore(wrap.firstChild, wrap);\n wrap.remove();\n }\n this.#wrap = null;\n }\n}\n","import { createIcon, ICONS } from './icons';\nimport { DEFAULT_ITEMS, defineControl, resolveControl } from './registry';\nimport { registerBuiltins } from './builtins';\nimport { applyColorScheme, ensureStyles } from './styles';\nimport { Thumbnails } from './thumbnails';\nimport { Outline } from './outline';\nimport { Search } from './search';\nimport { Arrows } from './arrows';\n\n/** The side panels, all of which share the one rail beside the book. */\nconst PANELS = { thumbnails: Thumbnails, outline: Outline, search: Search };\ntype PanelKind = keyof typeof PANELS;\n/** What every panel has in common: build itself on construction, and clean up after itself. */\ntype Panel = InstanceType<(typeof PANELS)[PanelKind]>;\nimport type {\n ControlContext,\n ControlDef,\n ControlItem,\n ControlsColorScheme,\n ControlsOptions,\n ControlsPosition,\n} from './types';\nimport type { Zine } from '../zine';\n\n/**\n * Events the container listens to for flipping, zooming and dragging. The gesture layer binds\n * these on the container and never checks `event.target`, so anything the toolbar handles has to\n * be stopped here or a button press would also register as a page tap.\n */\nconst SWALLOWED = ['pointerdown', 'pointerup', 'pointermove', 'click', 'dblclick', 'wheel', 'keydown'];\n\ninterface Popover {\n el: HTMLElement;\n trigger: HTMLElement;\n}\n\nexport class Toolbar {\n #zine: Zine;\n #doc: Document;\n #root: HTMLElement;\n #bar: HTMLElement;\n /** Every control on screen, buttons and `render` widgets alike, so #refresh can reach them. */\n #buttons: { el: HTMLElement; def: ControlDef }[] = [];\n #popover: Popover | null = null;\n #unsubscribe: (() => void)[] = [];\n #pageInput: HTMLInputElement | null = null;\n /** The flex wrapper docked mode inserts around the container; unwound on destroy. */\n #wrap: HTMLElement | null = null;\n #container: HTMLElement;\n /** The side panel currently in the rail. Only one at a time — they share the space. */\n #panel: Panel | null = null;\n #panelKind: PanelKind | null = null;\n /** Whether the document turned out to have any outline entries. Null until known: the answer\n * is async, and the outline control stays hidden rather than flash in and out. */\n #hasOutline: boolean | null = null;\n #arrows: Arrows | null = null;\n /** Which palette the built-in colours resolve to. Passed on to the arrows, panels and share\n * dialog, each of which mounts its own root outside this one and so must be stamped too. */\n #colorScheme: ControlsColorScheme;\n /** The edge the bar sits on, kept so a popover can open away from it. */\n #position: ControlsPosition;\n\n constructor(zine: Zine, container: HTMLElement, options: ControlsOptions) {\n this.#zine = zine;\n this.#container = container;\n const doc = container.ownerDocument!;\n this.#doc = doc;\n ensureStyles(doc);\n\n this.#colorScheme = options.colorScheme ?? 'auto';\n const position = options.position ?? 'bottom';\n this.#position = position;\n const docked = options.docked ?? true;\n this.#root = doc.createElement('div');\n this.#root.className = [\n 'zine-controls',\n `zine-controls-${position}`,\n docked ? 'zine-controls-docked' : 'zine-controls-floating',\n options.className,\n ]\n .filter(Boolean)\n .join(' ');\n this.#root.setAttribute('role', 'toolbar');\n this.#root.setAttribute('aria-label', 'Flipbook controls');\n applyColorScheme(this.#root, this.#colorScheme);\n\n this.#bar = doc.createElement('div');\n this.#bar.className = 'zine-controls-bar';\n this.#root.appendChild(this.#bar);\n\n // Toolbar keys are handled first, then every listed event is stopped so none of it reaches\n // the book's gesture, zoom or keyboard handlers on the container.\n this.#root.addEventListener('keydown', (e) => this.#onKeyDown(e as KeyboardEvent));\n for (const event of SWALLOWED) {\n this.#root.addEventListener(event, (e) => this.#isolate(e));\n }\n this.#place(container, position, docked);\n\n const refresh = (): void => this.#refresh();\n this.#unsubscribe.push(zine.on('pageChanged', refresh));\n this.#unsubscribe.push(zine.on('zoomChanged', refresh));\n this.#unsubscribe.push(zine.on('flipEnd', refresh));\n const onDocPointer = (e: Event): void => {\n if (!this.#popover) return;\n const target = e.target as Node;\n // Leave a press on the trigger to its own click handler, which toggles the menu shut —\n // closing here first would let that click reopen it, so it would never close.\n if (this.#popover.el.contains(target) || this.#popover.trigger.contains(target)) return;\n this.#closePopover();\n };\n doc.addEventListener('pointerdown', onDocPointer, true);\n this.#unsubscribe.push(() => doc.removeEventListener('pointerdown', onDocPointer, true));\n }\n\n /**\n * Put the toolbar in the page.\n *\n * Floating is simple: a child of the container, absolutely positioned over the book.\n *\n * Docked has to sit *outside* the container, because the container's measured box is what the\n * renderer sizes the book from and what pointer hit-testing maps into — a bar inside it would\n * shrink the book and skew every tap. So the container is wrapped in a flex column (or row),\n * with the toolbar as its sibling. The wrapper inherits the container's own layout box so the\n * arrangement the consumer's CSS set up is preserved.\n */\n #place(container: HTMLElement, position: ControlsPosition, docked: boolean): void {\n if (!docked) {\n container.appendChild(this.#root);\n return;\n }\n const parent = container.parentNode;\n if (!parent) {\n // Not in a document yet: fall back to overlaying rather than losing the toolbar.\n this.#root.classList.replace('zine-controls-docked', 'zine-controls-floating');\n container.appendChild(this.#root);\n return;\n }\n const wrap = this.#doc.createElement('div');\n wrap.className = `zine-controls-wrap zine-controls-wrap-${position}`;\n parent.insertBefore(wrap, container);\n wrap.appendChild(container);\n // 'top'/'left' put the bar first; flex-direction (set in CSS) handles the axis.\n if (position === 'top' || position === 'left') wrap.insertBefore(this.#root, container);\n else wrap.appendChild(this.#root);\n this.#wrap = wrap;\n }\n\n /** Build the layout. Separate from construction so instance-bound widgets (the page field, the\n * search panel) can be registered against this toolbar first. */\n mount(options: ControlsOptions): void {\n this.#build(options.items ?? DEFAULT_ITEMS);\n // After #place, so the arrows can wrap whatever it built around the book. A string value picks\n // the device the arrows show on; only an explicit false suppresses them entirely.\n const arrows = options.arrows ?? true;\n if (arrows !== false)\n this.#arrows = new Arrows(\n this.#zine,\n this.#container,\n this.#colorScheme,\n arrows === true ? null : arrows,\n );\n this.#refresh();\n this.#probeOutline();\n }\n\n destroy(): void {\n for (const off of this.#unsubscribe) off();\n this.#unsubscribe = [];\n this.#closePopover();\n this.#panel?.destroy();\n this.#panel = null;\n this.#panelKind = null;\n this.#root.remove();\n // Unwrap from the inside out: the arrows enclose the toolbar's wrapper, so removing theirs\n // first would leave this one orphaned outside the document.\n const wrap = this.#wrap;\n if (wrap?.parentNode) {\n wrap.parentNode.insertBefore(wrap.firstChild!, wrap);\n wrap.remove();\n }\n this.#wrap = null;\n this.#arrows?.destroy();\n this.#arrows = null;\n }\n\n /** Keep toolbar interaction from reaching the book's own gesture/zoom/keyboard handlers. */\n #isolate(event: Event): void {\n event.stopPropagation();\n }\n\n #context(close: () => void = () => this.#closePopover()): ControlContext {\n return { zine: this.#zine, close, colorScheme: this.#colorScheme };\n }\n\n #build(items: readonly ControlItem[]): void {\n for (const item of items) {\n if (item === '|') {\n const sep = this.#doc.createElement('div');\n sep.className = 'zine-controls-sep';\n this.#bar.appendChild(sep);\n continue;\n }\n const def = resolveControl(item);\n let el: HTMLElement;\n if (def.render) {\n el = def.render(this.#context());\n // Registered like any button so its predicates are re-read on every change; #button\n // does this for the controls it builds.\n this.#buttons.push({ el, def });\n } else {\n el = this.#button(def, 'zine-controls-btn');\n }\n this.#bar.appendChild(el);\n }\n }\n\n /** A control button. Bar buttons are icon-only with the title as tooltip; menu items and any\n * control without an icon also carry a visible label. */\n /** A control's label, which may be a function of the current state. */\n #titleOf(def: ControlDef): string {\n return typeof def.title === 'function' ? def.title(this.#context()) : def.title;\n }\n\n /** A control's glyph, which may likewise depend on state (an RTL mirror, say). */\n #iconOf(def: ControlDef): string | undefined {\n return typeof def.icon === 'function' ? def.icon(this.#context()) : def.icon;\n }\n\n #button(def: ControlDef, className: string, withLabel = false): HTMLButtonElement {\n const btn = this.#doc.createElement('button');\n const title = this.#titleOf(def);\n const icon = this.#iconOf(def);\n btn.type = 'button';\n btn.className = className;\n btn.title = title;\n btn.setAttribute('aria-label', title);\n if (icon) btn.appendChild(createIcon(this.#doc, icon));\n if (withLabel || !icon) {\n const label = this.#doc.createElement('span');\n label.className = 'zine-controls-label';\n label.textContent = title;\n btn.appendChild(label);\n }\n btn.addEventListener('click', () => this.#activate(def, btn));\n this.#buttons.push({ el: btn, def });\n return btn;\n }\n\n #activate(def: ControlDef, trigger: HTMLButtonElement): void {\n if (def.isDisabled?.(this.#context())) return;\n // A submenu toggles its popover; a second press on the same trigger closes it.\n if (def.children) {\n if (this.#popover?.trigger === trigger) this.#closePopover();\n else this.#openMenu(def, trigger);\n return;\n }\n if (trigger.classList.contains('zine-controls-busy')) return; // a slow action already running\n const result = def.action?.(this.#context());\n // A synchronous action is done; a promise means slow work (fetching a cross-origin file to save\n // or print). Mark the button busy and hold it there until the work settles. The action closes\n // its own menu when ready, so the spinner stays visible on the still-open item meanwhile.\n if (result && typeof result.then === 'function') {\n this.#setBusy(trigger, true);\n void result.finally(() => {\n this.#setBusy(trigger, false);\n this.#refresh();\n });\n }\n this.#refresh();\n }\n\n /** Toggle the pending-action state on a button: a trailing spinner, disabled while it spins. */\n #setBusy(btn: HTMLButtonElement, busy: boolean): void {\n btn.classList.toggle('zine-controls-busy', busy);\n btn.setAttribute('aria-busy', String(busy));\n btn.disabled = busy;\n }\n\n #openMenu(def: ControlDef, trigger: HTMLButtonElement): void {\n this.#closePopover();\n const menu = this.#doc.createElement('div');\n menu.className = 'zine-controls-menu';\n menu.setAttribute('role', 'menu');\n const ctx = this.#context();\n for (const child of def.children ?? []) {\n if (child === '|') continue;\n const childDef = resolveControl(child);\n if (childDef.isVisible && !childDef.isVisible(ctx)) continue;\n let item: HTMLElement;\n if (childDef.render) {\n item = childDef.render(this.#context());\n this.#buttons.push({ el: item, def: childDef });\n } else {\n item = this.#button(childDef, 'zine-controls-btn', true);\n }\n item.setAttribute('role', 'menuitem');\n menu.appendChild(item);\n }\n this.#showPopover(menu, trigger);\n // The entries were only just built, so nothing has evaluated their state yet: without this\n // a menu opens with every entry enabled until the next page turn happens to refresh it.\n this.#refresh();\n menu.querySelector('button')?.focus();\n }\n\n #showPopover(el: HTMLElement, trigger: HTMLElement): void {\n this.#root.appendChild(el);\n this.#popover = { el, trigger };\n trigger.setAttribute('aria-expanded', 'true');\n this.#placePopover(el, trigger);\n }\n\n /** Anchor a popover to its trigger, flipped to stay inside the book. */\n #placePopover(el: HTMLElement, trigger: HTMLElement): void {\n const root = this.#root.getBoundingClientRect();\n const anchor = trigger.getBoundingClientRect();\n const box = el.getBoundingClientRect();\n if (!root.width || !box.width) return; // not laid out (e.g. jsdom): leave to CSS\n let left = anchor.left - root.left + anchor.width / 2 - box.width / 2;\n left = Math.max(4, Math.min(left, root.width - box.width - 4));\n el.style.left = `${left}px`;\n const below = anchor.bottom - root.top + 6;\n const above = anchor.top - root.top - box.height - 6;\n // A bottom bar opens its menu upward (above the button), so it never spills off the bottom\n // edge — the book above always has the room. Every other edge keeps the pick-whichever-fits\n // behaviour, preferring upward only when there is space for it.\n el.style.top = `${this.#position === 'bottom' ? above : above >= 0 ? above : below}px`;\n }\n\n #closePopover(): void {\n if (!this.#popover) return;\n const { el, trigger } = this.#popover;\n this.#popover = null;\n trigger.removeAttribute('aria-expanded');\n // Drop buttons that belonged to the popover so #refresh stops touching detached nodes.\n this.#buttons = this.#buttons.filter((b) => !el.contains(b.el));\n el.remove();\n if (this.#doc.activeElement && el.contains(this.#doc.activeElement)) trigger.focus();\n }\n\n /** Arrow keys move between controls; Escape dismisses an open popover. */\n #onKeyDown(event: KeyboardEvent): void {\n if (event.key === 'Escape' && this.#popover) {\n const { trigger } = this.#popover;\n this.#closePopover();\n trigger.focus();\n event.preventDefault();\n return;\n }\n const focusables = [...this.#root.querySelectorAll<HTMLElement>('button, input')];\n const at = focusables.indexOf(this.#doc.activeElement as HTMLElement);\n if (at < 0) return;\n // Let the page-number field use its own arrows for text editing.\n if (this.#doc.activeElement instanceof HTMLInputElement) return;\n const step = event.key === 'ArrowRight' || event.key === 'ArrowDown' ? 1 : event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -1 : 0;\n if (step === 0) return;\n const next = focusables[(at + step + focusables.length) % focusables.length];\n next?.focus();\n event.preventDefault();\n }\n\n /**\n * Re-read every control's state after the book changes.\n *\n * Applies to custom widgets built with `render` as well as to plain buttons: the predicates\n * are on `ControlDef`, so honouring them only for button-shaped controls would make the same\n * field mean different things depending on how a control chose to draw itself. A widget may be\n * any element, so each piece of state is applied in whatever way that element supports.\n */\n #refresh(): void {\n const ctx = this.#context();\n for (const { el, def } of this.#buttons) {\n // Does not apply to this book at all: take the space back, it is not coming again.\n const applies = def.children ? this.#hasVisibleChildren(def, ctx) : def.isVisible?.(ctx);\n if (applies !== undefined) el.style.display = applies ? '' : 'none';\n\n // A button running a slow action stays disabled until it settles, whatever its predicate says.\n const busy = el.classList.contains('zine-controls-busy');\n const disabled = (def.isDisabled?.(ctx) ?? false) || busy;\n // A wrapper element cannot be disabled, so mark it and disable the fields inside.\n for (const field of this.#formFields(el)) field.disabled = disabled;\n if (!this.#formFields(el).length) el.setAttribute('aria-disabled', String(disabled));\n el.classList.toggle('zine-controls-off', disabled);\n\n if (def.isActive) el.setAttribute('aria-pressed', String(def.isActive(ctx)));\n if (typeof def.title === 'function') {\n const title = def.title(ctx);\n el.title = title;\n el.setAttribute('aria-label', title);\n const label = el.querySelector('.zine-controls-label');\n if (label) label.textContent = title;\n }\n if (typeof def.icon === 'function') {\n el.querySelector('svg')?.replaceWith(createIcon(this.#doc, def.icon(ctx)));\n }\n }\n if (this.#pageInput && this.#doc.activeElement !== this.#pageInput) {\n this.#pageInput.value = String(this.#zine.getPage() + 1);\n }\n }\n\n /** The element itself if it can be disabled, else any inputs/buttons it wraps. */\n #formFields(el: HTMLElement): (HTMLButtonElement | HTMLInputElement)[] {\n if (el instanceof HTMLButtonElement || el instanceof HTMLInputElement) return [el];\n return [...el.querySelectorAll<HTMLButtonElement | HTMLInputElement>('button, input')];\n }\n\n /** A submenu is only worth showing if something inside it is. Keeps the overflow button from\n * opening onto nothing when every entry has hidden itself. */\n #hasVisibleChildren(def: ControlDef, ctx: ControlContext): boolean {\n if (def.isVisible && !def.isVisible(ctx)) return false;\n return (def.children ?? []).some((child) => {\n if (child === '|') return false;\n const childDef = resolveControl(child);\n return childDef.isVisible ? childDef.isVisible(ctx) : true;\n });\n }\n\n /** Registered lazily by {@link registerWidgets} so the widget can reach this instance. */\n makePageInput(): HTMLElement {\n const wrap = this.#doc.createElement('div');\n wrap.className = 'zine-controls-page';\n const input = this.#doc.createElement('input');\n input.type = 'number';\n input.min = '1';\n input.max = String(this.#zine.getPageCount());\n input.value = String(this.#zine.getPage() + 1);\n input.setAttribute('aria-label', 'Page number');\n const total = this.#doc.createElement('span');\n total.textContent = `/ ${this.#zine.getPageCount()}`;\n\n // Enter commits and then blurs, and the blur commits again; a click-away blur commits on its\n // own. Guard so only the first call per editing session navigates: flipTo animates, so a\n // second commit right after would read the still-old getPage() and turn the book back to it —\n // the page that is (still) in the URL hash. Re-armed on focus for the next edit.\n let committed = false;\n const commit = (): void => {\n if (committed) return;\n committed = true;\n const n = Number(input.value);\n const last = Math.max(0, this.#zine.getPageCount() - 1);\n const target = Number.isFinite(n)\n ? Math.min(Math.max(Math.round(n) - 1, 0), last)\n : this.#zine.getPage();\n if (target !== this.#zine.getPage()) {\n this.#zine.flipTo(target); // #refresh (pageChanged/flipEnd) resyncs the field once it lands\n } else {\n input.value = String(this.#zine.getPage() + 1); // no navigation: restore the display now\n }\n };\n input.addEventListener('focus', () => {\n committed = false;\n });\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n commit();\n input.blur();\n }\n });\n input.addEventListener('blur', commit);\n wrap.append(input, total);\n this.#pageInput = input;\n return wrap;\n }\n\n /** Which side panel is showing, if any. */\n openPanel(): PanelKind | null {\n return this.#panelKind;\n }\n\n /**\n * Whether this document has a table of contents worth offering.\n *\n * Many PDFs have none, and a control that can only report its own emptiness is a dead end, so\n * the outline button stays hidden until the outline is known to be non-empty. The first call\n * kicks off the lookup and refreshes the toolbar when it lands; `Zine.getOutline` memoizes, so\n * the panel does not pay for it twice.\n */\n hasOutline(): boolean {\n return this.#hasOutline === true;\n }\n\n /** Ask the document whether it has an outline, and refresh once the answer lands. Started as\n * the toolbar mounts so the button settles into place immediately rather than on first use. */\n #probeOutline(): void {\n if (this.#hasOutline !== null || !this.#zine.canOutline()) return;\n this.#hasOutline = false; // assume not, until we hear otherwise\n void this.#zine.getOutline().then((items) => {\n if (items.length === 0) return;\n this.#hasOutline = true;\n this.#refresh();\n // The menu may already be open with the entry missing; rebuild it so it appears.\n const popover = this.#popover;\n if (popover) {\n const def = this.#buttons.find((b) => b.el === popover.trigger)?.def;\n this.#closePopover();\n if (def) this.#openMenu(def, popover.trigger as HTMLButtonElement);\n }\n });\n }\n\n /**\n * Show one of the side panels, or close it if it is already up. They share the rail beside the\n * book, so opening one closes the others. Rebuilt each time, so the thumbnail rail always\n * matches the current spread grouping (which `spreadMode` and the responsive fallback change).\n */\n togglePanel(kind: PanelKind): void {\n const wasOpen = this.#panelKind;\n this.#panel?.destroy();\n this.#panel = null;\n this.#panelKind = null;\n if (wasOpen !== kind) {\n // Reopening the same kind toggles it shut, the same path the toolbar button takes, so the\n // scrim and Escape can dismiss the narrow-screen drawer without any new close machinery.\n this.#panel = new PANELS[kind](this.#zine, this.#container, {\n onDismiss: () => this.togglePanel(kind),\n });\n applyColorScheme(this.#panel.root, this.#colorScheme);\n this.#panelKind = kind;\n }\n this.#refresh();\n }\n\n}\n\n/**\n * Controls that need the toolbar itself (a DOM widget, or a panel to open) are registered per\n * instance, since the registry is module-level and shared.\n */\nexport function registerWidgets(toolbar: Toolbar): void {\n registerBuiltins();\n defineControl({\n id: 'thumbnails',\n title: () => (toolbar.openPanel() === 'thumbnails' ? 'Hide thumbnails' : 'Show thumbnails'),\n icon: ICONS.thumbnails,\n // Documents only, for now: an image book is already a short, flat list of pictures.\n isVisible: (ctx) => ctx.zine.isDocument(),\n isActive: () => toolbar.openPanel() === 'thumbnails',\n action: (ctx) => {\n toolbar.togglePanel('thumbnails');\n ctx.close();\n },\n });\n defineControl({\n id: 'outline',\n title: () => (toolbar.openPanel() === 'outline' ? 'Hide outline' : 'Show outline'),\n icon: ICONS.outline,\n // Only when the document actually has entries to show.\n isVisible: () => toolbar.hasOutline(),\n isActive: () => toolbar.openPanel() === 'outline',\n action: (ctx) => {\n toolbar.togglePanel('outline');\n ctx.close();\n },\n });\n defineControl({\n id: 'pageInput',\n title: 'Page',\n render: () => toolbar.makePageInput(),\n });\n defineControl({\n id: 'search',\n title: () => (toolbar.openPanel() === 'search' ? 'Hide search' : 'Search'),\n icon: ICONS.search,\n isVisible: (ctx) => ctx.zine.canSearch(),\n isActive: () => toolbar.openPanel() === 'search',\n action: () => toolbar.togglePanel('search'),\n });\n}\n","import { registerWidgets, Toolbar } from './toolbar';\nimport type { ControlsOptions } from './types';\nimport type { Zine } from '../zine';\n\nexport { defineControl, getControl, DEFAULT_ITEMS } from './registry';\nexport { ICONS, createIcon } from './icons';\nexport type {\n ControlContext,\n ControlDef,\n ControlItem,\n ControlsColorScheme,\n ControlsOptions,\n ControlsPosition,\n} from './types';\n\n/**\n * Build the toolbar inside `container` and return its teardown.\n *\n * This module is the lazy chunk `zine.ts` reaches through a dynamic `import()`, so a book built\n * with `controls: false` never downloads the toolbar, its icons or its stylesheet. The file is\n * named `controls.ts` rather than `index.ts` so the emitted chunk is `controls-<hash>.js`, which\n * is what the size-limit budget watches.\n */\nexport function mountControls(\n zine: Zine,\n container: HTMLElement,\n options: ControlsOptions = {},\n): () => void {\n const toolbar = new Toolbar(zine, container, options);\n // Built-ins and the instance-bound widgets have to exist before the layout resolves ids.\n registerWidgets(toolbar);\n toolbar.mount(options);\n return () => toolbar.destroy();\n}\n"],"mappings":"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"}
|