@embedpdf/web 3.0.0-next.11 → 3.0.0-next.12
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/index.cjs +39 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1120,6 +1120,44 @@ function createStageSurface(el, stage, options = {}) {
|
|
|
1120
1120
|
};
|
|
1121
1121
|
}
|
|
1122
1122
|
//#endregion
|
|
1123
|
+
//#region src/byte-store.ts
|
|
1124
|
+
/** Open (creating on first use) one IndexedDB object store keyed by id. */
|
|
1125
|
+
function indexedDbByteStore(dbName, opts = {}) {
|
|
1126
|
+
const storeName = opts.storeName ?? "bytes";
|
|
1127
|
+
const open = () => new Promise((resolve, reject) => {
|
|
1128
|
+
const request = indexedDB.open(dbName, 1);
|
|
1129
|
+
request.onupgradeneeded = () => {
|
|
1130
|
+
if (!request.result.objectStoreNames.contains(storeName)) request.result.createObjectStore(storeName);
|
|
1131
|
+
};
|
|
1132
|
+
request.onsuccess = () => resolve(request.result);
|
|
1133
|
+
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("indexedDB.open failed"));
|
|
1134
|
+
});
|
|
1135
|
+
const run = async (mode, body) => {
|
|
1136
|
+
const db = await open();
|
|
1137
|
+
try {
|
|
1138
|
+
return await new Promise((resolve, reject) => {
|
|
1139
|
+
const request = body(db.transaction(storeName, mode).objectStore(storeName));
|
|
1140
|
+
request.onsuccess = () => resolve(request.result);
|
|
1141
|
+
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("indexedDB request failed"));
|
|
1142
|
+
});
|
|
1143
|
+
} finally {
|
|
1144
|
+
db.close();
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
return {
|
|
1148
|
+
list: async () => {
|
|
1149
|
+
const keys = await run("readonly", (store) => store.getAllKeys());
|
|
1150
|
+
const values = await run("readonly", (store) => store.getAll());
|
|
1151
|
+
return keys.map((key, i) => ({
|
|
1152
|
+
id: String(key),
|
|
1153
|
+
bytes: new Uint8Array(values[i])
|
|
1154
|
+
}));
|
|
1155
|
+
},
|
|
1156
|
+
put: (id, bytes) => run("readwrite", (store) => store.put(new Uint8Array(bytes), id)).then(() => void 0),
|
|
1157
|
+
delete: (id) => run("readwrite", (store) => store.delete(id)).then(() => void 0)
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
//#endregion
|
|
1123
1161
|
exports.attachSelectionHandle = attachSelectionHandle;
|
|
1124
1162
|
exports.bindPaintedImage = bindPaintedImage;
|
|
1125
1163
|
exports.computeReleaseVelocity = computeReleaseVelocity;
|
|
@@ -1127,6 +1165,7 @@ exports.copySelection = copySelection;
|
|
|
1127
1165
|
exports.createDefaultActionsUiAdapter = createDefaultActionsUiAdapter;
|
|
1128
1166
|
exports.createStageGestureController = createStageGestureController;
|
|
1129
1167
|
exports.createStageSurface = createStageSurface;
|
|
1168
|
+
exports.indexedDbByteStore = indexedDbByteStore;
|
|
1130
1169
|
exports.observeClientGeometry = observeClientGeometry;
|
|
1131
1170
|
exports.pickFile = pickFile;
|
|
1132
1171
|
exports.pickImageFile = pickImageFile;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["w"],"sources":["../src/file-picker.ts","../src/clipboard.ts","../src/anchored-position.ts","../src/cursor.ts","../src/external-uri.ts","../src/actions-ui.ts","../src/painted-image.ts","../src/feedback.ts","../src/wheel.ts","../src/stage-gestures.ts","../src/selection-handles.ts","../src/stage-surface.ts"],"sourcesContent":["/**\n * A one-shot file dialog. Opens a throwaway `<input type=file>`, resolves the\n * chosen `File`, or `null` when the dialog is dismissed. MUST be called\n * synchronously from a user gesture (a click) or the browser refuses to open it.\n *\n * A `File` is a `Blob`, which is a `BinarySource` everywhere in the stack, so the\n * caller can hand the result straight to the engine — this module stays a pure\n * DOM utility with no EmbedPDF types.\n */\nconst IMAGE_ACCEPT = 'image/png,image/jpeg,application/pdf';\n\nexport interface PickFileOptions {\n /** The dialog's `accept` filter. Defaults to PNG / JPEG / PDF. */\n accept?: string;\n /** Allow multiple selection (returns the first file). Defaults to false. */\n multiple?: boolean;\n}\n\n/** Open the file dialog; resolve the picked file, or null if cancelled. */\nexport function pickImageFile(options: PickFileOptions = {}): Promise<File | null> {\n const { accept = IMAGE_ACCEPT, multiple = false } = options;\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n input.multiple = multiple;\n input.style.display = 'none';\n\n let settled = false;\n const finish = (value: File | null): void => {\n if (settled) return;\n settled = true;\n window.removeEventListener('focus', onFocus);\n input.remove();\n resolve(value);\n };\n\n // A window refocus means the dialog closed. Give a queued `change` a tick to\n // win, then treat a bare refocus as a cancel — the fallback for browsers that\n // predate the `cancel` event.\n const onFocus = (): void => {\n setTimeout(() => finish(null), 300);\n };\n\n input.addEventListener('change', () => finish(input.files?.[0] ?? null), { once: true });\n input.addEventListener('cancel', () => finish(null), { once: true });\n window.addEventListener('focus', onFocus, { once: true });\n\n document.body.appendChild(input);\n input.click();\n });\n}\n\n/** The same dialog with NO type filter — any file (attachments). */\nexport const pickFile = (options: PickFileOptions = {}): Promise<File | null> =>\n pickImageFile({ accept: '*/*', ...options });\n\n/**\n * Hand bytes to the user as a browser download (a one-shot `<a download>`).\n * The DOM half of \"download this attachment\" — pair with the annotation\n * capability's `downloadAttachment` or the document `attachments.download`.\n */\nexport function saveFile(bytes: Uint8Array | Blob, name: string, mimeType?: string): void {\n const blob =\n bytes instanceof Blob\n ? bytes\n : new Blob([bytes as BlobPart], { type: mimeType ?? 'application/octet-stream' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = name;\n a.style.display = 'none';\n document.body.appendChild(a);\n a.click();\n a.remove();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n","/**\n * Clipboard wiring for text selection — the environmental half of copy.\n *\n * The selection plugin is deliberately DOM-free: its public capability ends\n * at `readText()` (data). Everything that touches `navigator.clipboard` or\n * the native `copy` event lives HERE, structurally typed so this module\n * stays a pure DOM utility with no EmbedPDF dependencies (the selection\n * capability satisfies {@link ClipboardSelectionSource} as-is).\n */\n\n/** The slice of the selection capability clipboard wiring needs. */\nexport interface ClipboardSelectionSource {\n hasSelection(): boolean;\n canCopy(): boolean;\n readText(): Promise<string>;\n onChange(cb: () => void): () => void;\n onCommit(cb: () => void): () => void;\n}\n\nexport interface SelectionClipboardOptions {\n /**\n * Prefetch the selected text when the selection settles (default true).\n * This is what makes the NATIVE copy path work: a `copy` event handler\n * must call `clipboardData.setData` synchronously — awaiting a page-text\n * read inside it is too late — and it also keeps the async path instant.\n * The fetch is one versioned, cached, permission-gated read per page;\n * pass false to make copying strictly on-demand.\n */\n prefetch?: boolean;\n /** Event target to listen on. Defaults to `document`. */\n target?: Document;\n}\n\nconst PREFETCH_DEBOUNCE_MS = 150;\n\n/**\n * Wire clipboard copy for a selection. Two delivery paths, both fed by the\n * prefetched text:\n *\n * - the native `copy` event (menu Edit→Copy, or ctrl/cmd+C while the page\n * has a DOM selection or focused editable — e.g. the viewer shell's\n * focus sink): answered SYNCHRONOUSLY from the cache;\n * - ctrl/cmd+C with no DOM selection (canvas-rendered viewers usually\n * have none, and browsers don't dispatch `copy` then): a keydown\n * fallback writes via the async Clipboard API inside the keystroke's\n * user-activation window.\n *\n * Returns the unwire function. Mount once per document view (not per page).\n */\nexport function wireSelectionClipboard(\n selection: ClipboardSelectionSource,\n options: SelectionClipboardOptions = {},\n): () => void {\n const target = options.target ?? document;\n const prefetch = options.prefetch ?? true;\n let cached: string | null = null;\n let generation = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const refresh = (): void => {\n cached = null;\n if (!prefetch || !selection.hasSelection() || !selection.canCopy()) return;\n const gen = ++generation;\n selection.readText().then(\n (text) => {\n if (gen === generation) cached = text;\n },\n () => {}, // denied / failed — the async path will surface it if used\n );\n };\n\n // Commit (gesture end) refreshes immediately; change (drag, programmatic\n // select) debounces so a drag doesn't fetch per pointer move.\n const offCommit = selection.onCommit(() => {\n if (timer) clearTimeout(timer);\n timer = null;\n refresh();\n });\n const offChange = selection.onChange(() => {\n generation++;\n cached = null;\n if (timer) clearTimeout(timer);\n timer = setTimeout(refresh, PREFETCH_DEBOUNCE_MS);\n });\n\n const onCopy = (e: ClipboardEvent): void => {\n if (cached == null || cached === '' || !selection.hasSelection()) return;\n e.clipboardData?.setData('text/plain', cached);\n e.preventDefault();\n };\n\n const onKeyDown = (e: KeyboardEvent): void => {\n if (!(e.metaKey || e.ctrlKey) || e.key.toLowerCase() !== 'c' || e.shiftKey || e.altKey) {\n return;\n }\n if (!selection.hasSelection() || !selection.canCopy()) return;\n // A live DOM selection means the browser will dispatch `copy` itself and\n // our synchronous handler answers — don't double-write.\n const dom = target.getSelection?.();\n if (dom && !dom.isCollapsed) return;\n void copySelection(selection); // instant when prefetched; still inside user activation\n };\n\n target.addEventListener('copy', onCopy);\n target.addEventListener('keydown', onKeyDown);\n return () => {\n offCommit();\n offChange();\n if (timer) clearTimeout(timer);\n target.removeEventListener('copy', onCopy);\n target.removeEventListener('keydown', onKeyDown);\n };\n}\n\n/**\n * Toolbar/menu path: read the selected text and write it to the clipboard\n * via the async Clipboard API. Call from a user-gesture handler (browsers\n * require a secure context + user activation for clipboard writes). Resolves\n * with what was copied ('' when nothing was — nothing is written then, so an\n * empty selection never clobbers the user's clipboard).\n */\nexport async function copySelection(selection: ClipboardSelectionSource): Promise<string> {\n const text = await selection.readText();\n if (text !== '') await navigator.clipboard.writeText(text);\n return text;\n}\n","/**\n * Anchored-overlay geometry for UI floating over page content (selection\n * menus, draft menus, popovers): the projector SNAPSHOT contract, the\n * anchor shape, and the placement math. Pure values and pure functions over\n * structural rect/point shapes — no EmbedPDF types, no framework, no\n * reactive lifecycle — so every framework adapter (React, Vue, Svelte,\n * Angular) shares ONE implementation and binds it with its NATIVE\n * reactivity (context revision, signals, computed, $derived).\n *\n * The boundary laws:\n * - Placement policy lives HERE, above the projector seam: a projector\n * only knows geometry — never what is being positioned nor where it\n * prefers to sit.\n * - A projector describes the CURRENT projection; it never implements a\n * framework-style reactive lifecycle. State-driven changes (the Stage\n * camera) must reach consumers through the framework's own render\n * cycle so surface and overlay commit TOGETHER; only genuinely\n * browser-driven changes (a PageView moving because the document\n * scrolled) use an external listener ({@link observeClientGeometry}).\n */\n\nexport interface AnchoredRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface AnchoredPoint {\n x: number;\n y: number;\n}\n\nexport type AnchoredPlacement = 'top' | 'right' | 'bottom' | 'left';\n\n/** CSS-ready output: absolute/fixed `left`/`top` plus a centering transform. */\nexport interface AnchoredPosition {\n left: number;\n top: number;\n transform: string;\n}\n\n/**\n * Place an upright element around `box` (screen px). `avoid` is a screen\n * point the element must clear (e.g. the rotate knob): the element extends\n * ONLY the edge it sits on, and ONLY when the point protrudes past that\n * edge — so it clears the obstacle without ever shifting off-centre on the\n * other axis. When the point is on another side (e.g. a 90° shape, knob at\n * mid-height for a `top` placement) the edge is untouched and the element\n * stays centred on `box`.\n */\n/** What anchored UI attaches to: a content-space rect on a page, plus\n * optional content-space points the UI must clear (e.g. a rotate knob).\n * Structural — plugin anchor reads satisfy it without importing this\n * package. */\nexport interface AnchorTarget {\n pon: number;\n bounds: AnchoredRect;\n avoid?: AnchoredPoint[];\n}\n\n/**\n * A page surface's PROJECTION SNAPSHOT: how a content-space rect on a page\n * becomes screen coordinates, right now. Provided by `<Stage>`\n * (camera-driven, pure state, no DOM reads) and `<PageView>` (DOM-measured).\n * Deliberately NO subscribe here — when projection changes is a framework\n * binding concern (see the module doc), not part of the snapshot.\n */\nexport interface ViewProjector {\n /**\n * Which coordinate space `toScreen` speaks, and therefore how anchored\n * content mounts:\n * - 'overlay': coords are relative to the surface's own positioned\n * container → rendered in place, position:absolute. (Stage)\n * - 'client': coords are viewport px → portalled to the document body,\n * position:fixed, so no ancestor overflow can clip. (PageView)\n */\n space: 'overlay' | 'client';\n /** Content-space rect on a page → coords in `space`. Null: not projectable\n * right now (page not shown / not measurable yet). */\n toScreen(pon: number, rect: AnchoredRect): AnchoredRect | null;\n toScreenPoint(pon: number, at: AnchoredPoint): AnchoredPoint | null;\n /** The page's live view facts (for anchor reads that need them — e.g. the\n * screen-constant rotate-knob stalk), or null when the page isn't shown. */\n viewEnv(pon: number): {\n scale: number;\n rotation: 0 | 90 | 180 | 270;\n zoom: number;\n } | null;\n}\n\n/**\n * THE anchored projection: anchor → screen position, in one pure call —\n * projection, avoid-point transform, and the shared placement policy.\n * Frameworks recompute this inside their native reactive primitive\n * (a React render, a Vue `computed`, an Angular signal, a Svelte\n * `$derived`); nothing here schedules anything.\n */\nexport function projectAnchoredTarget(\n projector: ViewProjector,\n anchor: AnchorTarget,\n placement: AnchoredPlacement,\n gap: number,\n): AnchoredPosition | null {\n const box = projector.toScreen(anchor.pon, anchor.bounds);\n if (!box) return null;\n const avoid = anchor.avoid?.length\n ? projector.toScreenPoint(anchor.pon, anchor.avoid[0])\n : null;\n return positionAnchoredRect(box, placement, gap, avoid);\n}\n\n/**\n * The one genuinely BROWSER-driven invalidation: an element in normal\n * document flow moves when the document scrolls or the window resizes —\n * no state change announces it. `<PageView>`'s binding registers this;\n * the Stage never does (its camera is state, delivered through render).\n */\nexport function observeClientGeometry(callback: () => void): () => void {\n window.addEventListener('scroll', callback, true);\n window.addEventListener('resize', callback);\n return () => {\n window.removeEventListener('scroll', callback, true);\n window.removeEventListener('resize', callback);\n };\n}\n\nexport function positionAnchoredRect(\n box: AnchoredRect,\n placement: AnchoredPlacement,\n gap: number,\n avoid?: AnchoredPoint | null,\n): AnchoredPosition {\n const cx = box.x + box.width / 2;\n const cy = box.y + box.height / 2;\n\n switch (placement) {\n case 'bottom': {\n const edge = Math.max(box.y + box.height, avoid ? avoid.y : -Infinity);\n return { left: cx, top: edge + gap, transform: 'translate(-50%, 0)' };\n }\n case 'left': {\n const edge = Math.min(box.x, avoid ? avoid.x : Infinity);\n return { left: edge - gap, top: cy, transform: 'translate(-100%, -50%)' };\n }\n case 'right': {\n const edge = Math.max(box.x + box.width, avoid ? avoid.x : -Infinity);\n return { left: edge + gap, top: cy, transform: 'translate(0, -50%)' };\n }\n case 'top':\n default: {\n const edge = Math.min(box.y, avoid ? avoid.y : Infinity);\n return { left: cx, top: edge - gap, transform: 'translate(-50%, -100%)' };\n }\n }\n}\n","/**\n * SVG → CSS cursor string. The armed-tool indicator is the CURSOR itself (the\n * only zero-latency pointer-locked pixel the web has — the OS composites it),\n * so an app hands its toolbar icon here and assigns the result via the\n * interaction hub's `setToolCursor`.\n *\n * Browser rules baked into this module:\n * - The SVG must carry explicit `width`/`height` attributes (not just a\n * viewBox) or Chromium/WebKit ignore it — enforced here.\n * - The hard ceiling is 128×128 CSS px (bigger falls back to the keyword).\n * Above 32×32, Chromium temporarily resets the cursor to default while it\n * would overlap browser UI (anti-spoofing) — fine mid-viewport, so ~40px\n * compositions are common; stay ≤128.\n * - The keyword fallback covers browsers that refuse SVG cursor images.\n *\n * Pure string work with no EmbedPDF types — like the file picker, this module\n * is a plain web utility.\n */\n\nexport interface SvgCursorOptions {\n /** Complete `<svg>` markup, colors baked in (no `currentColor` — a cursor\n * image has no cascade to inherit from). */\n svg: string;\n /** The click point inside the image, CSS px from the top-left. Default 0,0\n * (arrow-style: the image's corner is the tip). */\n hotspot?: { x: number; y: number };\n /** Keyword cursor when the image can't render. Default `'crosshair'`. */\n fallback?: string;\n}\n\n/** Build the CSS cursor value: `url(\"data:image/svg+xml,…\") x y, fallback`. */\nexport function svgCursor({ svg, hotspot, fallback = 'crosshair' }: SvgCursorOptions): string {\n // Scan only the opening `<svg …>` tag (bounded slice) rather than running\n // `/<svg[^>]*\\swidth\\s*=/` over the whole string — the unbounded form is\n // quadratic on pathological inputs (many `<svg` fragments with no `>`),\n // while accepting exactly the same markup: `[^>]*` could never cross a\n // `>` either.\n const tagStart = svg.indexOf('<svg');\n const tagEnd = tagStart >= 0 ? svg.indexOf('>', tagStart) : -1;\n const openTag = tagEnd >= 0 ? svg.slice(tagStart, tagEnd + 1) : '';\n if (!/\\swidth\\s*=/.test(openTag) || !/\\sheight\\s*=/.test(openTag)) {\n throw new Error(\n '[web] svgCursor: the <svg> needs explicit width/height attributes — ' +\n 'browsers ignore cursor images without an intrinsic size',\n );\n }\n const uri = `data:image/svg+xml,${encodeURIComponent(svg)}`;\n const spot = hotspot ? ` ${Math.round(hotspot.x)} ${Math.round(hotspot.y)}` : '';\n return `url(\"${uri}\")${spot}, ${fallback}`;\n}\n","/**\n * External-URI hygiene for PDF link annotations — written ONCE here so every\n * framework adapter (react, vue, svelte, angular) shares the same policy.\n *\n * PDFs are untrusted input: a link's `/URI` can carry anything, including\n * `javascript:` payloads. The default allowlist covers what a document link\n * legitimately opens; everything else (javascript:, file:, data:, vbscript:,\n * custom app schemes) resolves to `null` and the adapters render the link\n * WITHOUT an href — activation still reports through the plugin, so an\n * embedder that needs a custom scheme can handle it in `onActivate`.\n */\nconst DEFAULT_ALLOWED_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:']);\n\n/**\n * Validate a link URI for opening in the browser. Returns the URI when its\n * scheme is allowed (scheme-relative and bare URIs resolve as https), else\n * `null`. Never throws — a malformed URI is just an unopenable one.\n */\nexport function sanitizeExternalUri(\n uri: string,\n allowedSchemes: Iterable<string> = DEFAULT_ALLOWED_SCHEMES,\n): string | null {\n const trimmed = uri.trim();\n if (!trimmed) return null;\n const allowed = new Set(\n [...allowedSchemes].map((s) => (s.endsWith(':') ? s.toLowerCase() : `${s.toLowerCase()}:`)),\n );\n try {\n // Base resolves scheme-relative (\"//host\") and bare (\"example.com/x\" is a\n // relative path → https) forms; an absolute scheme wins over the base.\n const url = new URL(trimmed, 'https://invalid.example/');\n return allowed.has(url.protocol) ? trimmed : null;\n } catch {\n return null;\n }\n}\n","/**\n * The DEFAULT actions UI adapter — the origin×phase visibility matrix plus\n * the browser fallbacks, written ONCE here so every framework binding\n * (react, angular, …) ships the SAME policy instead of forking it per\n * framework. Types are structural twins of `@embedpdf/plugin-actions`'\n * adapter contract (declared here so this package stays plugin-free, per\n * the layering law); assignability is checked where a binding installs it.\n *\n * The matrix (defaults only — an embedder override receives EVERY effect\n * with its context attached and decides for itself):\n * - URIs open through `sanitizeExternalUri` in a new tab (the dispatcher\n * already policy-gated the node; blocked schemes are dropped here).\n * - Print: hover/lifecycle-origin requests never open the dialog; user\n * requests fall back to `globalThis.print`. The `doc.print` AUTHORITY\n * gate is upstream in the plugin and not overridable.\n * - Alert: document-open nags (lifecycle origin, boot phase) never reach\n * `window.alert` — the boot-nag gap, closed.\n * - gotoPage: delegates to the binding-supplied navigator (the stage).\n */\nimport { sanitizeExternalUri } from './external-uri';\n\n/** Structural twin of plugin-actions' `ActionOrigin`. */\nexport type ActionsUiOrigin = 'user' | 'hover' | 'lifecycle';\n\n/** Structural twin of plugin-actions' `ActionUiContext`. */\nexport interface ActionsUiEffectContext {\n origin: ActionsUiOrigin;\n phase: 'boot' | 'user';\n}\n\n/** Structural twin of plugin-actions' `ActionUiAdapter`. */\nexport interface ActionsUiAdapterShape {\n openUri(uri: string, opts: { isMap: boolean; origin: ActionsUiOrigin }): void;\n print(opts?: ActionsUiEffectContext): void;\n alert?(message: string, opts: ActionsUiEffectContext & { icon: number; title?: string }): void;\n gotoPage?(page: number, opts: ActionsUiEffectContext): void;\n}\n\nexport interface DefaultActionsUiAdapterOptions {\n /**\n * Per-handler overrides. Pass a FUNCTION for late binding (a binding's\n * mutable handlers ref) — it is consulted on every effect, so a fresh\n * closure never needs a reinstall.\n */\n overrides?: Partial<ActionsUiAdapterShape> | (() => Partial<ActionsUiAdapterShape> | undefined);\n /** Navigation default for script `this.pageNum = n` requests. */\n goToPage?: (page: number) => void;\n}\n\nexport const createDefaultActionsUiAdapter = (\n options: DefaultActionsUiAdapterOptions = {},\n): ActionsUiAdapterShape => {\n const overridesOf = (): Partial<ActionsUiAdapterShape> | undefined =>\n typeof options.overrides === 'function' ? options.overrides() : options.overrides;\n return {\n openUri: (uri, opts) => {\n const current = overridesOf();\n if (current?.openUri) {\n current.openUri(uri, opts);\n return;\n }\n const href = sanitizeExternalUri(uri);\n if (href && typeof window !== 'undefined') {\n window.open(href, '_blank', 'noopener,noreferrer');\n }\n },\n print: (opts) => {\n const current = overridesOf();\n if (current?.print) current.print(opts);\n else if (opts && opts.origin !== 'user') {\n // Hover/lifecycle scripts never open the print dialog by default.\n } else if (typeof globalThis.print === 'function') globalThis.print();\n },\n alert: (message, opts) => {\n const current = overridesOf();\n if (current?.alert) current.alert(message, opts);\n else if (opts.origin === 'lifecycle' || opts.phase === 'boot') {\n // Document-open nags (Adobe version checks, lifecycle scripts)\n // never alert by default — the boot-nag gap, closed.\n } else if (typeof globalThis.alert === 'function') globalThis.alert(message);\n },\n gotoPage: (page, opts) => {\n const current = overridesOf();\n if (current?.gotoPage) current.gotoPage(page, opts);\n else options.goToPage?.(page);\n },\n };\n};\n","/** A browser image source whose object URL has an explicit lifetime. */\nexport interface ObjectUrlImageSource {\n objectUrl(signal?: AbortSignal): Promise<{ url: string; revoke(): void }>;\n}\n\nexport interface PaintedImageCallbacks {\n onPainted(): void;\n onUnpainted(): void;\n}\n\n/**\n * Bind a raster source to an <img> without ever painting its incomplete-image\n * placeholder. The source becomes visible on load; retained coverage is\n * released only after the browser has had one full frame to present it.\n *\n * Call this from the framework's pre-paint mount hook and invoke the returned\n * cleanup on unmount. Keeping this DOM lifecycle here lets every framework\n * adapter remain a ref plus one bind/unbind hook.\n */\nexport function bindPaintedImage(\n image: HTMLImageElement,\n source: ObjectUrlImageSource,\n callbacks: PaintedImageCallbacks,\n): () => void {\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n let frame = 0;\n let painted = false;\n\n // The element is already committed when a mount hook runs. Hide it before\n // that commit can paint, then reveal only once the bitmap has loaded.\n image.style.visibility = 'hidden';\n\n const onLoad = () => {\n image.style.visibility = 'visible';\n\n // rAF callbacks run before a frame paints. The first frame presents the\n // newly visible image while retained coverage remains; the second reports\n // that presentation opportunity and permits the retained source to leave.\n frame = requestAnimationFrame(() => {\n frame = requestAnimationFrame(() => {\n frame = 0;\n painted = true;\n callbacks.onPainted();\n });\n });\n };\n\n image.addEventListener('load', onLoad, { once: true });\n\n void source.objectUrl(controller.signal).then(\n (objectUrl) => {\n if (controller.signal.aborted) {\n objectUrl.revoke();\n return;\n }\n revoke = objectUrl.revoke;\n image.src = objectUrl.url;\n },\n () => {\n // Aborted or unavailable: remain hidden and retain fallback coverage.\n },\n );\n\n return () => {\n controller.abort();\n if (frame) cancelAnimationFrame(frame);\n image.removeEventListener('load', onLoad);\n image.style.visibility = 'hidden';\n image.removeAttribute('src');\n revoke?.();\n if (painted) callbacks.onUnpainted();\n };\n}\n","/**\n * Platform-feedback providers — the browser implementations of the interaction\n * hub's haptic port (structurally `PlatformFeedback` from\n * `@embedpdf/plugin-interaction`; declared structurally here so this package\n * stays plugin-free, per the layering law).\n *\n * Honesty about reach: `navigator.vibrate` works on Android browsers and is\n * NOT implemented in iOS Safari — there the default provider is a silent\n * no-op until Apple ships a haptics API. (The community switch-control hacks\n * were evaluated and deliberately NOT shipped: Apple patched programmatic\n * triggering in iOS 26.5, leaving only tap-on-control tricks that don't fit a\n * viewer's gesture moments.) The real iPhone haptic today comes through a\n * native shell: a WKWebView embedder installs a script message handler and\n * passes {@link wkFeedback} (or their own provider) instead.\n */\n\n/** Structural twin of plugin-interaction's `PlatformFeedback`. */\nexport interface WebPlatformFeedback {\n selection(): void;\n impact(weight?: 'light' | 'medium' | 'heavy'): void;\n notify(kind: 'success' | 'warning' | 'error'): void;\n}\n\nconst vibrate = (pattern: number | number[]): void => {\n if (typeof navigator !== 'undefined') navigator.vibrate?.(pattern);\n};\n\n/**\n * The default web provider: the Vibration API, mapped onto the three haptic\n * families (selection = the shortest tick; impact scales with weight; notify\n * uses patterns). A no-op wherever vibration is unsupported — notably iOS\n * Safari — so it is always safe to install.\n */\nexport const vibrationFeedback: WebPlatformFeedback = {\n selection: () => vibrate(8),\n impact: (weight = 'light') => vibrate(weight === 'heavy' ? 30 : weight === 'medium' ? 20 : 10),\n notify: (kind) =>\n vibrate(kind === 'error' ? [12, 60, 12, 60, 12] : kind === 'warning' ? [15, 70, 15] : [10, 50, 10]),\n};\n\ninterface WKMessageHandler {\n postMessage(message: unknown): void;\n}\n\n/**\n * A provider for native iOS shells: forwards each call to a WKWebView script\n * message handler the HOST APP installed under `handlerName` — an explicit\n * contract between the embedder and their own app, never a global this library\n * sniffs on its own. The message shape is\n * `{ family: 'selection' } | { family: 'impact', weight } | { family: 'notify', kind }`;\n * the native side maps families onto the matching `UIFeedbackGenerator`.\n * Calls fall back to `fallback` (default: {@link vibrationFeedback}) when the\n * handler is absent, so one provider serves a hybrid web/app deployment.\n */\nexport const wkFeedback = (\n handlerName: string,\n fallback: WebPlatformFeedback = vibrationFeedback,\n): WebPlatformFeedback => {\n const handler = (): WKMessageHandler | undefined => {\n if (typeof window === 'undefined') return undefined;\n const w = window as Window & {\n webkit?: { messageHandlers?: Record<string, WKMessageHandler | undefined> };\n };\n return w.webkit?.messageHandlers?.[handlerName];\n };\n return {\n selection: () => {\n const h = handler();\n if (h) h.postMessage({ family: 'selection' });\n else fallback.selection();\n },\n impact: (weight = 'light') => {\n const h = handler();\n if (h) h.postMessage({ family: 'impact', weight });\n else fallback.impact(weight);\n },\n notify: (kind) => {\n const h = handler();\n if (h) h.postMessage({ family: 'notify', kind });\n else fallback.notify(kind);\n },\n };\n};\n","/**\n * Wheel-input classification for ambient zoom — pure, so every framework\n * adapter shares one feel and the mapping is unit-testable.\n *\n * A ctrl/cmd wheel event is one of three physically different inputs that\n * happen to share an event type, each with its own unit system:\n *\n * • trackpad PINCH — browsers (Chrome/Edge/Firefox) synthesize ctrl+wheel\n * px-deltas scaled to the de-facto convention that `exp(-Δ/100)` tracks\n * the physical finger scale 1:1. Anything weaker feels dead (the content\n * must track the fingers — the Figma/maps feel).\n * • mouse NOTCHES — coarse steps: ±100/±120 px in Chrome, lines/pages mode\n * in Firefox. The pinch mapping would triple per click; a notch instead\n * steps by 1.2, the same ratio as the zoom buttons.\n * • cmd + continuous SCROLL — real scroll px, big and streaming (with\n * momentum): a gentle scrub coefficient. A cmd+mouse notch (±120 px)\n * lands within 0.3% of the 1.2 button step, so it needs no special case.\n *\n * (Safari never synthesizes ctrl+wheel for pinch — it fires proprietary\n * gesture events with an absolute scale; that wiring lives in the adapters.)\n */\nexport interface WheelSample {\n deltaY: number;\n /** WheelEvent.deltaMode: 0 = pixels, 1 = lines, 2 = pages. */\n deltaMode: number;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\n/** The per-notch step for discrete wheels — the zoom buttons' ratio. */\nconst NOTCH = 1.2;\n\n/** Zoom factor for a ctrl/cmd wheel event (the caller decides zoom vs pan). */\nexport function wheelZoomFactor(e: WheelSample): number {\n // Lines/pages mode (Firefox mouse wheels): one button-step per notch.\n if (e.deltaMode !== 0) return Math.pow(NOTCH, -Math.sign(e.deltaY));\n if (e.ctrlKey) {\n // px deltas with ctrl: a real mouse notch arrives big (±100/±120) and\n // steps like the buttons; anything smaller is a synthesized pinch.\n if (Math.abs(e.deltaY) >= 100) return Math.pow(NOTCH, -Math.sign(e.deltaY));\n return Math.exp(-e.deltaY / 100);\n }\n // cmd + continuous scroll: the gentle scrub.\n return Math.exp(-e.deltaY * 0.0015);\n}\n","/**\n * Stage gesture controller — the ONE DOM input binding for a Stage surface,\n * shared by every framework adapter (React, Angular, …) so the feel can never\n * drift between them.\n *\n * The premise (see the stage plugin's camera doctrine): desktop inputs arrive\n * with physics already applied by the OS — momentum lives in the wheel stream,\n * a trackpad pinch is one pre-arbitrated gesture stream. Touch arrives RAW:\n * with `touch-action: none` the platform scroller is out of the loop, so the\n * ballistics (velocity → fling) and the arbitration (is this contact a scroll,\n * a pinch, a tap, or a tool gesture?) must be synthesized here.\n *\n * Arbitration is MODALITY-AWARE:\n * - touch — navigation-first: one finger pans (whatever tool is armed), two\n * fingers pinch-zoom around their centroid, release velocity flings, a tap\n * forwards as a click, a double-tap zoom-toggles, a long-press hands the\n * gesture to the interaction hub (text selection), and a second finger\n * landing mid-tool-gesture CANCELS it into a pinch (the Notes/Procreate\n * convention).\n * - mouse/pen — tool-first, exactly the pre-existing behavior: with a hub,\n * every down/move/up forwards (pan is the pan tool's job); without one,\n * dragging pans. Wheel and Safari-trackpad gesture events are unchanged.\n *\n * Camera writes are rAF-COALESCED: pointer events only update gesture state;\n * one animation-frame tick applies at most one pan and one zoom per frame,\n * inside the host's begin/endGesture transaction. Events may arrive at 120 Hz;\n * the camera moves at display rate.\n *\n * Dependency note: this module speaks to the stage through the STRUCTURAL\n * {@link StageGestureHost} interface (satisfied by `StageCapability`) and to\n * the interaction hub through {@link StageGestureSink} (a closure the adapter\n * builds) — @embedpdf/web stays free of plugin imports, per the layering law.\n */\n\nimport { wheelZoomFactor } from './wheel';\n\nexport type StagePointerKind = 'mouse' | 'pen' | 'touch';\n\n/** What the controller needs from the camera — `StageCapability` satisfies it. */\nexport interface StageGestureHost {\n panBy(dxScreen: number, dyScreen: number): void;\n zoomAround(screenPt: { x: number; y: number }, factor: number): void;\n beginGesture(options?: { elastic?: boolean }): void;\n endGesture(): void;\n /** Momentum pan from a release velocity in screen px/s. */\n fling(velocityX: number, velocityY: number): void;\n /** True while a tween/fling runs — a touch-down then is a \"catch\", not a tap. */\n cameraInMotion(): boolean;\n doubleTapZoom(screenPt: { x: number; y: number }): void;\n}\n\n/**\n * Where non-navigation gestures go — the adapter's bridge to the interaction\n * hub. Every callback receives the ORIGINAL PointerEvent so the adapter can\n * resolve pages/points exactly as it always has. Omit the sink entirely for a\n * hub-less (built-in pan) stage.\n */\nexport interface StageGestureSink {\n down(e: PointerEvent, clickCount: number): void;\n move(e: PointerEvent): void;\n up(e: PointerEvent): void;\n /** The gesture was taken over by navigation (second finger → pinch) or\n * cancelled by the system — abort, don't commit. */\n cancel(e: PointerEvent): void;\n /** Pointer travel with no gesture in flight — cursor feedback. */\n hover(e: PointerEvent): void;\n /** A touch press held still: hand the gesture to the hub (the adapter\n * typically forwards it as a word-select down). Subsequent move/up arrive\n * via {@link move}/{@link up}. */\n longPress(e: PointerEvent): void;\n /**\n * Touch-consent pre-flight, asked at touch-down BEFORE the contact is\n * classified: does a tool have standing to own this contact? True when the\n * armed tool takes fingers wholesale (a drawing tool) or something under\n * the point claims its drags (a selected annotation's body or handles).\n * True routes the whole contact to {@link down}/{@link move}/{@link up} —\n * where a second finger still cancels it into a pinch. Must be a pure\n * read. Absent = never; the contact navigates.\n */\n claimsPoint?(e: PointerEvent): boolean;\n}\n\n/** The wheel fields the zoom classifier reads (see `./wheel`'s `WheelSample`). */\nexport interface StageWheelSample {\n deltaY: number;\n deltaMode: number;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\nexport interface StageGestureOptions {\n /** Ambient zoom (wheel-zoom, pinch-zoom, double-tap zoom). Off: zoom wheels\n * fall through to pan and pinches pan without zooming — but are still\n * swallowed, never page-zooming the browser. Default true. */\n zoomGestures?: boolean;\n /** The wheel → zoom-factor classifier. Defaults to this package's\n * `wheelZoomFactor` (browser wheel classification lives HERE, with the rest\n * of the browser input handling); inject to override or to fake in tests. */\n wheelZoomFactor?: (sample: StageWheelSample) => number;\n /** Tool routing for non-navigation gestures; omit for built-in-pan stages. */\n sink?: StageGestureSink | null;\n /** Touch press duration that becomes a long-press (ms). Default 450. */\n longPressMs?: number;\n /** Finger travel below which a touch stays a tap/press (px). Default 10. */\n tapSlopPx?: number;\n /** Max gap between taps for a double-tap (ms). Default 300. */\n doubleTapMs?: number;\n /** Release speed below which no fling starts (px/s). Default 50. */\n flingMinVelocity?: number;\n}\n\n/**\n * Release velocity from a trail of pointer samples: the mean velocity over the\n * trailing `windowMs` (first-to-last inside the window). Null when the trail is\n * too thin or too stale to trust — the standard \"held still, then let go\"\n * case, which must NOT fling. Pure; exported for tests.\n */\nexport function computeReleaseVelocity(\n samples: ReadonlyArray<{ t: number; x: number; y: number }>,\n now: number,\n windowMs = 100,\n): { vx: number; vy: number } | null {\n let firstIdx = -1;\n for (let i = 0; i < samples.length; i++) {\n if (now - samples[i].t <= windowMs) {\n firstIdx = i;\n break;\n }\n }\n if (firstIdx < 0 || firstIdx === samples.length - 1) return null;\n const first = samples[firstIdx];\n const last = samples[samples.length - 1];\n const dt = last.t - first.t;\n if (dt < 10) return null;\n return { vx: ((last.x - first.x) / dt) * 1000, vy: ((last.y - first.y) / dt) * 1000 };\n}\n\ninterface Tracked {\n id: number;\n kind: StagePointerKind;\n x: number;\n y: number;\n downX: number;\n downY: number;\n}\n\ntype Mode = 'idle' | 'pending' | 'pan' | 'pinch' | 'tool' | 'mousedrag';\n\n/** Attach the gesture controller to a Stage container. Returns the detach fn. */\nexport function createStageGestureController(\n el: HTMLElement,\n host: StageGestureHost,\n options: StageGestureOptions,\n): () => void {\n const zoomGestures = options.zoomGestures ?? true;\n const wheelZoom = options.wheelZoomFactor ?? wheelZoomFactor;\n const sink = options.sink ?? null;\n const LONG_PRESS_MS = options.longPressMs ?? 450;\n const SLOP = options.tapSlopPx ?? 10;\n const DOUBLE_TAP_MS = options.doubleTapMs ?? 300;\n const DOUBLE_TAP_RADIUS = 25;\n const FLING_MIN = options.flingMinVelocity ?? 50;\n const MIN_PINCH_SPAN = 20; // px — below this a span ratio is mostly noise\n const PINCH_RELEASE_GRACE_MS = 160; // leftover finger must OUTLIVE the release\n const SETTLE_SPEED = 0.12; // px/ms — below this the leftover finger has SETTLED\n\n const pointers = new Map<number, Tracked>();\n let mode: Mode = 'idle';\n let began = false; // a host gesture transaction is open\n let suppressTap = false; // this contact CAUGHT a moving camera — never a tap\n let touchToolGesture = false; // 'tool' mode entered via touch long-press\n let downEvent: PointerEvent | null = null; // first touch's down, for tap/long-press forwarding\n let longPressTimer: ReturnType<typeof setTimeout> | null = null;\n let panId = -1;\n // A pan is ARMED once its finger has crossed slop. Fresh touches arm on the\n // pending→pan transition; the finger LEFT OVER when a pinch ends starts\n // DISARMED — a gesture's identity persists through its release, so the\n // leftover contact must re-earn pan-hood exactly like a new finger would.\n // While disarmed, its micro-rolls neither pan nor pollute the velocity\n // trail (which still holds the pinch's CENTROID motion — the momentum a\n // compound gesture actually has). For a GRACE window after the transition\n // the slop base FOLLOWS the finger: in a fast release the leftover finger\n // is still genuinely moving, and distance alone cannot tell that from a\n // deliberate continuation — only outliving the release window can.\n let panArmed = true;\n let panGraceUntil = 0;\n let graceSample = { t: 0, x: 0, y: 0 }; // leftover finger's last observed sample\n // iOS-visible seam: real WebKit can deliver a trailing gesturechange after\n // the last pointerup of a pinch — with no touches left, the desktop-trackpad\n // path would apply a stray end-of-pinch zoom. Any RECENT touch activity\n // therefore suppresses gesture events; desktop trackpads never have any.\n let lastTouchAt = -Infinity;\n\n // rAF application state\n let frame = 0;\n let dirty = false;\n let lastApplied = { x: 0, y: 0 }; // pan focal point, client px\n let lastSpan = 0;\n\n // velocity trail of the pan focal point (finger, or pinch centroid)\n let trail: Array<{ t: number; x: number; y: number }> = [];\n // the pinch's SPAN trail, sampled beside the centroid: at release it decides\n // the gesture's CHARACTER. A fast pinch release has asymmetric finger\n // speeds (the lifting finger flicks away), which moves the TRUE centroid at\n // hundreds of px/s — physically real, but zoom-release noise the platform\n // ignores. Fling only when the centroid rate DOMINATES the span rate\n // (a two-finger pan); a zoom-dominant release ends still.\n let spanTrail: Array<{ t: number; x: number; y: number }> = [];\n\n // tap-pair state for double-tap\n let lastTapT = 0;\n let lastTapX = 0;\n let lastTapY = 0;\n\n // multi-click counter for mouse/pen tool downs (parity with the adapters'\n // previous createClickCounter: 400 ms / 6 px)\n let mLast = 0;\n let mX = 0;\n let mY = 0;\n let mCount = 0;\n const clickCount = (e: PointerEvent): number => {\n const now = Date.now();\n mCount =\n now - mLast <= 400 && Math.hypot(e.clientX - mX, e.clientY - mY) <= 6 ? mCount + 1 : 1;\n mLast = now;\n mX = e.clientX;\n mY = e.clientY;\n return mCount;\n };\n\n const vpt = (clientX: number, clientY: number) => {\n const r = el.getBoundingClientRect();\n return { x: clientX - r.left, y: clientY - r.top };\n };\n const touches = (): Tracked[] => {\n const out: Tracked[] = [];\n pointers.forEach((p) => {\n if (p.kind === 'touch') out.push(p);\n });\n return out;\n };\n\n const clearLongPress = () => {\n if (longPressTimer !== null) {\n clearTimeout(longPressTimer);\n longPressTimer = null;\n }\n };\n const stopFrames = () => {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n };\n const begin = (elastic = false) => {\n if (!began) {\n began = true;\n // touch contacts are elastic (rubber-band past the clamp); mouse drags\n // stay rigid — the desktop convention\n host.beginGesture(elastic ? { elastic: true } : undefined);\n }\n };\n const end = () => {\n if (began) {\n began = false;\n host.endGesture();\n }\n };\n const toIdle = () => {\n mode = 'idle';\n downEvent = null;\n touchToolGesture = false;\n trail = [];\n spanTrail = [];\n dirty = false;\n panId = -1;\n panArmed = true;\n panGraceUntil = 0;\n clearLongPress();\n stopFrames();\n };\n const pushSample = (x: number, y: number) => {\n trail.push({ t: performance.now(), x, y });\n if (trail.length > 16) trail.shift();\n };\n const maybeFling = () => {\n const v = computeReleaseVelocity(trail, performance.now());\n if (v && Math.hypot(v.vx, v.vy) >= FLING_MIN) host.fling(v.vx, v.vy);\n };\n\n // ── the application step, shared by the frame loop AND the release paths —\n // a release must flush whatever the last tick hadn't applied yet, or a fast\n // flick loses its final sub-frame of travel and a pinch its last span step.\n const flushPanTo = (x: number, y: number) => {\n const dx = x - lastApplied.x;\n const dy = y - lastApplied.y;\n if (dx !== 0 || dy !== 0) host.panBy(dx, dy);\n lastApplied = { x, y };\n dirty = false;\n };\n const flushPinch = (ax: number, ay: number, bx: number, by: number) => {\n const cx = (ax + bx) / 2;\n const cy = (ay + by) / 2;\n const span = Math.hypot(ax - bx, ay - by);\n const dx = cx - lastApplied.x;\n const dy = cy - lastApplied.y;\n if (dx !== 0 || dy !== 0) host.panBy(dx, dy);\n if (zoomGestures && span > MIN_PINCH_SPAN && lastSpan > MIN_PINCH_SPAN && span !== lastSpan) {\n host.zoomAround(vpt(cx, cy), span / lastSpan);\n }\n // The centroid + span trails sample HERE — once per applied frame, where\n // both fingers are read coherently. Per-event sampling zig-zags (fingers\n // report sequentially, so each single-finger move fakes a half-step of\n // centroid motion) and manufactures phantom release velocity. The span\n // trail is the release gate's evidence of the gesture's CHARACTER.\n pushSample(cx, cy);\n spanTrail.push({ t: performance.now(), x: span, y: 0 });\n if (spanTrail.length > 16) spanTrail.shift();\n lastApplied = { x: cx, y: cy };\n lastSpan = span;\n dirty = false;\n };\n\n // ── the per-frame application (one camera write per frame) ────────────────\n const tick = () => {\n frame = 0;\n if (mode === 'pan' || mode === 'mousedrag') {\n const p = pointers.get(panId);\n if (p && dirty) flushPanTo(p.x, p.y);\n frame = requestAnimationFrame(tick);\n } else if (mode === 'pinch') {\n const [a, b] = touches();\n if (a && b && dirty) flushPinch(a.x, a.y, b.x, b.y);\n frame = requestAnimationFrame(tick);\n }\n };\n const ensureFrames = () => {\n if (!frame) frame = requestAnimationFrame(tick);\n };\n\n const enterPinch = () => {\n const [a, b] = touches();\n if (!a || !b) return;\n mode = 'pinch';\n lastApplied = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n lastSpan = Math.hypot(a.x - b.x, a.y - b.y);\n trail = [];\n dirty = false;\n ensureFrames();\n };\n\n const onLongPress = () => {\n longPressTimer = null;\n if (mode !== 'pending' || !downEvent || !sink) return;\n // The camera transaction closes; the tool owns the rest of this contact.\n end();\n mode = 'tool';\n touchToolGesture = true;\n sink.longPress(downEvent);\n };\n\n // ── listeners ─────────────────────────────────────────────────────────────\n const onDown = (e: PointerEvent) => {\n const kind = (e.pointerType || 'mouse') as StagePointerKind;\n if (kind === 'touch') lastTouchAt = performance.now();\n if (kind !== 'touch') {\n if (kind === 'mouse' && e.button !== 0) return;\n if (mode !== 'idle') return; // an active gesture owns the surface\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n if (sink) {\n // tool-first, exactly the pre-existing hub behavior\n mode = 'tool';\n sink.down(e, clickCount(e));\n } else {\n mode = 'mousedrag';\n begin();\n panId = e.pointerId;\n lastApplied = { x: e.clientX, y: e.clientY };\n ensureFrames();\n }\n return;\n }\n\n // touch\n switch (mode) {\n case 'idle': {\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n // Consent pre-flight — but a MOVING camera always catches first: while\n // content flies under the finger, the touch means \"stop\", never \"grab\n // whatever happens to pass beneath it\".\n const moving = host.cameraInMotion();\n if (!moving && sink?.claimsPoint?.(e)) {\n // A tool owns this contact from the first pixel (selected-annotation\n // drag, or an armed drawing tool). No camera transaction — this is\n // not a navigation gesture; a second finger converts it to a pinch\n // via the 'tool' branch (sink.cancel), exactly like a long-press.\n mode = 'tool';\n touchToolGesture = true;\n sink.down(e, 1);\n break;\n }\n mode = 'pending';\n downEvent = e;\n suppressTap = moving; // a catch, not a tap\n begin(true); // stops any fling/tween under the finger\n trail = [];\n pushSample(e.clientX, e.clientY);\n clearLongPress();\n if (sink && !suppressTap) longPressTimer = setTimeout(onLongPress, LONG_PRESS_MS);\n break;\n }\n case 'pending':\n case 'pan': {\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n clearLongPress();\n enterPinch();\n break;\n }\n case 'tool': {\n // A second finger during a TOUCH tool gesture cancels it into a pinch\n // (the platform convention). Mouse tool gestures ignore stray touches.\n if (!touchToolGesture) return;\n sink?.cancel(e);\n touchToolGesture = false;\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n begin(true);\n enterPinch();\n break;\n }\n case 'pinch':\n case 'mousedrag':\n return; // ignore extra contacts\n }\n };\n\n const onWindowMove = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n if (p.kind === 'touch') lastTouchAt = performance.now();\n p.x = e.clientX;\n p.y = e.clientY;\n switch (mode) {\n case 'pending': {\n pushSample(p.x, p.y);\n if (Math.hypot(p.x - p.downX, p.y - p.downY) > SLOP) {\n clearLongPress();\n mode = 'pan';\n panId = p.id;\n lastApplied = { x: p.x, y: p.y }; // absorb the slop, like a scroller\n ensureFrames();\n }\n break;\n }\n case 'pan':\n if (p.id !== panId) break;\n if (!panArmed) {\n // pinch-leftover contact. Inside the release-grace window the slop\n // base follows the finger — a fast release keeps moving and must\n // never accumulate distance; only a contact that OUTLIVES the\n // window can re-earn panning by crossing slop from where it\n // settled.\n const nowT = performance.now();\n if (nowT < panGraceUntil) {\n // a finger that SETTLES (speed drops) inside the window is a\n // continuation taking hold — end the grace early so a deliberate\n // pause-then-drag stays responsive\n const dt = nowT - graceSample.t;\n if (dt >= 8) {\n const speed = Math.hypot(p.x - graceSample.x, p.y - graceSample.y) / dt;\n graceSample = { t: nowT, x: p.x, y: p.y };\n if (speed < SETTLE_SPEED) panGraceUntil = 0;\n }\n p.downX = p.x;\n p.downY = p.y;\n break;\n }\n if (Math.hypot(p.x - p.downX, p.y - p.downY) <= SLOP) break;\n panArmed = true;\n lastApplied = { x: p.x, y: p.y }; // absorb, like any fresh pan\n trail = [];\n spanTrail = [];\n }\n pushSample(p.x, p.y);\n dirty = true;\n break;\n case 'pinch':\n // centroid samples are taken per applied FRAME (see flushPinch) —\n // per-event sampling here would zig-zag between the two fingers\n dirty = true;\n break;\n case 'tool':\n sink?.move(e);\n break;\n case 'mousedrag':\n if (p.id === panId) dirty = true;\n break;\n case 'idle':\n break;\n }\n };\n\n // Hover (cursor feedback) — only with no gesture in flight, and only from\n // the element itself, matching the previous adapters.\n const onHoverMove = (e: PointerEvent) => {\n if (mode === 'idle' && sink) sink.hover(e);\n };\n\n const backToSingleFinger = (): boolean => {\n const rest = touches();\n if (rest.length !== 1) return false;\n mode = 'pan';\n panId = rest[0].id;\n panArmed = false; // leftover contact re-earns pan-hood via slop\n panGraceUntil = performance.now() + PINCH_RELEASE_GRACE_MS;\n graceSample = { t: performance.now(), x: rest[0].x, y: rest[0].y };\n rest[0].downX = rest[0].x;\n rest[0].downY = rest[0].y;\n lastApplied = { x: rest[0].x, y: rest[0].y };\n trail = [];\n dirty = false;\n return true;\n };\n\n const onUp = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n if (p.kind === 'touch') lastTouchAt = performance.now();\n pointers.delete(e.pointerId);\n switch (mode) {\n case 'pending': {\n // Slop never exceeded, timer never fired: a tap (or a catch).\n clearLongPress();\n end();\n const now = performance.now();\n const isDouble =\n now - lastTapT <= DOUBLE_TAP_MS &&\n Math.hypot(e.clientX - lastTapX, e.clientY - lastTapY) <= DOUBLE_TAP_RADIUS;\n if (suppressTap) {\n lastTapT = 0; // a catch never counts toward a double-tap\n } else if (zoomGestures && isDouble) {\n lastTapT = 0;\n host.doubleTapZoom(vpt(e.clientX, e.clientY));\n } else {\n lastTapT = now;\n lastTapX = e.clientX;\n lastTapY = e.clientY;\n if (sink && downEvent) {\n // The whole click, delivered at release — tools see exactly the\n // down/up pair they would from a mouse.\n sink.down(downEvent, 1);\n sink.up(e);\n }\n }\n toIdle();\n break;\n }\n case 'pan': {\n if (!panArmed) {\n // The pinch's OTHER finger leaving: the gesture ends as a pinch.\n // No flush (release-rolls are noise). Glide is CHARACTER-gated: a\n // human fast release moves the true centroid (the lifting finger\n // flicks away), so centroid velocity alone lies — the glide fires\n // only when the centroid rate DOMINATES the span rate (a\n // two-finger pan), never on a zoom-dominant release.\n const now = performance.now();\n const v = computeReleaseVelocity(trail, now);\n const vs = computeReleaseVelocity(spanTrail, now);\n end(); // close the bracket first — endGesture owns the elastic settle\n if (v) {\n const speed = Math.hypot(v.vx, v.vy);\n if (speed >= FLING_MIN && speed > Math.abs(vs?.vx ?? 0)) host.fling(v.vx, v.vy);\n }\n toIdle();\n break;\n }\n flushPanTo(e.clientX, e.clientY); // apply the final sub-frame of travel\n pushSample(e.clientX, e.clientY);\n end();\n maybeFling();\n toIdle();\n break;\n }\n case 'pinch': {\n const rest = touches();\n if (rest.length === 1) {\n // The pinch ends AT ITS LAST COHERENT FRAME. No flush here: the\n // lifting finger's release position is fresh but the other one's is\n // stale (its move for this window may not have arrived), and a\n // centroid of two instants is fiction — in a fast pinch that skewed\n // write was the visible end-of-pinch hop, and its poisoned trail\n // sample the phantom fling. Only coherent finger-pairs write the\n // camera; the sub-frame remainder is discarded, as the platform\n // recognizers do. The remaining contact gets a DISARMED pan with\n // the centroid trail preserved and a release-grace window armed.\n mode = 'pan';\n panId = rest[0].id;\n panArmed = false;\n panGraceUntil = performance.now() + PINCH_RELEASE_GRACE_MS;\n graceSample = { t: performance.now(), x: rest[0].x, y: rest[0].y };\n rest[0].downX = rest[0].x;\n rest[0].downY = rest[0].y;\n lastApplied = { x: rest[0].x, y: rest[0].y };\n dirty = false;\n break;\n }\n end();\n maybeFling();\n toIdle();\n break;\n }\n case 'tool': {\n sink?.up(e);\n toIdle();\n break;\n }\n case 'mousedrag': {\n if (p.id !== panId) break;\n flushPanTo(e.clientX, e.clientY);\n end();\n toIdle();\n break;\n }\n case 'idle':\n break;\n }\n };\n\n const onCancel = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n pointers.delete(e.pointerId);\n if (mode === 'tool') {\n sink?.cancel(e);\n toIdle();\n return;\n }\n if (mode === 'pinch' && backToSingleFinger()) return;\n end();\n toIdle();\n };\n\n // Wheel is ambient navigation in BOTH modes: ctrl/meta zooms (classified per\n // input by the injected wheelZoomFactor), else scrolls. With zoom gestures\n // off, a zoom-wheel falls through to ordinary pan.\n const onWheel = (e: WheelEvent) => {\n e.preventDefault();\n if (zoomGestures && (e.ctrlKey || e.metaKey)) {\n host.zoomAround(vpt(e.clientX, e.clientY), wheelZoom(e));\n } else {\n const dx = e.shiftKey ? e.deltaY : e.deltaX;\n const dy = e.shiftKey ? e.deltaX : e.deltaY;\n host.panBy(-dx, -dy);\n }\n };\n\n // Safari's proprietary gesture events. On DESKTOP Safari they are the only\n // trace of a trackpad pinch — convert the absolute scale to per-event ratios.\n // On iOS they fire ALONGSIDE per-finger pointer events; there the pointer\n // path owns the pinch and these are preventDefault-ed only (a pinch over the\n // stage must never page-zoom Safari). The guard is live touch contacts.\n let lastScale = 1;\n const onGestureStart = (e: Event) => {\n e.preventDefault();\n lastScale = (e as unknown as { scale?: number }).scale ?? 1;\n };\n const onGestureChange = (e: Event) => {\n e.preventDefault();\n // iOS: the pointer path owns touch pinches — and a TRAILING gesturechange\n // can arrive after the last pointerup, so suppression keys on RECENT\n // touch activity, not just live contacts. Desktop trackpads have none.\n if (touches().length > 0 || performance.now() - lastTouchAt < 500) return;\n const g = e as unknown as { scale?: number; clientX: number; clientY: number };\n const scale = g.scale ?? 1;\n if (zoomGestures && scale > 0) {\n host.zoomAround(vpt(g.clientX, g.clientY), scale / lastScale);\n }\n lastScale = scale;\n };\n\n el.addEventListener('pointerdown', onDown);\n el.addEventListener('pointermove', onHoverMove);\n window.addEventListener('pointermove', onWindowMove);\n window.addEventListener('pointerup', onUp);\n window.addEventListener('pointercancel', onCancel);\n el.addEventListener('wheel', onWheel, { passive: false });\n const hasGestureEvents = 'GestureEvent' in window;\n if (hasGestureEvents) {\n el.addEventListener('gesturestart', onGestureStart);\n el.addEventListener('gesturechange', onGestureChange);\n el.addEventListener('gestureend', onGestureStart); // reset the base\n }\n\n return () => {\n el.removeEventListener('pointerdown', onDown);\n el.removeEventListener('pointermove', onHoverMove);\n window.removeEventListener('pointermove', onWindowMove);\n window.removeEventListener('pointerup', onUp);\n window.removeEventListener('pointercancel', onCancel);\n el.removeEventListener('wheel', onWheel);\n if (hasGestureEvents) {\n el.removeEventListener('gesturestart', onGestureStart);\n el.removeEventListener('gesturechange', onGestureChange);\n el.removeEventListener('gestureend', onGestureStart);\n }\n clearLongPress();\n stopFrames();\n end(); // balance an open transaction on unmount\n };\n}\n","/**\n * Selection-handle DOM binding — the listener mechanics of dragging one\n * handle element, shared by every framework adapter (the policy — geometry\n * and the drag session — lives in `@embedpdf/plugin-selection`'s pure\n * `handles` module; this file never imports it, per the layering law:\n * structural interfaces only).\n *\n * The load-bearing subtlety this module owns: the pointer-DOWN shield must be\n * a NATIVE listener. The stage's gesture controller listens natively on the\n * container, so a framework-synthetic stopPropagation (React runs its\n * handlers at the root, AFTER the container's native ones) would be too late\n * — the stage would already be panning underneath the handle drag. Once the\n * down is captured here, movement tracks by CLIENT DELTAS from the handle's\n * own base point: no DOM geometry reads, and the client↔overlay conversion\n * cancels out.\n */\n\ninterface SurfacePoint {\n x: number;\n y: number;\n}\n\n/** The armed drag the adapter hands back — plugin-selection's\n * `SelectionHandleDragSession` satisfies it. */\nexport interface SelectionHandleSession {\n move(overlay: SurfacePoint): void;\n end(): void;\n}\n\nexport interface AttachSelectionHandleOptions {\n /**\n * Called at pointer-down to arm a drag: return the drag session plus the\n * handle's current BASE point in overlay space (the bar's midpoint — the\n * point the user grabbed). Return null to decline (endpoint vanished, page\n * not laid out) — the press then does nothing and nothing is captured.\n */\n arm(): { base: SurfacePoint; session: SelectionHandleSession } | null;\n}\n\n/** Bind one handle element. Returns the detach fn. */\nexport function attachSelectionHandle(\n el: HTMLElement,\n options: AttachSelectionHandleOptions,\n): () => void {\n let active: {\n session: SelectionHandleSession;\n base: SurfacePoint;\n baseClient: SurfacePoint;\n pointerId: number;\n } | null = null;\n\n const onMove = (e: PointerEvent) => {\n if (!active || e.pointerId !== active.pointerId) return;\n active.session.move({\n x: active.base.x + (e.clientX - active.baseClient.x),\n y: active.base.y + (e.clientY - active.baseClient.y),\n });\n };\n // Release and system-cancel settle identically: the selection's own commit\n // semantics live in the session; there is no half-state to revert here.\n const onUp = (e: PointerEvent) => {\n if (!active || e.pointerId !== active.pointerId) return;\n const s = active.session;\n active = null;\n s.end();\n };\n const onDown = (e: PointerEvent) => {\n if (active) return;\n const armed = options.arm();\n if (!armed) return;\n e.preventDefault();\n e.stopPropagation(); // native: fires BEFORE the stage controller's listener\n try {\n el.setPointerCapture(e.pointerId);\n } catch {\n // best-effort, like the scrollbar: an already-released pointer\n // (pen/touch races, synthetic events in tests) throws — the drag must\n // still arm, and the window-less capture path still delivers to `el`.\n }\n active = {\n session: armed.session,\n base: armed.base,\n baseClient: { x: e.clientX, y: e.clientY },\n pointerId: e.pointerId,\n };\n };\n\n el.addEventListener('pointerdown', onDown);\n el.addEventListener('pointermove', onMove);\n el.addEventListener('pointerup', onUp);\n el.addEventListener('pointercancel', onUp);\n return () => {\n el.removeEventListener('pointerdown', onDown);\n el.removeEventListener('pointermove', onMove);\n el.removeEventListener('pointerup', onUp);\n el.removeEventListener('pointercancel', onUp);\n active = null;\n };\n}\n","/**\n * Stage surface binding — everything DOM about attaching one element to one\n * stage lens, shared by every framework adapter (React, Angular, …) so the\n * binding can never drift between them:\n *\n * • ResizeObserver → `setViewport` (the shell only reports; placement is the\n * stage plugin's job)\n * • device-pixel-ratio observation → `setDevicePixelRatio` (dppx changes\n * re-subscribe, since the media query's value itself moves)\n * • pointer-sample normalization — the ONE place a PointerEvent becomes a\n * page-resolved, source-stamped sample for the interaction hub\n * • the gesture controller (wheel, Safari trackpad gestures, synthesized\n * touch physics) from `./stage-gestures`\n *\n * The framework adapters keep only their reactive glue: refs/effects, page\n * surfaces, cursor-style subscription, overlay slots.\n *\n * Dependency note: like the gesture controller, this module speaks to the\n * stage and the hub through STRUCTURAL interfaces ({@link StageSurfaceHost},\n * {@link StageSurfaceHub}) — satisfied by `StageCapability` and\n * `InteractionCapability`, imported by neither. @embedpdf/web stays free of\n * plugin imports, per the layering law.\n *\n * Browser-only by nature (ResizeObserver, matchMedia): call from a mounted\n * effect / browser platform check, exactly like the gesture controller.\n */\nimport { createStageGestureController } from './stage-gestures';\nimport type { StageGestureHost, StageGestureSink, StageWheelSample } from './stage-gestures';\n\ninterface SurfacePoint {\n x: number;\n y: number;\n}\n\n/** What the binding needs from the stage — `StageCapability` satisfies it. */\nexport interface StageSurfaceHost extends StageGestureHost {\n setViewport(size: { width: number; height: number }): void;\n setDevicePixelRatio(ratio: number): void;\n /** Viewport point → the page under it (with per-page display context), or null over a gap. */\n pageAt(screen: SurfacePoint): {\n pon: number;\n point: SurfacePoint;\n scale?: number;\n rotation?: 0 | 90 | 180 | 270;\n zoom?: number;\n } | null;\n /** Viewport point → a SPECIFIC page's content space, unclamped (frame-stable projection). */\n pointOnPage(pon: number, screen: SurfacePoint): SurfacePoint | null;\n}\n\n/**\n * The page-resolved sample the binding hands the hub — structurally a\n * `PointerSample` (plugin-interaction). `source` carries the lens identity so\n * the hub can route lens-scoped handlers; with several stages on one document,\n * a drag on one lens must never be captured by another lens's handler.\n */\nexport interface StageSurfaceSample {\n phase: 'down' | 'move' | 'up' | 'cancel';\n viewport: SurfacePoint;\n page?: {\n pon: number;\n point: SurfacePoint;\n scale?: number;\n rotation?: 0 | 90 | 180 | 270;\n zoom?: number;\n };\n project: (pon: number) => SurfacePoint | null;\n modifiers: { shift: boolean; alt: boolean; ctrl: boolean; meta: boolean };\n clickCount: number;\n pointerType: 'mouse' | 'pen' | 'touch';\n gesture?: 'long-press';\n source?: string;\n}\n\n/** What the binding needs from the interaction hub — `InteractionCapability` satisfies it. */\nexport interface StageSurfaceHub {\n dispatch(sample: StageSurfaceSample): void;\n activeTool(): { touchDirect?: boolean };\n wouldClaimTouch(sample: StageSurfaceSample): boolean;\n}\n\nexport interface StageSurfaceOptions {\n /** Route pointer input to the interaction hub (page-resolved samples) instead\n * of built-in drag-to-pan. Omit/null for a hub-less stage. */\n hub?: StageSurfaceHub | null;\n /** The lens identity stamped on every sample (`StageCapability.lensId()`),\n * so lens-scoped handlers only see their own stage's input. */\n source?: string;\n /** Ambient zoom gestures (see {@link StageGestureOptions}). Default true. */\n zoomGestures?: boolean;\n /** Override the wheel → zoom-factor classifier (defaults to `./wheel`). */\n wheelZoomFactor?: (sample: StageWheelSample) => number;\n}\n\n/** Attach the full surface binding. Returns the detach fn. */\nexport function createStageSurface(\n el: HTMLElement,\n stage: StageSurfaceHost,\n options: StageSurfaceOptions = {},\n): () => void {\n const hub = options.hub ?? null;\n const cleanups: Array<() => void> = [];\n\n // Only report the viewport size. Initial placement (home) is the stage\n // plugin's job — it places when it first learns a real size (and a\n // higher-priority initial-view provider can override). The shell stays dumb.\n const setViewport = () => stage.setViewport({ width: el.clientWidth, height: el.clientHeight });\n const ro = new ResizeObserver(setViewport);\n ro.observe(el);\n setViewport();\n cleanups.push(() => ro.disconnect());\n\n // Report the device pixel ratio so page transforms render crisp. dppx changes\n // (browser zoom, dragging between monitors) fire the media query;\n // re-subscribe each time since the query value itself moves.\n let mq: MediaQueryList | null = null;\n const reportDpr = () => {\n stage.setDevicePixelRatio(window.devicePixelRatio || 1);\n mq?.removeEventListener('change', reportDpr);\n mq = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);\n mq.addEventListener('change', reportDpr);\n };\n reportDpr();\n cleanups.push(() => mq?.removeEventListener('change', reportDpr));\n\n // The sink is the hub bridge: it converts events to page-resolved samples\n // (`pageAt` per event, so a drag can cross pages) and stamps the lens source.\n const sampleOf = (\n phase: StageSurfaceSample['phase'],\n e: PointerEvent,\n clickCount = 1,\n gesture?: StageSurfaceSample['gesture'],\n ): StageSurfaceSample => {\n const r = el.getBoundingClientRect();\n const viewport = { x: e.clientX - r.left, y: e.clientY - r.top };\n return {\n phase,\n viewport,\n page: stage.pageAt(viewport) ?? undefined,\n // Page-anchored gestures (annotation move/resize) track the origin\n // page's frame through this even when the cursor is off that page.\n project: (pon) => stage.pointOnPage(pon, viewport),\n modifiers: { shift: e.shiftKey, alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey },\n clickCount,\n pointerType: (e.pointerType || 'mouse') as StageSurfaceSample['pointerType'],\n ...(gesture ? { gesture } : {}),\n ...(options.source ? { source: options.source } : {}),\n };\n };\n const forward = (\n phase: StageSurfaceSample['phase'],\n e: PointerEvent,\n clickCount = 1,\n gesture?: StageSurfaceSample['gesture'],\n ) => {\n hub?.dispatch(sampleOf(phase, e, clickCount, gesture));\n };\n const sink: StageGestureSink | null = hub\n ? {\n down: (e, clickCount) => forward('down', e, clickCount),\n move: (e) => forward('move', e),\n up: (e) => forward('up', e),\n cancel: (e) => forward('cancel', e),\n hover: (e) => forward('move', e), // no owner → the hub routes to onHover\n // Touch long-press = a word-select down (clickCount 2 keeps the\n // word-selection contract; the `gesture` marker is the honest\n // long-press signal for haptics/pickup handlers).\n longPress: (e) => forward('down', e, 2, 'long-press'),\n // Touch consent: an armed drawing/markup tool takes fingers wholesale;\n // otherwise per-point claims (a selected annotation's body or handles)\n // decide. A pure pre-flight — nothing captures.\n claimsPoint: (e) =>\n !!hub.activeTool().touchDirect || hub.wouldClaimTouch(sampleOf('down', e)),\n }\n : null;\n cleanups.push(\n createStageGestureController(el, stage, {\n zoomGestures: options.zoomGestures,\n wheelZoomFactor: options.wheelZoomFactor,\n sink,\n }),\n );\n\n return () => {\n for (const fn of cleanups) fn();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["w"],"sources":["../src/file-picker.ts","../src/clipboard.ts","../src/anchored-position.ts","../src/cursor.ts","../src/external-uri.ts","../src/actions-ui.ts","../src/painted-image.ts","../src/feedback.ts","../src/wheel.ts","../src/stage-gestures.ts","../src/selection-handles.ts","../src/stage-surface.ts","../src/byte-store.ts"],"sourcesContent":["/**\n * A one-shot file dialog. Opens a throwaway `<input type=file>`, resolves the\n * chosen `File`, or `null` when the dialog is dismissed. MUST be called\n * synchronously from a user gesture (a click) or the browser refuses to open it.\n *\n * A `File` is a `Blob`, which is a `BinarySource` everywhere in the stack, so the\n * caller can hand the result straight to the engine — this module stays a pure\n * DOM utility with no EmbedPDF types.\n */\nconst IMAGE_ACCEPT = 'image/png,image/jpeg,application/pdf';\n\nexport interface PickFileOptions {\n /** The dialog's `accept` filter. Defaults to PNG / JPEG / PDF. */\n accept?: string;\n /** Allow multiple selection (returns the first file). Defaults to false. */\n multiple?: boolean;\n}\n\n/** Open the file dialog; resolve the picked file, or null if cancelled. */\nexport function pickImageFile(options: PickFileOptions = {}): Promise<File | null> {\n const { accept = IMAGE_ACCEPT, multiple = false } = options;\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n input.multiple = multiple;\n input.style.display = 'none';\n\n let settled = false;\n const finish = (value: File | null): void => {\n if (settled) return;\n settled = true;\n window.removeEventListener('focus', onFocus);\n input.remove();\n resolve(value);\n };\n\n // A window refocus means the dialog closed. Give a queued `change` a tick to\n // win, then treat a bare refocus as a cancel — the fallback for browsers that\n // predate the `cancel` event.\n const onFocus = (): void => {\n setTimeout(() => finish(null), 300);\n };\n\n input.addEventListener('change', () => finish(input.files?.[0] ?? null), { once: true });\n input.addEventListener('cancel', () => finish(null), { once: true });\n window.addEventListener('focus', onFocus, { once: true });\n\n document.body.appendChild(input);\n input.click();\n });\n}\n\n/** The same dialog with NO type filter — any file (attachments). */\nexport const pickFile = (options: PickFileOptions = {}): Promise<File | null> =>\n pickImageFile({ accept: '*/*', ...options });\n\n/**\n * Hand bytes to the user as a browser download (a one-shot `<a download>`).\n * The DOM half of \"download this attachment\" — pair with the annotation\n * capability's `downloadAttachment` or the document `attachments.download`.\n */\nexport function saveFile(bytes: Uint8Array | Blob, name: string, mimeType?: string): void {\n const blob =\n bytes instanceof Blob\n ? bytes\n : new Blob([bytes as BlobPart], { type: mimeType ?? 'application/octet-stream' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = name;\n a.style.display = 'none';\n document.body.appendChild(a);\n a.click();\n a.remove();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n","/**\n * Clipboard wiring for text selection — the environmental half of copy.\n *\n * The selection plugin is deliberately DOM-free: its public capability ends\n * at `readText()` (data). Everything that touches `navigator.clipboard` or\n * the native `copy` event lives HERE, structurally typed so this module\n * stays a pure DOM utility with no EmbedPDF dependencies (the selection\n * capability satisfies {@link ClipboardSelectionSource} as-is).\n */\n\n/** The slice of the selection capability clipboard wiring needs. */\nexport interface ClipboardSelectionSource {\n hasSelection(): boolean;\n canCopy(): boolean;\n readText(): Promise<string>;\n onChange(cb: () => void): () => void;\n onCommit(cb: () => void): () => void;\n}\n\nexport interface SelectionClipboardOptions {\n /**\n * Prefetch the selected text when the selection settles (default true).\n * This is what makes the NATIVE copy path work: a `copy` event handler\n * must call `clipboardData.setData` synchronously — awaiting a page-text\n * read inside it is too late — and it also keeps the async path instant.\n * The fetch is one versioned, cached, permission-gated read per page;\n * pass false to make copying strictly on-demand.\n */\n prefetch?: boolean;\n /** Event target to listen on. Defaults to `document`. */\n target?: Document;\n}\n\nconst PREFETCH_DEBOUNCE_MS = 150;\n\n/**\n * Wire clipboard copy for a selection. Two delivery paths, both fed by the\n * prefetched text:\n *\n * - the native `copy` event (menu Edit→Copy, or ctrl/cmd+C while the page\n * has a DOM selection or focused editable — e.g. the viewer shell's\n * focus sink): answered SYNCHRONOUSLY from the cache;\n * - ctrl/cmd+C with no DOM selection (canvas-rendered viewers usually\n * have none, and browsers don't dispatch `copy` then): a keydown\n * fallback writes via the async Clipboard API inside the keystroke's\n * user-activation window.\n *\n * Returns the unwire function. Mount once per document view (not per page).\n */\nexport function wireSelectionClipboard(\n selection: ClipboardSelectionSource,\n options: SelectionClipboardOptions = {},\n): () => void {\n const target = options.target ?? document;\n const prefetch = options.prefetch ?? true;\n let cached: string | null = null;\n let generation = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const refresh = (): void => {\n cached = null;\n if (!prefetch || !selection.hasSelection() || !selection.canCopy()) return;\n const gen = ++generation;\n selection.readText().then(\n (text) => {\n if (gen === generation) cached = text;\n },\n () => {}, // denied / failed — the async path will surface it if used\n );\n };\n\n // Commit (gesture end) refreshes immediately; change (drag, programmatic\n // select) debounces so a drag doesn't fetch per pointer move.\n const offCommit = selection.onCommit(() => {\n if (timer) clearTimeout(timer);\n timer = null;\n refresh();\n });\n const offChange = selection.onChange(() => {\n generation++;\n cached = null;\n if (timer) clearTimeout(timer);\n timer = setTimeout(refresh, PREFETCH_DEBOUNCE_MS);\n });\n\n const onCopy = (e: ClipboardEvent): void => {\n if (cached == null || cached === '' || !selection.hasSelection()) return;\n e.clipboardData?.setData('text/plain', cached);\n e.preventDefault();\n };\n\n const onKeyDown = (e: KeyboardEvent): void => {\n if (!(e.metaKey || e.ctrlKey) || e.key.toLowerCase() !== 'c' || e.shiftKey || e.altKey) {\n return;\n }\n if (!selection.hasSelection() || !selection.canCopy()) return;\n // A live DOM selection means the browser will dispatch `copy` itself and\n // our synchronous handler answers — don't double-write.\n const dom = target.getSelection?.();\n if (dom && !dom.isCollapsed) return;\n void copySelection(selection); // instant when prefetched; still inside user activation\n };\n\n target.addEventListener('copy', onCopy);\n target.addEventListener('keydown', onKeyDown);\n return () => {\n offCommit();\n offChange();\n if (timer) clearTimeout(timer);\n target.removeEventListener('copy', onCopy);\n target.removeEventListener('keydown', onKeyDown);\n };\n}\n\n/**\n * Toolbar/menu path: read the selected text and write it to the clipboard\n * via the async Clipboard API. Call from a user-gesture handler (browsers\n * require a secure context + user activation for clipboard writes). Resolves\n * with what was copied ('' when nothing was — nothing is written then, so an\n * empty selection never clobbers the user's clipboard).\n */\nexport async function copySelection(selection: ClipboardSelectionSource): Promise<string> {\n const text = await selection.readText();\n if (text !== '') await navigator.clipboard.writeText(text);\n return text;\n}\n","/**\n * Anchored-overlay geometry for UI floating over page content (selection\n * menus, draft menus, popovers): the projector SNAPSHOT contract, the\n * anchor shape, and the placement math. Pure values and pure functions over\n * structural rect/point shapes — no EmbedPDF types, no framework, no\n * reactive lifecycle — so every framework adapter (React, Vue, Svelte,\n * Angular) shares ONE implementation and binds it with its NATIVE\n * reactivity (context revision, signals, computed, $derived).\n *\n * The boundary laws:\n * - Placement policy lives HERE, above the projector seam: a projector\n * only knows geometry — never what is being positioned nor where it\n * prefers to sit.\n * - A projector describes the CURRENT projection; it never implements a\n * framework-style reactive lifecycle. State-driven changes (the Stage\n * camera) must reach consumers through the framework's own render\n * cycle so surface and overlay commit TOGETHER; only genuinely\n * browser-driven changes (a PageView moving because the document\n * scrolled) use an external listener ({@link observeClientGeometry}).\n */\n\nexport interface AnchoredRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface AnchoredPoint {\n x: number;\n y: number;\n}\n\nexport type AnchoredPlacement = 'top' | 'right' | 'bottom' | 'left';\n\n/** CSS-ready output: absolute/fixed `left`/`top` plus a centering transform. */\nexport interface AnchoredPosition {\n left: number;\n top: number;\n transform: string;\n}\n\n/**\n * Place an upright element around `box` (screen px). `avoid` is a screen\n * point the element must clear (e.g. the rotate knob): the element extends\n * ONLY the edge it sits on, and ONLY when the point protrudes past that\n * edge — so it clears the obstacle without ever shifting off-centre on the\n * other axis. When the point is on another side (e.g. a 90° shape, knob at\n * mid-height for a `top` placement) the edge is untouched and the element\n * stays centred on `box`.\n */\n/** What anchored UI attaches to: a content-space rect on a page, plus\n * optional content-space points the UI must clear (e.g. a rotate knob).\n * Structural — plugin anchor reads satisfy it without importing this\n * package. */\nexport interface AnchorTarget {\n pon: number;\n bounds: AnchoredRect;\n avoid?: AnchoredPoint[];\n}\n\n/**\n * A page surface's PROJECTION SNAPSHOT: how a content-space rect on a page\n * becomes screen coordinates, right now. Provided by `<Stage>`\n * (camera-driven, pure state, no DOM reads) and `<PageView>` (DOM-measured).\n * Deliberately NO subscribe here — when projection changes is a framework\n * binding concern (see the module doc), not part of the snapshot.\n */\nexport interface ViewProjector {\n /**\n * Which coordinate space `toScreen` speaks, and therefore how anchored\n * content mounts:\n * - 'overlay': coords are relative to the surface's own positioned\n * container → rendered in place, position:absolute. (Stage)\n * - 'client': coords are viewport px → portalled to the document body,\n * position:fixed, so no ancestor overflow can clip. (PageView)\n */\n space: 'overlay' | 'client';\n /** Content-space rect on a page → coords in `space`. Null: not projectable\n * right now (page not shown / not measurable yet). */\n toScreen(pon: number, rect: AnchoredRect): AnchoredRect | null;\n toScreenPoint(pon: number, at: AnchoredPoint): AnchoredPoint | null;\n /** The page's live view facts (for anchor reads that need them — e.g. the\n * screen-constant rotate-knob stalk), or null when the page isn't shown. */\n viewEnv(pon: number): {\n scale: number;\n rotation: 0 | 90 | 180 | 270;\n zoom: number;\n } | null;\n}\n\n/**\n * THE anchored projection: anchor → screen position, in one pure call —\n * projection, avoid-point transform, and the shared placement policy.\n * Frameworks recompute this inside their native reactive primitive\n * (a React render, a Vue `computed`, an Angular signal, a Svelte\n * `$derived`); nothing here schedules anything.\n */\nexport function projectAnchoredTarget(\n projector: ViewProjector,\n anchor: AnchorTarget,\n placement: AnchoredPlacement,\n gap: number,\n): AnchoredPosition | null {\n const box = projector.toScreen(anchor.pon, anchor.bounds);\n if (!box) return null;\n const avoid = anchor.avoid?.length\n ? projector.toScreenPoint(anchor.pon, anchor.avoid[0])\n : null;\n return positionAnchoredRect(box, placement, gap, avoid);\n}\n\n/**\n * The one genuinely BROWSER-driven invalidation: an element in normal\n * document flow moves when the document scrolls or the window resizes —\n * no state change announces it. `<PageView>`'s binding registers this;\n * the Stage never does (its camera is state, delivered through render).\n */\nexport function observeClientGeometry(callback: () => void): () => void {\n window.addEventListener('scroll', callback, true);\n window.addEventListener('resize', callback);\n return () => {\n window.removeEventListener('scroll', callback, true);\n window.removeEventListener('resize', callback);\n };\n}\n\nexport function positionAnchoredRect(\n box: AnchoredRect,\n placement: AnchoredPlacement,\n gap: number,\n avoid?: AnchoredPoint | null,\n): AnchoredPosition {\n const cx = box.x + box.width / 2;\n const cy = box.y + box.height / 2;\n\n switch (placement) {\n case 'bottom': {\n const edge = Math.max(box.y + box.height, avoid ? avoid.y : -Infinity);\n return { left: cx, top: edge + gap, transform: 'translate(-50%, 0)' };\n }\n case 'left': {\n const edge = Math.min(box.x, avoid ? avoid.x : Infinity);\n return { left: edge - gap, top: cy, transform: 'translate(-100%, -50%)' };\n }\n case 'right': {\n const edge = Math.max(box.x + box.width, avoid ? avoid.x : -Infinity);\n return { left: edge + gap, top: cy, transform: 'translate(0, -50%)' };\n }\n case 'top':\n default: {\n const edge = Math.min(box.y, avoid ? avoid.y : Infinity);\n return { left: cx, top: edge - gap, transform: 'translate(-50%, -100%)' };\n }\n }\n}\n","/**\n * SVG → CSS cursor string. The armed-tool indicator is the CURSOR itself (the\n * only zero-latency pointer-locked pixel the web has — the OS composites it),\n * so an app hands its toolbar icon here and assigns the result via the\n * interaction hub's `setToolCursor`.\n *\n * Browser rules baked into this module:\n * - The SVG must carry explicit `width`/`height` attributes (not just a\n * viewBox) or Chromium/WebKit ignore it — enforced here.\n * - The hard ceiling is 128×128 CSS px (bigger falls back to the keyword).\n * Above 32×32, Chromium temporarily resets the cursor to default while it\n * would overlap browser UI (anti-spoofing) — fine mid-viewport, so ~40px\n * compositions are common; stay ≤128.\n * - The keyword fallback covers browsers that refuse SVG cursor images.\n *\n * Pure string work with no EmbedPDF types — like the file picker, this module\n * is a plain web utility.\n */\n\nexport interface SvgCursorOptions {\n /** Complete `<svg>` markup, colors baked in (no `currentColor` — a cursor\n * image has no cascade to inherit from). */\n svg: string;\n /** The click point inside the image, CSS px from the top-left. Default 0,0\n * (arrow-style: the image's corner is the tip). */\n hotspot?: { x: number; y: number };\n /** Keyword cursor when the image can't render. Default `'crosshair'`. */\n fallback?: string;\n}\n\n/** Build the CSS cursor value: `url(\"data:image/svg+xml,…\") x y, fallback`. */\nexport function svgCursor({ svg, hotspot, fallback = 'crosshair' }: SvgCursorOptions): string {\n // Scan only the opening `<svg …>` tag (bounded slice) rather than running\n // `/<svg[^>]*\\swidth\\s*=/` over the whole string — the unbounded form is\n // quadratic on pathological inputs (many `<svg` fragments with no `>`),\n // while accepting exactly the same markup: `[^>]*` could never cross a\n // `>` either.\n const tagStart = svg.indexOf('<svg');\n const tagEnd = tagStart >= 0 ? svg.indexOf('>', tagStart) : -1;\n const openTag = tagEnd >= 0 ? svg.slice(tagStart, tagEnd + 1) : '';\n if (!/\\swidth\\s*=/.test(openTag) || !/\\sheight\\s*=/.test(openTag)) {\n throw new Error(\n '[web] svgCursor: the <svg> needs explicit width/height attributes — ' +\n 'browsers ignore cursor images without an intrinsic size',\n );\n }\n const uri = `data:image/svg+xml,${encodeURIComponent(svg)}`;\n const spot = hotspot ? ` ${Math.round(hotspot.x)} ${Math.round(hotspot.y)}` : '';\n return `url(\"${uri}\")${spot}, ${fallback}`;\n}\n","/**\n * External-URI hygiene for PDF link annotations — written ONCE here so every\n * framework adapter (react, vue, svelte, angular) shares the same policy.\n *\n * PDFs are untrusted input: a link's `/URI` can carry anything, including\n * `javascript:` payloads. The default allowlist covers what a document link\n * legitimately opens; everything else (javascript:, file:, data:, vbscript:,\n * custom app schemes) resolves to `null` and the adapters render the link\n * WITHOUT an href — activation still reports through the plugin, so an\n * embedder that needs a custom scheme can handle it in `onActivate`.\n */\nconst DEFAULT_ALLOWED_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:']);\n\n/**\n * Validate a link URI for opening in the browser. Returns the URI when its\n * scheme is allowed (scheme-relative and bare URIs resolve as https), else\n * `null`. Never throws — a malformed URI is just an unopenable one.\n */\nexport function sanitizeExternalUri(\n uri: string,\n allowedSchemes: Iterable<string> = DEFAULT_ALLOWED_SCHEMES,\n): string | null {\n const trimmed = uri.trim();\n if (!trimmed) return null;\n const allowed = new Set(\n [...allowedSchemes].map((s) => (s.endsWith(':') ? s.toLowerCase() : `${s.toLowerCase()}:`)),\n );\n try {\n // Base resolves scheme-relative (\"//host\") and bare (\"example.com/x\" is a\n // relative path → https) forms; an absolute scheme wins over the base.\n const url = new URL(trimmed, 'https://invalid.example/');\n return allowed.has(url.protocol) ? trimmed : null;\n } catch {\n return null;\n }\n}\n","/**\n * The DEFAULT actions UI adapter — the origin×phase visibility matrix plus\n * the browser fallbacks, written ONCE here so every framework binding\n * (react, angular, …) ships the SAME policy instead of forking it per\n * framework. Types are structural twins of `@embedpdf/plugin-actions`'\n * adapter contract (declared here so this package stays plugin-free, per\n * the layering law); assignability is checked where a binding installs it.\n *\n * The matrix (defaults only — an embedder override receives EVERY effect\n * with its context attached and decides for itself):\n * - URIs open through `sanitizeExternalUri` in a new tab (the dispatcher\n * already policy-gated the node; blocked schemes are dropped here).\n * - Print: hover/lifecycle-origin requests never open the dialog; user\n * requests fall back to `globalThis.print`. The `doc.print` AUTHORITY\n * gate is upstream in the plugin and not overridable.\n * - Alert: document-open nags (lifecycle origin, boot phase) never reach\n * `window.alert` — the boot-nag gap, closed.\n * - gotoPage: delegates to the binding-supplied navigator (the stage).\n */\nimport { sanitizeExternalUri } from './external-uri';\n\n/** Structural twin of plugin-actions' `ActionOrigin`. */\nexport type ActionsUiOrigin = 'user' | 'hover' | 'lifecycle';\n\n/** Structural twin of plugin-actions' `ActionUiContext`. */\nexport interface ActionsUiEffectContext {\n origin: ActionsUiOrigin;\n phase: 'boot' | 'user';\n}\n\n/** Structural twin of plugin-actions' `ActionUiAdapter`. */\nexport interface ActionsUiAdapterShape {\n openUri(uri: string, opts: { isMap: boolean; origin: ActionsUiOrigin }): void;\n print(opts?: ActionsUiEffectContext): void;\n alert?(message: string, opts: ActionsUiEffectContext & { icon: number; title?: string }): void;\n gotoPage?(page: number, opts: ActionsUiEffectContext): void;\n}\n\nexport interface DefaultActionsUiAdapterOptions {\n /**\n * Per-handler overrides. Pass a FUNCTION for late binding (a binding's\n * mutable handlers ref) — it is consulted on every effect, so a fresh\n * closure never needs a reinstall.\n */\n overrides?: Partial<ActionsUiAdapterShape> | (() => Partial<ActionsUiAdapterShape> | undefined);\n /** Navigation default for script `this.pageNum = n` requests. */\n goToPage?: (page: number) => void;\n}\n\nexport const createDefaultActionsUiAdapter = (\n options: DefaultActionsUiAdapterOptions = {},\n): ActionsUiAdapterShape => {\n const overridesOf = (): Partial<ActionsUiAdapterShape> | undefined =>\n typeof options.overrides === 'function' ? options.overrides() : options.overrides;\n return {\n openUri: (uri, opts) => {\n const current = overridesOf();\n if (current?.openUri) {\n current.openUri(uri, opts);\n return;\n }\n const href = sanitizeExternalUri(uri);\n if (href && typeof window !== 'undefined') {\n window.open(href, '_blank', 'noopener,noreferrer');\n }\n },\n print: (opts) => {\n const current = overridesOf();\n if (current?.print) current.print(opts);\n else if (opts && opts.origin !== 'user') {\n // Hover/lifecycle scripts never open the print dialog by default.\n } else if (typeof globalThis.print === 'function') globalThis.print();\n },\n alert: (message, opts) => {\n const current = overridesOf();\n if (current?.alert) current.alert(message, opts);\n else if (opts.origin === 'lifecycle' || opts.phase === 'boot') {\n // Document-open nags (Adobe version checks, lifecycle scripts)\n // never alert by default — the boot-nag gap, closed.\n } else if (typeof globalThis.alert === 'function') globalThis.alert(message);\n },\n gotoPage: (page, opts) => {\n const current = overridesOf();\n if (current?.gotoPage) current.gotoPage(page, opts);\n else options.goToPage?.(page);\n },\n };\n};\n","/** A browser image source whose object URL has an explicit lifetime. */\nexport interface ObjectUrlImageSource {\n objectUrl(signal?: AbortSignal): Promise<{ url: string; revoke(): void }>;\n}\n\nexport interface PaintedImageCallbacks {\n onPainted(): void;\n onUnpainted(): void;\n}\n\n/**\n * Bind a raster source to an <img> without ever painting its incomplete-image\n * placeholder. The source becomes visible on load; retained coverage is\n * released only after the browser has had one full frame to present it.\n *\n * Call this from the framework's pre-paint mount hook and invoke the returned\n * cleanup on unmount. Keeping this DOM lifecycle here lets every framework\n * adapter remain a ref plus one bind/unbind hook.\n */\nexport function bindPaintedImage(\n image: HTMLImageElement,\n source: ObjectUrlImageSource,\n callbacks: PaintedImageCallbacks,\n): () => void {\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n let frame = 0;\n let painted = false;\n\n // The element is already committed when a mount hook runs. Hide it before\n // that commit can paint, then reveal only once the bitmap has loaded.\n image.style.visibility = 'hidden';\n\n const onLoad = () => {\n image.style.visibility = 'visible';\n\n // rAF callbacks run before a frame paints. The first frame presents the\n // newly visible image while retained coverage remains; the second reports\n // that presentation opportunity and permits the retained source to leave.\n frame = requestAnimationFrame(() => {\n frame = requestAnimationFrame(() => {\n frame = 0;\n painted = true;\n callbacks.onPainted();\n });\n });\n };\n\n image.addEventListener('load', onLoad, { once: true });\n\n void source.objectUrl(controller.signal).then(\n (objectUrl) => {\n if (controller.signal.aborted) {\n objectUrl.revoke();\n return;\n }\n revoke = objectUrl.revoke;\n image.src = objectUrl.url;\n },\n () => {\n // Aborted or unavailable: remain hidden and retain fallback coverage.\n },\n );\n\n return () => {\n controller.abort();\n if (frame) cancelAnimationFrame(frame);\n image.removeEventListener('load', onLoad);\n image.style.visibility = 'hidden';\n image.removeAttribute('src');\n revoke?.();\n if (painted) callbacks.onUnpainted();\n };\n}\n","/**\n * Platform-feedback providers — the browser implementations of the interaction\n * hub's haptic port (structurally `PlatformFeedback` from\n * `@embedpdf/plugin-interaction`; declared structurally here so this package\n * stays plugin-free, per the layering law).\n *\n * Honesty about reach: `navigator.vibrate` works on Android browsers and is\n * NOT implemented in iOS Safari — there the default provider is a silent\n * no-op until Apple ships a haptics API. (The community switch-control hacks\n * were evaluated and deliberately NOT shipped: Apple patched programmatic\n * triggering in iOS 26.5, leaving only tap-on-control tricks that don't fit a\n * viewer's gesture moments.) The real iPhone haptic today comes through a\n * native shell: a WKWebView embedder installs a script message handler and\n * passes {@link wkFeedback} (or their own provider) instead.\n */\n\n/** Structural twin of plugin-interaction's `PlatformFeedback`. */\nexport interface WebPlatformFeedback {\n selection(): void;\n impact(weight?: 'light' | 'medium' | 'heavy'): void;\n notify(kind: 'success' | 'warning' | 'error'): void;\n}\n\nconst vibrate = (pattern: number | number[]): void => {\n if (typeof navigator !== 'undefined') navigator.vibrate?.(pattern);\n};\n\n/**\n * The default web provider: the Vibration API, mapped onto the three haptic\n * families (selection = the shortest tick; impact scales with weight; notify\n * uses patterns). A no-op wherever vibration is unsupported — notably iOS\n * Safari — so it is always safe to install.\n */\nexport const vibrationFeedback: WebPlatformFeedback = {\n selection: () => vibrate(8),\n impact: (weight = 'light') => vibrate(weight === 'heavy' ? 30 : weight === 'medium' ? 20 : 10),\n notify: (kind) =>\n vibrate(kind === 'error' ? [12, 60, 12, 60, 12] : kind === 'warning' ? [15, 70, 15] : [10, 50, 10]),\n};\n\ninterface WKMessageHandler {\n postMessage(message: unknown): void;\n}\n\n/**\n * A provider for native iOS shells: forwards each call to a WKWebView script\n * message handler the HOST APP installed under `handlerName` — an explicit\n * contract between the embedder and their own app, never a global this library\n * sniffs on its own. The message shape is\n * `{ family: 'selection' } | { family: 'impact', weight } | { family: 'notify', kind }`;\n * the native side maps families onto the matching `UIFeedbackGenerator`.\n * Calls fall back to `fallback` (default: {@link vibrationFeedback}) when the\n * handler is absent, so one provider serves a hybrid web/app deployment.\n */\nexport const wkFeedback = (\n handlerName: string,\n fallback: WebPlatformFeedback = vibrationFeedback,\n): WebPlatformFeedback => {\n const handler = (): WKMessageHandler | undefined => {\n if (typeof window === 'undefined') return undefined;\n const w = window as Window & {\n webkit?: { messageHandlers?: Record<string, WKMessageHandler | undefined> };\n };\n return w.webkit?.messageHandlers?.[handlerName];\n };\n return {\n selection: () => {\n const h = handler();\n if (h) h.postMessage({ family: 'selection' });\n else fallback.selection();\n },\n impact: (weight = 'light') => {\n const h = handler();\n if (h) h.postMessage({ family: 'impact', weight });\n else fallback.impact(weight);\n },\n notify: (kind) => {\n const h = handler();\n if (h) h.postMessage({ family: 'notify', kind });\n else fallback.notify(kind);\n },\n };\n};\n","/**\n * Wheel-input classification for ambient zoom — pure, so every framework\n * adapter shares one feel and the mapping is unit-testable.\n *\n * A ctrl/cmd wheel event is one of three physically different inputs that\n * happen to share an event type, each with its own unit system:\n *\n * • trackpad PINCH — browsers (Chrome/Edge/Firefox) synthesize ctrl+wheel\n * px-deltas scaled to the de-facto convention that `exp(-Δ/100)` tracks\n * the physical finger scale 1:1. Anything weaker feels dead (the content\n * must track the fingers — the Figma/maps feel).\n * • mouse NOTCHES — coarse steps: ±100/±120 px in Chrome, lines/pages mode\n * in Firefox. The pinch mapping would triple per click; a notch instead\n * steps by 1.2, the same ratio as the zoom buttons.\n * • cmd + continuous SCROLL — real scroll px, big and streaming (with\n * momentum): a gentle scrub coefficient. A cmd+mouse notch (±120 px)\n * lands within 0.3% of the 1.2 button step, so it needs no special case.\n *\n * (Safari never synthesizes ctrl+wheel for pinch — it fires proprietary\n * gesture events with an absolute scale; that wiring lives in the adapters.)\n */\nexport interface WheelSample {\n deltaY: number;\n /** WheelEvent.deltaMode: 0 = pixels, 1 = lines, 2 = pages. */\n deltaMode: number;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\n/** The per-notch step for discrete wheels — the zoom buttons' ratio. */\nconst NOTCH = 1.2;\n\n/** Zoom factor for a ctrl/cmd wheel event (the caller decides zoom vs pan). */\nexport function wheelZoomFactor(e: WheelSample): number {\n // Lines/pages mode (Firefox mouse wheels): one button-step per notch.\n if (e.deltaMode !== 0) return Math.pow(NOTCH, -Math.sign(e.deltaY));\n if (e.ctrlKey) {\n // px deltas with ctrl: a real mouse notch arrives big (±100/±120) and\n // steps like the buttons; anything smaller is a synthesized pinch.\n if (Math.abs(e.deltaY) >= 100) return Math.pow(NOTCH, -Math.sign(e.deltaY));\n return Math.exp(-e.deltaY / 100);\n }\n // cmd + continuous scroll: the gentle scrub.\n return Math.exp(-e.deltaY * 0.0015);\n}\n","/**\n * Stage gesture controller — the ONE DOM input binding for a Stage surface,\n * shared by every framework adapter (React, Angular, …) so the feel can never\n * drift between them.\n *\n * The premise (see the stage plugin's camera doctrine): desktop inputs arrive\n * with physics already applied by the OS — momentum lives in the wheel stream,\n * a trackpad pinch is one pre-arbitrated gesture stream. Touch arrives RAW:\n * with `touch-action: none` the platform scroller is out of the loop, so the\n * ballistics (velocity → fling) and the arbitration (is this contact a scroll,\n * a pinch, a tap, or a tool gesture?) must be synthesized here.\n *\n * Arbitration is MODALITY-AWARE:\n * - touch — navigation-first: one finger pans (whatever tool is armed), two\n * fingers pinch-zoom around their centroid, release velocity flings, a tap\n * forwards as a click, a double-tap zoom-toggles, a long-press hands the\n * gesture to the interaction hub (text selection), and a second finger\n * landing mid-tool-gesture CANCELS it into a pinch (the Notes/Procreate\n * convention).\n * - mouse/pen — tool-first, exactly the pre-existing behavior: with a hub,\n * every down/move/up forwards (pan is the pan tool's job); without one,\n * dragging pans. Wheel and Safari-trackpad gesture events are unchanged.\n *\n * Camera writes are rAF-COALESCED: pointer events only update gesture state;\n * one animation-frame tick applies at most one pan and one zoom per frame,\n * inside the host's begin/endGesture transaction. Events may arrive at 120 Hz;\n * the camera moves at display rate.\n *\n * Dependency note: this module speaks to the stage through the STRUCTURAL\n * {@link StageGestureHost} interface (satisfied by `StageCapability`) and to\n * the interaction hub through {@link StageGestureSink} (a closure the adapter\n * builds) — @embedpdf/web stays free of plugin imports, per the layering law.\n */\n\nimport { wheelZoomFactor } from './wheel';\n\nexport type StagePointerKind = 'mouse' | 'pen' | 'touch';\n\n/** What the controller needs from the camera — `StageCapability` satisfies it. */\nexport interface StageGestureHost {\n panBy(dxScreen: number, dyScreen: number): void;\n zoomAround(screenPt: { x: number; y: number }, factor: number): void;\n beginGesture(options?: { elastic?: boolean }): void;\n endGesture(): void;\n /** Momentum pan from a release velocity in screen px/s. */\n fling(velocityX: number, velocityY: number): void;\n /** True while a tween/fling runs — a touch-down then is a \"catch\", not a tap. */\n cameraInMotion(): boolean;\n doubleTapZoom(screenPt: { x: number; y: number }): void;\n}\n\n/**\n * Where non-navigation gestures go — the adapter's bridge to the interaction\n * hub. Every callback receives the ORIGINAL PointerEvent so the adapter can\n * resolve pages/points exactly as it always has. Omit the sink entirely for a\n * hub-less (built-in pan) stage.\n */\nexport interface StageGestureSink {\n down(e: PointerEvent, clickCount: number): void;\n move(e: PointerEvent): void;\n up(e: PointerEvent): void;\n /** The gesture was taken over by navigation (second finger → pinch) or\n * cancelled by the system — abort, don't commit. */\n cancel(e: PointerEvent): void;\n /** Pointer travel with no gesture in flight — cursor feedback. */\n hover(e: PointerEvent): void;\n /** A touch press held still: hand the gesture to the hub (the adapter\n * typically forwards it as a word-select down). Subsequent move/up arrive\n * via {@link move}/{@link up}. */\n longPress(e: PointerEvent): void;\n /**\n * Touch-consent pre-flight, asked at touch-down BEFORE the contact is\n * classified: does a tool have standing to own this contact? True when the\n * armed tool takes fingers wholesale (a drawing tool) or something under\n * the point claims its drags (a selected annotation's body or handles).\n * True routes the whole contact to {@link down}/{@link move}/{@link up} —\n * where a second finger still cancels it into a pinch. Must be a pure\n * read. Absent = never; the contact navigates.\n */\n claimsPoint?(e: PointerEvent): boolean;\n}\n\n/** The wheel fields the zoom classifier reads (see `./wheel`'s `WheelSample`). */\nexport interface StageWheelSample {\n deltaY: number;\n deltaMode: number;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\nexport interface StageGestureOptions {\n /** Ambient zoom (wheel-zoom, pinch-zoom, double-tap zoom). Off: zoom wheels\n * fall through to pan and pinches pan without zooming — but are still\n * swallowed, never page-zooming the browser. Default true. */\n zoomGestures?: boolean;\n /** The wheel → zoom-factor classifier. Defaults to this package's\n * `wheelZoomFactor` (browser wheel classification lives HERE, with the rest\n * of the browser input handling); inject to override or to fake in tests. */\n wheelZoomFactor?: (sample: StageWheelSample) => number;\n /** Tool routing for non-navigation gestures; omit for built-in-pan stages. */\n sink?: StageGestureSink | null;\n /** Touch press duration that becomes a long-press (ms). Default 450. */\n longPressMs?: number;\n /** Finger travel below which a touch stays a tap/press (px). Default 10. */\n tapSlopPx?: number;\n /** Max gap between taps for a double-tap (ms). Default 300. */\n doubleTapMs?: number;\n /** Release speed below which no fling starts (px/s). Default 50. */\n flingMinVelocity?: number;\n}\n\n/**\n * Release velocity from a trail of pointer samples: the mean velocity over the\n * trailing `windowMs` (first-to-last inside the window). Null when the trail is\n * too thin or too stale to trust — the standard \"held still, then let go\"\n * case, which must NOT fling. Pure; exported for tests.\n */\nexport function computeReleaseVelocity(\n samples: ReadonlyArray<{ t: number; x: number; y: number }>,\n now: number,\n windowMs = 100,\n): { vx: number; vy: number } | null {\n let firstIdx = -1;\n for (let i = 0; i < samples.length; i++) {\n if (now - samples[i].t <= windowMs) {\n firstIdx = i;\n break;\n }\n }\n if (firstIdx < 0 || firstIdx === samples.length - 1) return null;\n const first = samples[firstIdx];\n const last = samples[samples.length - 1];\n const dt = last.t - first.t;\n if (dt < 10) return null;\n return { vx: ((last.x - first.x) / dt) * 1000, vy: ((last.y - first.y) / dt) * 1000 };\n}\n\ninterface Tracked {\n id: number;\n kind: StagePointerKind;\n x: number;\n y: number;\n downX: number;\n downY: number;\n}\n\ntype Mode = 'idle' | 'pending' | 'pan' | 'pinch' | 'tool' | 'mousedrag';\n\n/** Attach the gesture controller to a Stage container. Returns the detach fn. */\nexport function createStageGestureController(\n el: HTMLElement,\n host: StageGestureHost,\n options: StageGestureOptions,\n): () => void {\n const zoomGestures = options.zoomGestures ?? true;\n const wheelZoom = options.wheelZoomFactor ?? wheelZoomFactor;\n const sink = options.sink ?? null;\n const LONG_PRESS_MS = options.longPressMs ?? 450;\n const SLOP = options.tapSlopPx ?? 10;\n const DOUBLE_TAP_MS = options.doubleTapMs ?? 300;\n const DOUBLE_TAP_RADIUS = 25;\n const FLING_MIN = options.flingMinVelocity ?? 50;\n const MIN_PINCH_SPAN = 20; // px — below this a span ratio is mostly noise\n const PINCH_RELEASE_GRACE_MS = 160; // leftover finger must OUTLIVE the release\n const SETTLE_SPEED = 0.12; // px/ms — below this the leftover finger has SETTLED\n\n const pointers = new Map<number, Tracked>();\n let mode: Mode = 'idle';\n let began = false; // a host gesture transaction is open\n let suppressTap = false; // this contact CAUGHT a moving camera — never a tap\n let touchToolGesture = false; // 'tool' mode entered via touch long-press\n let downEvent: PointerEvent | null = null; // first touch's down, for tap/long-press forwarding\n let longPressTimer: ReturnType<typeof setTimeout> | null = null;\n let panId = -1;\n // A pan is ARMED once its finger has crossed slop. Fresh touches arm on the\n // pending→pan transition; the finger LEFT OVER when a pinch ends starts\n // DISARMED — a gesture's identity persists through its release, so the\n // leftover contact must re-earn pan-hood exactly like a new finger would.\n // While disarmed, its micro-rolls neither pan nor pollute the velocity\n // trail (which still holds the pinch's CENTROID motion — the momentum a\n // compound gesture actually has). For a GRACE window after the transition\n // the slop base FOLLOWS the finger: in a fast release the leftover finger\n // is still genuinely moving, and distance alone cannot tell that from a\n // deliberate continuation — only outliving the release window can.\n let panArmed = true;\n let panGraceUntil = 0;\n let graceSample = { t: 0, x: 0, y: 0 }; // leftover finger's last observed sample\n // iOS-visible seam: real WebKit can deliver a trailing gesturechange after\n // the last pointerup of a pinch — with no touches left, the desktop-trackpad\n // path would apply a stray end-of-pinch zoom. Any RECENT touch activity\n // therefore suppresses gesture events; desktop trackpads never have any.\n let lastTouchAt = -Infinity;\n\n // rAF application state\n let frame = 0;\n let dirty = false;\n let lastApplied = { x: 0, y: 0 }; // pan focal point, client px\n let lastSpan = 0;\n\n // velocity trail of the pan focal point (finger, or pinch centroid)\n let trail: Array<{ t: number; x: number; y: number }> = [];\n // the pinch's SPAN trail, sampled beside the centroid: at release it decides\n // the gesture's CHARACTER. A fast pinch release has asymmetric finger\n // speeds (the lifting finger flicks away), which moves the TRUE centroid at\n // hundreds of px/s — physically real, but zoom-release noise the platform\n // ignores. Fling only when the centroid rate DOMINATES the span rate\n // (a two-finger pan); a zoom-dominant release ends still.\n let spanTrail: Array<{ t: number; x: number; y: number }> = [];\n\n // tap-pair state for double-tap\n let lastTapT = 0;\n let lastTapX = 0;\n let lastTapY = 0;\n\n // multi-click counter for mouse/pen tool downs (parity with the adapters'\n // previous createClickCounter: 400 ms / 6 px)\n let mLast = 0;\n let mX = 0;\n let mY = 0;\n let mCount = 0;\n const clickCount = (e: PointerEvent): number => {\n const now = Date.now();\n mCount =\n now - mLast <= 400 && Math.hypot(e.clientX - mX, e.clientY - mY) <= 6 ? mCount + 1 : 1;\n mLast = now;\n mX = e.clientX;\n mY = e.clientY;\n return mCount;\n };\n\n const vpt = (clientX: number, clientY: number) => {\n const r = el.getBoundingClientRect();\n return { x: clientX - r.left, y: clientY - r.top };\n };\n const touches = (): Tracked[] => {\n const out: Tracked[] = [];\n pointers.forEach((p) => {\n if (p.kind === 'touch') out.push(p);\n });\n return out;\n };\n\n const clearLongPress = () => {\n if (longPressTimer !== null) {\n clearTimeout(longPressTimer);\n longPressTimer = null;\n }\n };\n const stopFrames = () => {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n };\n const begin = (elastic = false) => {\n if (!began) {\n began = true;\n // touch contacts are elastic (rubber-band past the clamp); mouse drags\n // stay rigid — the desktop convention\n host.beginGesture(elastic ? { elastic: true } : undefined);\n }\n };\n const end = () => {\n if (began) {\n began = false;\n host.endGesture();\n }\n };\n const toIdle = () => {\n mode = 'idle';\n downEvent = null;\n touchToolGesture = false;\n trail = [];\n spanTrail = [];\n dirty = false;\n panId = -1;\n panArmed = true;\n panGraceUntil = 0;\n clearLongPress();\n stopFrames();\n };\n const pushSample = (x: number, y: number) => {\n trail.push({ t: performance.now(), x, y });\n if (trail.length > 16) trail.shift();\n };\n const maybeFling = () => {\n const v = computeReleaseVelocity(trail, performance.now());\n if (v && Math.hypot(v.vx, v.vy) >= FLING_MIN) host.fling(v.vx, v.vy);\n };\n\n // ── the application step, shared by the frame loop AND the release paths —\n // a release must flush whatever the last tick hadn't applied yet, or a fast\n // flick loses its final sub-frame of travel and a pinch its last span step.\n const flushPanTo = (x: number, y: number) => {\n const dx = x - lastApplied.x;\n const dy = y - lastApplied.y;\n if (dx !== 0 || dy !== 0) host.panBy(dx, dy);\n lastApplied = { x, y };\n dirty = false;\n };\n const flushPinch = (ax: number, ay: number, bx: number, by: number) => {\n const cx = (ax + bx) / 2;\n const cy = (ay + by) / 2;\n const span = Math.hypot(ax - bx, ay - by);\n const dx = cx - lastApplied.x;\n const dy = cy - lastApplied.y;\n if (dx !== 0 || dy !== 0) host.panBy(dx, dy);\n if (zoomGestures && span > MIN_PINCH_SPAN && lastSpan > MIN_PINCH_SPAN && span !== lastSpan) {\n host.zoomAround(vpt(cx, cy), span / lastSpan);\n }\n // The centroid + span trails sample HERE — once per applied frame, where\n // both fingers are read coherently. Per-event sampling zig-zags (fingers\n // report sequentially, so each single-finger move fakes a half-step of\n // centroid motion) and manufactures phantom release velocity. The span\n // trail is the release gate's evidence of the gesture's CHARACTER.\n pushSample(cx, cy);\n spanTrail.push({ t: performance.now(), x: span, y: 0 });\n if (spanTrail.length > 16) spanTrail.shift();\n lastApplied = { x: cx, y: cy };\n lastSpan = span;\n dirty = false;\n };\n\n // ── the per-frame application (one camera write per frame) ────────────────\n const tick = () => {\n frame = 0;\n if (mode === 'pan' || mode === 'mousedrag') {\n const p = pointers.get(panId);\n if (p && dirty) flushPanTo(p.x, p.y);\n frame = requestAnimationFrame(tick);\n } else if (mode === 'pinch') {\n const [a, b] = touches();\n if (a && b && dirty) flushPinch(a.x, a.y, b.x, b.y);\n frame = requestAnimationFrame(tick);\n }\n };\n const ensureFrames = () => {\n if (!frame) frame = requestAnimationFrame(tick);\n };\n\n const enterPinch = () => {\n const [a, b] = touches();\n if (!a || !b) return;\n mode = 'pinch';\n lastApplied = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n lastSpan = Math.hypot(a.x - b.x, a.y - b.y);\n trail = [];\n dirty = false;\n ensureFrames();\n };\n\n const onLongPress = () => {\n longPressTimer = null;\n if (mode !== 'pending' || !downEvent || !sink) return;\n // The camera transaction closes; the tool owns the rest of this contact.\n end();\n mode = 'tool';\n touchToolGesture = true;\n sink.longPress(downEvent);\n };\n\n // ── listeners ─────────────────────────────────────────────────────────────\n const onDown = (e: PointerEvent) => {\n const kind = (e.pointerType || 'mouse') as StagePointerKind;\n if (kind === 'touch') lastTouchAt = performance.now();\n if (kind !== 'touch') {\n if (kind === 'mouse' && e.button !== 0) return;\n if (mode !== 'idle') return; // an active gesture owns the surface\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n if (sink) {\n // tool-first, exactly the pre-existing hub behavior\n mode = 'tool';\n sink.down(e, clickCount(e));\n } else {\n mode = 'mousedrag';\n begin();\n panId = e.pointerId;\n lastApplied = { x: e.clientX, y: e.clientY };\n ensureFrames();\n }\n return;\n }\n\n // touch\n switch (mode) {\n case 'idle': {\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n // Consent pre-flight — but a MOVING camera always catches first: while\n // content flies under the finger, the touch means \"stop\", never \"grab\n // whatever happens to pass beneath it\".\n const moving = host.cameraInMotion();\n if (!moving && sink?.claimsPoint?.(e)) {\n // A tool owns this contact from the first pixel (selected-annotation\n // drag, or an armed drawing tool). No camera transaction — this is\n // not a navigation gesture; a second finger converts it to a pinch\n // via the 'tool' branch (sink.cancel), exactly like a long-press.\n mode = 'tool';\n touchToolGesture = true;\n sink.down(e, 1);\n break;\n }\n mode = 'pending';\n downEvent = e;\n suppressTap = moving; // a catch, not a tap\n begin(true); // stops any fling/tween under the finger\n trail = [];\n pushSample(e.clientX, e.clientY);\n clearLongPress();\n if (sink && !suppressTap) longPressTimer = setTimeout(onLongPress, LONG_PRESS_MS);\n break;\n }\n case 'pending':\n case 'pan': {\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n clearLongPress();\n enterPinch();\n break;\n }\n case 'tool': {\n // A second finger during a TOUCH tool gesture cancels it into a pinch\n // (the platform convention). Mouse tool gestures ignore stray touches.\n if (!touchToolGesture) return;\n sink?.cancel(e);\n touchToolGesture = false;\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n begin(true);\n enterPinch();\n break;\n }\n case 'pinch':\n case 'mousedrag':\n return; // ignore extra contacts\n }\n };\n\n const onWindowMove = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n if (p.kind === 'touch') lastTouchAt = performance.now();\n p.x = e.clientX;\n p.y = e.clientY;\n switch (mode) {\n case 'pending': {\n pushSample(p.x, p.y);\n if (Math.hypot(p.x - p.downX, p.y - p.downY) > SLOP) {\n clearLongPress();\n mode = 'pan';\n panId = p.id;\n lastApplied = { x: p.x, y: p.y }; // absorb the slop, like a scroller\n ensureFrames();\n }\n break;\n }\n case 'pan':\n if (p.id !== panId) break;\n if (!panArmed) {\n // pinch-leftover contact. Inside the release-grace window the slop\n // base follows the finger — a fast release keeps moving and must\n // never accumulate distance; only a contact that OUTLIVES the\n // window can re-earn panning by crossing slop from where it\n // settled.\n const nowT = performance.now();\n if (nowT < panGraceUntil) {\n // a finger that SETTLES (speed drops) inside the window is a\n // continuation taking hold — end the grace early so a deliberate\n // pause-then-drag stays responsive\n const dt = nowT - graceSample.t;\n if (dt >= 8) {\n const speed = Math.hypot(p.x - graceSample.x, p.y - graceSample.y) / dt;\n graceSample = { t: nowT, x: p.x, y: p.y };\n if (speed < SETTLE_SPEED) panGraceUntil = 0;\n }\n p.downX = p.x;\n p.downY = p.y;\n break;\n }\n if (Math.hypot(p.x - p.downX, p.y - p.downY) <= SLOP) break;\n panArmed = true;\n lastApplied = { x: p.x, y: p.y }; // absorb, like any fresh pan\n trail = [];\n spanTrail = [];\n }\n pushSample(p.x, p.y);\n dirty = true;\n break;\n case 'pinch':\n // centroid samples are taken per applied FRAME (see flushPinch) —\n // per-event sampling here would zig-zag between the two fingers\n dirty = true;\n break;\n case 'tool':\n sink?.move(e);\n break;\n case 'mousedrag':\n if (p.id === panId) dirty = true;\n break;\n case 'idle':\n break;\n }\n };\n\n // Hover (cursor feedback) — only with no gesture in flight, and only from\n // the element itself, matching the previous adapters.\n const onHoverMove = (e: PointerEvent) => {\n if (mode === 'idle' && sink) sink.hover(e);\n };\n\n const backToSingleFinger = (): boolean => {\n const rest = touches();\n if (rest.length !== 1) return false;\n mode = 'pan';\n panId = rest[0].id;\n panArmed = false; // leftover contact re-earns pan-hood via slop\n panGraceUntil = performance.now() + PINCH_RELEASE_GRACE_MS;\n graceSample = { t: performance.now(), x: rest[0].x, y: rest[0].y };\n rest[0].downX = rest[0].x;\n rest[0].downY = rest[0].y;\n lastApplied = { x: rest[0].x, y: rest[0].y };\n trail = [];\n dirty = false;\n return true;\n };\n\n const onUp = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n if (p.kind === 'touch') lastTouchAt = performance.now();\n pointers.delete(e.pointerId);\n switch (mode) {\n case 'pending': {\n // Slop never exceeded, timer never fired: a tap (or a catch).\n clearLongPress();\n end();\n const now = performance.now();\n const isDouble =\n now - lastTapT <= DOUBLE_TAP_MS &&\n Math.hypot(e.clientX - lastTapX, e.clientY - lastTapY) <= DOUBLE_TAP_RADIUS;\n if (suppressTap) {\n lastTapT = 0; // a catch never counts toward a double-tap\n } else if (zoomGestures && isDouble) {\n lastTapT = 0;\n host.doubleTapZoom(vpt(e.clientX, e.clientY));\n } else {\n lastTapT = now;\n lastTapX = e.clientX;\n lastTapY = e.clientY;\n if (sink && downEvent) {\n // The whole click, delivered at release — tools see exactly the\n // down/up pair they would from a mouse.\n sink.down(downEvent, 1);\n sink.up(e);\n }\n }\n toIdle();\n break;\n }\n case 'pan': {\n if (!panArmed) {\n // The pinch's OTHER finger leaving: the gesture ends as a pinch.\n // No flush (release-rolls are noise). Glide is CHARACTER-gated: a\n // human fast release moves the true centroid (the lifting finger\n // flicks away), so centroid velocity alone lies — the glide fires\n // only when the centroid rate DOMINATES the span rate (a\n // two-finger pan), never on a zoom-dominant release.\n const now = performance.now();\n const v = computeReleaseVelocity(trail, now);\n const vs = computeReleaseVelocity(spanTrail, now);\n end(); // close the bracket first — endGesture owns the elastic settle\n if (v) {\n const speed = Math.hypot(v.vx, v.vy);\n if (speed >= FLING_MIN && speed > Math.abs(vs?.vx ?? 0)) host.fling(v.vx, v.vy);\n }\n toIdle();\n break;\n }\n flushPanTo(e.clientX, e.clientY); // apply the final sub-frame of travel\n pushSample(e.clientX, e.clientY);\n end();\n maybeFling();\n toIdle();\n break;\n }\n case 'pinch': {\n const rest = touches();\n if (rest.length === 1) {\n // The pinch ends AT ITS LAST COHERENT FRAME. No flush here: the\n // lifting finger's release position is fresh but the other one's is\n // stale (its move for this window may not have arrived), and a\n // centroid of two instants is fiction — in a fast pinch that skewed\n // write was the visible end-of-pinch hop, and its poisoned trail\n // sample the phantom fling. Only coherent finger-pairs write the\n // camera; the sub-frame remainder is discarded, as the platform\n // recognizers do. The remaining contact gets a DISARMED pan with\n // the centroid trail preserved and a release-grace window armed.\n mode = 'pan';\n panId = rest[0].id;\n panArmed = false;\n panGraceUntil = performance.now() + PINCH_RELEASE_GRACE_MS;\n graceSample = { t: performance.now(), x: rest[0].x, y: rest[0].y };\n rest[0].downX = rest[0].x;\n rest[0].downY = rest[0].y;\n lastApplied = { x: rest[0].x, y: rest[0].y };\n dirty = false;\n break;\n }\n end();\n maybeFling();\n toIdle();\n break;\n }\n case 'tool': {\n sink?.up(e);\n toIdle();\n break;\n }\n case 'mousedrag': {\n if (p.id !== panId) break;\n flushPanTo(e.clientX, e.clientY);\n end();\n toIdle();\n break;\n }\n case 'idle':\n break;\n }\n };\n\n const onCancel = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n pointers.delete(e.pointerId);\n if (mode === 'tool') {\n sink?.cancel(e);\n toIdle();\n return;\n }\n if (mode === 'pinch' && backToSingleFinger()) return;\n end();\n toIdle();\n };\n\n // Wheel is ambient navigation in BOTH modes: ctrl/meta zooms (classified per\n // input by the injected wheelZoomFactor), else scrolls. With zoom gestures\n // off, a zoom-wheel falls through to ordinary pan.\n const onWheel = (e: WheelEvent) => {\n e.preventDefault();\n if (zoomGestures && (e.ctrlKey || e.metaKey)) {\n host.zoomAround(vpt(e.clientX, e.clientY), wheelZoom(e));\n } else {\n const dx = e.shiftKey ? e.deltaY : e.deltaX;\n const dy = e.shiftKey ? e.deltaX : e.deltaY;\n host.panBy(-dx, -dy);\n }\n };\n\n // Safari's proprietary gesture events. On DESKTOP Safari they are the only\n // trace of a trackpad pinch — convert the absolute scale to per-event ratios.\n // On iOS they fire ALONGSIDE per-finger pointer events; there the pointer\n // path owns the pinch and these are preventDefault-ed only (a pinch over the\n // stage must never page-zoom Safari). The guard is live touch contacts.\n let lastScale = 1;\n const onGestureStart = (e: Event) => {\n e.preventDefault();\n lastScale = (e as unknown as { scale?: number }).scale ?? 1;\n };\n const onGestureChange = (e: Event) => {\n e.preventDefault();\n // iOS: the pointer path owns touch pinches — and a TRAILING gesturechange\n // can arrive after the last pointerup, so suppression keys on RECENT\n // touch activity, not just live contacts. Desktop trackpads have none.\n if (touches().length > 0 || performance.now() - lastTouchAt < 500) return;\n const g = e as unknown as { scale?: number; clientX: number; clientY: number };\n const scale = g.scale ?? 1;\n if (zoomGestures && scale > 0) {\n host.zoomAround(vpt(g.clientX, g.clientY), scale / lastScale);\n }\n lastScale = scale;\n };\n\n el.addEventListener('pointerdown', onDown);\n el.addEventListener('pointermove', onHoverMove);\n window.addEventListener('pointermove', onWindowMove);\n window.addEventListener('pointerup', onUp);\n window.addEventListener('pointercancel', onCancel);\n el.addEventListener('wheel', onWheel, { passive: false });\n const hasGestureEvents = 'GestureEvent' in window;\n if (hasGestureEvents) {\n el.addEventListener('gesturestart', onGestureStart);\n el.addEventListener('gesturechange', onGestureChange);\n el.addEventListener('gestureend', onGestureStart); // reset the base\n }\n\n return () => {\n el.removeEventListener('pointerdown', onDown);\n el.removeEventListener('pointermove', onHoverMove);\n window.removeEventListener('pointermove', onWindowMove);\n window.removeEventListener('pointerup', onUp);\n window.removeEventListener('pointercancel', onCancel);\n el.removeEventListener('wheel', onWheel);\n if (hasGestureEvents) {\n el.removeEventListener('gesturestart', onGestureStart);\n el.removeEventListener('gesturechange', onGestureChange);\n el.removeEventListener('gestureend', onGestureStart);\n }\n clearLongPress();\n stopFrames();\n end(); // balance an open transaction on unmount\n };\n}\n","/**\n * Selection-handle DOM binding — the listener mechanics of dragging one\n * handle element, shared by every framework adapter (the policy — geometry\n * and the drag session — lives in `@embedpdf/plugin-selection`'s pure\n * `handles` module; this file never imports it, per the layering law:\n * structural interfaces only).\n *\n * The load-bearing subtlety this module owns: the pointer-DOWN shield must be\n * a NATIVE listener. The stage's gesture controller listens natively on the\n * container, so a framework-synthetic stopPropagation (React runs its\n * handlers at the root, AFTER the container's native ones) would be too late\n * — the stage would already be panning underneath the handle drag. Once the\n * down is captured here, movement tracks by CLIENT DELTAS from the handle's\n * own base point: no DOM geometry reads, and the client↔overlay conversion\n * cancels out.\n */\n\ninterface SurfacePoint {\n x: number;\n y: number;\n}\n\n/** The armed drag the adapter hands back — plugin-selection's\n * `SelectionHandleDragSession` satisfies it. */\nexport interface SelectionHandleSession {\n move(overlay: SurfacePoint): void;\n end(): void;\n}\n\nexport interface AttachSelectionHandleOptions {\n /**\n * Called at pointer-down to arm a drag: return the drag session plus the\n * handle's current BASE point in overlay space (the bar's midpoint — the\n * point the user grabbed). Return null to decline (endpoint vanished, page\n * not laid out) — the press then does nothing and nothing is captured.\n */\n arm(): { base: SurfacePoint; session: SelectionHandleSession } | null;\n}\n\n/** Bind one handle element. Returns the detach fn. */\nexport function attachSelectionHandle(\n el: HTMLElement,\n options: AttachSelectionHandleOptions,\n): () => void {\n let active: {\n session: SelectionHandleSession;\n base: SurfacePoint;\n baseClient: SurfacePoint;\n pointerId: number;\n } | null = null;\n\n const onMove = (e: PointerEvent) => {\n if (!active || e.pointerId !== active.pointerId) return;\n active.session.move({\n x: active.base.x + (e.clientX - active.baseClient.x),\n y: active.base.y + (e.clientY - active.baseClient.y),\n });\n };\n // Release and system-cancel settle identically: the selection's own commit\n // semantics live in the session; there is no half-state to revert here.\n const onUp = (e: PointerEvent) => {\n if (!active || e.pointerId !== active.pointerId) return;\n const s = active.session;\n active = null;\n s.end();\n };\n const onDown = (e: PointerEvent) => {\n if (active) return;\n const armed = options.arm();\n if (!armed) return;\n e.preventDefault();\n e.stopPropagation(); // native: fires BEFORE the stage controller's listener\n try {\n el.setPointerCapture(e.pointerId);\n } catch {\n // best-effort, like the scrollbar: an already-released pointer\n // (pen/touch races, synthetic events in tests) throws — the drag must\n // still arm, and the window-less capture path still delivers to `el`.\n }\n active = {\n session: armed.session,\n base: armed.base,\n baseClient: { x: e.clientX, y: e.clientY },\n pointerId: e.pointerId,\n };\n };\n\n el.addEventListener('pointerdown', onDown);\n el.addEventListener('pointermove', onMove);\n el.addEventListener('pointerup', onUp);\n el.addEventListener('pointercancel', onUp);\n return () => {\n el.removeEventListener('pointerdown', onDown);\n el.removeEventListener('pointermove', onMove);\n el.removeEventListener('pointerup', onUp);\n el.removeEventListener('pointercancel', onUp);\n active = null;\n };\n}\n","/**\n * Stage surface binding — everything DOM about attaching one element to one\n * stage lens, shared by every framework adapter (React, Angular, …) so the\n * binding can never drift between them:\n *\n * • ResizeObserver → `setViewport` (the shell only reports; placement is the\n * stage plugin's job)\n * • device-pixel-ratio observation → `setDevicePixelRatio` (dppx changes\n * re-subscribe, since the media query's value itself moves)\n * • pointer-sample normalization — the ONE place a PointerEvent becomes a\n * page-resolved, source-stamped sample for the interaction hub\n * • the gesture controller (wheel, Safari trackpad gestures, synthesized\n * touch physics) from `./stage-gestures`\n *\n * The framework adapters keep only their reactive glue: refs/effects, page\n * surfaces, cursor-style subscription, overlay slots.\n *\n * Dependency note: like the gesture controller, this module speaks to the\n * stage and the hub through STRUCTURAL interfaces ({@link StageSurfaceHost},\n * {@link StageSurfaceHub}) — satisfied by `StageCapability` and\n * `InteractionCapability`, imported by neither. @embedpdf/web stays free of\n * plugin imports, per the layering law.\n *\n * Browser-only by nature (ResizeObserver, matchMedia): call from a mounted\n * effect / browser platform check, exactly like the gesture controller.\n */\nimport { createStageGestureController } from './stage-gestures';\nimport type { StageGestureHost, StageGestureSink, StageWheelSample } from './stage-gestures';\n\ninterface SurfacePoint {\n x: number;\n y: number;\n}\n\n/** What the binding needs from the stage — `StageCapability` satisfies it. */\nexport interface StageSurfaceHost extends StageGestureHost {\n setViewport(size: { width: number; height: number }): void;\n setDevicePixelRatio(ratio: number): void;\n /** Viewport point → the page under it (with per-page display context), or null over a gap. */\n pageAt(screen: SurfacePoint): {\n pon: number;\n point: SurfacePoint;\n scale?: number;\n rotation?: 0 | 90 | 180 | 270;\n zoom?: number;\n } | null;\n /** Viewport point → a SPECIFIC page's content space, unclamped (frame-stable projection). */\n pointOnPage(pon: number, screen: SurfacePoint): SurfacePoint | null;\n}\n\n/**\n * The page-resolved sample the binding hands the hub — structurally a\n * `PointerSample` (plugin-interaction). `source` carries the lens identity so\n * the hub can route lens-scoped handlers; with several stages on one document,\n * a drag on one lens must never be captured by another lens's handler.\n */\nexport interface StageSurfaceSample {\n phase: 'down' | 'move' | 'up' | 'cancel';\n viewport: SurfacePoint;\n page?: {\n pon: number;\n point: SurfacePoint;\n scale?: number;\n rotation?: 0 | 90 | 180 | 270;\n zoom?: number;\n };\n project: (pon: number) => SurfacePoint | null;\n modifiers: { shift: boolean; alt: boolean; ctrl: boolean; meta: boolean };\n clickCount: number;\n pointerType: 'mouse' | 'pen' | 'touch';\n gesture?: 'long-press';\n source?: string;\n}\n\n/** What the binding needs from the interaction hub — `InteractionCapability` satisfies it. */\nexport interface StageSurfaceHub {\n dispatch(sample: StageSurfaceSample): void;\n activeTool(): { touchDirect?: boolean };\n wouldClaimTouch(sample: StageSurfaceSample): boolean;\n}\n\nexport interface StageSurfaceOptions {\n /** Route pointer input to the interaction hub (page-resolved samples) instead\n * of built-in drag-to-pan. Omit/null for a hub-less stage. */\n hub?: StageSurfaceHub | null;\n /** The lens identity stamped on every sample (`StageCapability.lensId()`),\n * so lens-scoped handlers only see their own stage's input. */\n source?: string;\n /** Ambient zoom gestures (see {@link StageGestureOptions}). Default true. */\n zoomGestures?: boolean;\n /** Override the wheel → zoom-factor classifier (defaults to `./wheel`). */\n wheelZoomFactor?: (sample: StageWheelSample) => number;\n}\n\n/** Attach the full surface binding. Returns the detach fn. */\nexport function createStageSurface(\n el: HTMLElement,\n stage: StageSurfaceHost,\n options: StageSurfaceOptions = {},\n): () => void {\n const hub = options.hub ?? null;\n const cleanups: Array<() => void> = [];\n\n // Only report the viewport size. Initial placement (home) is the stage\n // plugin's job — it places when it first learns a real size (and a\n // higher-priority initial-view provider can override). The shell stays dumb.\n const setViewport = () => stage.setViewport({ width: el.clientWidth, height: el.clientHeight });\n const ro = new ResizeObserver(setViewport);\n ro.observe(el);\n setViewport();\n cleanups.push(() => ro.disconnect());\n\n // Report the device pixel ratio so page transforms render crisp. dppx changes\n // (browser zoom, dragging between monitors) fire the media query;\n // re-subscribe each time since the query value itself moves.\n let mq: MediaQueryList | null = null;\n const reportDpr = () => {\n stage.setDevicePixelRatio(window.devicePixelRatio || 1);\n mq?.removeEventListener('change', reportDpr);\n mq = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);\n mq.addEventListener('change', reportDpr);\n };\n reportDpr();\n cleanups.push(() => mq?.removeEventListener('change', reportDpr));\n\n // The sink is the hub bridge: it converts events to page-resolved samples\n // (`pageAt` per event, so a drag can cross pages) and stamps the lens source.\n const sampleOf = (\n phase: StageSurfaceSample['phase'],\n e: PointerEvent,\n clickCount = 1,\n gesture?: StageSurfaceSample['gesture'],\n ): StageSurfaceSample => {\n const r = el.getBoundingClientRect();\n const viewport = { x: e.clientX - r.left, y: e.clientY - r.top };\n return {\n phase,\n viewport,\n page: stage.pageAt(viewport) ?? undefined,\n // Page-anchored gestures (annotation move/resize) track the origin\n // page's frame through this even when the cursor is off that page.\n project: (pon) => stage.pointOnPage(pon, viewport),\n modifiers: { shift: e.shiftKey, alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey },\n clickCount,\n pointerType: (e.pointerType || 'mouse') as StageSurfaceSample['pointerType'],\n ...(gesture ? { gesture } : {}),\n ...(options.source ? { source: options.source } : {}),\n };\n };\n const forward = (\n phase: StageSurfaceSample['phase'],\n e: PointerEvent,\n clickCount = 1,\n gesture?: StageSurfaceSample['gesture'],\n ) => {\n hub?.dispatch(sampleOf(phase, e, clickCount, gesture));\n };\n const sink: StageGestureSink | null = hub\n ? {\n down: (e, clickCount) => forward('down', e, clickCount),\n move: (e) => forward('move', e),\n up: (e) => forward('up', e),\n cancel: (e) => forward('cancel', e),\n hover: (e) => forward('move', e), // no owner → the hub routes to onHover\n // Touch long-press = a word-select down (clickCount 2 keeps the\n // word-selection contract; the `gesture` marker is the honest\n // long-press signal for haptics/pickup handlers).\n longPress: (e) => forward('down', e, 2, 'long-press'),\n // Touch consent: an armed drawing/markup tool takes fingers wholesale;\n // otherwise per-point claims (a selected annotation's body or handles)\n // decide. A pure pre-flight — nothing captures.\n claimsPoint: (e) =>\n !!hub.activeTool().touchDirect || hub.wouldClaimTouch(sampleOf('down', e)),\n }\n : null;\n cleanups.push(\n createStageGestureController(el, stage, {\n zoomGestures: options.zoomGestures,\n wheelZoomFactor: options.wheelZoomFactor,\n sink,\n }),\n );\n\n return () => {\n for (const fn of cleanups) fn();\n };\n}\n","/**\n * A bytes-by-id store over IndexedDB — the browser's answer to \"keep these\n * blobs for me\". Structurally the store port a plugin declares for its own\n * persistence (e.g. `StampLibraryStore` in `@embedpdf/plugin-stamp`); declared\n * here as a twin so this package stays plugin-free, per the layering law.\n *\n * IndexedDB rather than localStorage: a library or an image is binary, and a\n * localStorage string store caps at a few MB. One database, one object store,\n * keys are the caller's ids, values are the bytes as stored.\n */\nexport interface ByteStore {\n list(): Promise<Array<{ id: string; bytes: Uint8Array }>>;\n put(id: string, bytes: Uint8Array): Promise<void>;\n delete(id: string): Promise<void>;\n}\n\nexport interface IndexedDbByteStoreOptions {\n /** Object store name inside the database. Default `'bytes'`. */\n storeName?: string;\n}\n\n/** Open (creating on first use) one IndexedDB object store keyed by id. */\nexport function indexedDbByteStore(\n dbName: string,\n opts: IndexedDbByteStoreOptions = {},\n): ByteStore {\n const storeName = opts.storeName ?? 'bytes';\n const open = (): Promise<IDBDatabase> =>\n new Promise((resolve, reject) => {\n const request = indexedDB.open(dbName, 1);\n request.onupgradeneeded = () => {\n if (!request.result.objectStoreNames.contains(storeName)) {\n request.result.createObjectStore(storeName);\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error('indexedDB.open failed'));\n });\n const run = async <T>(\n mode: IDBTransactionMode,\n body: (store: IDBObjectStore) => IDBRequest<T>,\n ): Promise<T> => {\n const db = await open();\n try {\n return await new Promise<T>((resolve, reject) => {\n const request = body(db.transaction(storeName, mode).objectStore(storeName));\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error('indexedDB request failed'));\n });\n } finally {\n db.close();\n }\n };\n return {\n list: async () => {\n const keys = await run<IDBValidKey[]>('readonly', (store) => store.getAllKeys());\n const values = await run<Uint8Array[]>('readonly', (store) => store.getAll());\n return keys.map((key, i) => ({ id: String(key), bytes: new Uint8Array(values[i]) }));\n },\n put: (id, bytes) =>\n run('readwrite', (store) => store.put(new Uint8Array(bytes), id)).then(() => undefined),\n delete: (id) => run('readwrite', (store) => store.delete(id)).then(() => undefined),\n };\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -590,5 +590,31 @@ interface WheelSample {
|
|
|
590
590
|
/** Zoom factor for a ctrl/cmd wheel event (the caller decides zoom vs pan). */
|
|
591
591
|
declare function wheelZoomFactor(e: WheelSample): number;
|
|
592
592
|
//#endregion
|
|
593
|
-
|
|
593
|
+
//#region src/byte-store.d.ts
|
|
594
|
+
/**
|
|
595
|
+
* A bytes-by-id store over IndexedDB — the browser's answer to "keep these
|
|
596
|
+
* blobs for me". Structurally the store port a plugin declares for its own
|
|
597
|
+
* persistence (e.g. `StampLibraryStore` in `@embedpdf/plugin-stamp`); declared
|
|
598
|
+
* here as a twin so this package stays plugin-free, per the layering law.
|
|
599
|
+
*
|
|
600
|
+
* IndexedDB rather than localStorage: a library or an image is binary, and a
|
|
601
|
+
* localStorage string store caps at a few MB. One database, one object store,
|
|
602
|
+
* keys are the caller's ids, values are the bytes as stored.
|
|
603
|
+
*/
|
|
604
|
+
interface ByteStore {
|
|
605
|
+
list(): Promise<Array<{
|
|
606
|
+
id: string;
|
|
607
|
+
bytes: Uint8Array;
|
|
608
|
+
}>>;
|
|
609
|
+
put(id: string, bytes: Uint8Array): Promise<void>;
|
|
610
|
+
delete(id: string): Promise<void>;
|
|
611
|
+
}
|
|
612
|
+
interface IndexedDbByteStoreOptions {
|
|
613
|
+
/** Object store name inside the database. Default `'bytes'`. */
|
|
614
|
+
storeName?: string;
|
|
615
|
+
}
|
|
616
|
+
/** Open (creating on first use) one IndexedDB object store keyed by id. */
|
|
617
|
+
declare function indexedDbByteStore(dbName: string, opts?: IndexedDbByteStoreOptions): ByteStore;
|
|
618
|
+
//#endregion
|
|
619
|
+
export { type ActionsUiAdapterShape, type ActionsUiEffectContext, type ActionsUiOrigin, type AnchorTarget, type AnchoredPlacement, type AnchoredPoint, type AnchoredPosition, type AnchoredRect, type AttachSelectionHandleOptions, type ByteStore, type ClipboardSelectionSource, type DefaultActionsUiAdapterOptions, type IndexedDbByteStoreOptions, type ObjectUrlImageSource, type PaintedImageCallbacks, type PickFileOptions, type SelectionClipboardOptions, type SelectionHandleSession, type StageGestureHost, type StageGestureOptions, type StageGestureSink, type StagePointerKind, type StageSurfaceHost, type StageSurfaceHub, type StageSurfaceOptions, type StageSurfaceSample, type StageWheelSample, type SvgCursorOptions, type ViewProjector, type WebPlatformFeedback, type WheelSample, attachSelectionHandle, bindPaintedImage, computeReleaseVelocity, copySelection, createDefaultActionsUiAdapter, createStageGestureController, createStageSurface, indexedDbByteStore, observeClientGeometry, pickFile, pickImageFile, positionAnchoredRect, projectAnchoredTarget, sanitizeExternalUri, saveFile, svgCursor, vibrationFeedback, wheelZoomFactor, wireSelectionClipboard, wkFeedback };
|
|
594
620
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -590,5 +590,31 @@ interface WheelSample {
|
|
|
590
590
|
/** Zoom factor for a ctrl/cmd wheel event (the caller decides zoom vs pan). */
|
|
591
591
|
declare function wheelZoomFactor(e: WheelSample): number;
|
|
592
592
|
//#endregion
|
|
593
|
-
|
|
593
|
+
//#region src/byte-store.d.ts
|
|
594
|
+
/**
|
|
595
|
+
* A bytes-by-id store over IndexedDB — the browser's answer to "keep these
|
|
596
|
+
* blobs for me". Structurally the store port a plugin declares for its own
|
|
597
|
+
* persistence (e.g. `StampLibraryStore` in `@embedpdf/plugin-stamp`); declared
|
|
598
|
+
* here as a twin so this package stays plugin-free, per the layering law.
|
|
599
|
+
*
|
|
600
|
+
* IndexedDB rather than localStorage: a library or an image is binary, and a
|
|
601
|
+
* localStorage string store caps at a few MB. One database, one object store,
|
|
602
|
+
* keys are the caller's ids, values are the bytes as stored.
|
|
603
|
+
*/
|
|
604
|
+
interface ByteStore {
|
|
605
|
+
list(): Promise<Array<{
|
|
606
|
+
id: string;
|
|
607
|
+
bytes: Uint8Array;
|
|
608
|
+
}>>;
|
|
609
|
+
put(id: string, bytes: Uint8Array): Promise<void>;
|
|
610
|
+
delete(id: string): Promise<void>;
|
|
611
|
+
}
|
|
612
|
+
interface IndexedDbByteStoreOptions {
|
|
613
|
+
/** Object store name inside the database. Default `'bytes'`. */
|
|
614
|
+
storeName?: string;
|
|
615
|
+
}
|
|
616
|
+
/** Open (creating on first use) one IndexedDB object store keyed by id. */
|
|
617
|
+
declare function indexedDbByteStore(dbName: string, opts?: IndexedDbByteStoreOptions): ByteStore;
|
|
618
|
+
//#endregion
|
|
619
|
+
export { type ActionsUiAdapterShape, type ActionsUiEffectContext, type ActionsUiOrigin, type AnchorTarget, type AnchoredPlacement, type AnchoredPoint, type AnchoredPosition, type AnchoredRect, type AttachSelectionHandleOptions, type ByteStore, type ClipboardSelectionSource, type DefaultActionsUiAdapterOptions, type IndexedDbByteStoreOptions, type ObjectUrlImageSource, type PaintedImageCallbacks, type PickFileOptions, type SelectionClipboardOptions, type SelectionHandleSession, type StageGestureHost, type StageGestureOptions, type StageGestureSink, type StagePointerKind, type StageSurfaceHost, type StageSurfaceHub, type StageSurfaceOptions, type StageSurfaceSample, type StageWheelSample, type SvgCursorOptions, type ViewProjector, type WebPlatformFeedback, type WheelSample, attachSelectionHandle, bindPaintedImage, computeReleaseVelocity, copySelection, createDefaultActionsUiAdapter, createStageGestureController, createStageSurface, indexedDbByteStore, observeClientGeometry, pickFile, pickImageFile, positionAnchoredRect, projectAnchoredTarget, sanitizeExternalUri, saveFile, svgCursor, vibrationFeedback, wheelZoomFactor, wireSelectionClipboard, wkFeedback };
|
|
594
620
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1119,6 +1119,44 @@ function createStageSurface(el, stage, options = {}) {
|
|
|
1119
1119
|
};
|
|
1120
1120
|
}
|
|
1121
1121
|
//#endregion
|
|
1122
|
-
|
|
1122
|
+
//#region src/byte-store.ts
|
|
1123
|
+
/** Open (creating on first use) one IndexedDB object store keyed by id. */
|
|
1124
|
+
function indexedDbByteStore(dbName, opts = {}) {
|
|
1125
|
+
const storeName = opts.storeName ?? "bytes";
|
|
1126
|
+
const open = () => new Promise((resolve, reject) => {
|
|
1127
|
+
const request = indexedDB.open(dbName, 1);
|
|
1128
|
+
request.onupgradeneeded = () => {
|
|
1129
|
+
if (!request.result.objectStoreNames.contains(storeName)) request.result.createObjectStore(storeName);
|
|
1130
|
+
};
|
|
1131
|
+
request.onsuccess = () => resolve(request.result);
|
|
1132
|
+
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("indexedDB.open failed"));
|
|
1133
|
+
});
|
|
1134
|
+
const run = async (mode, body) => {
|
|
1135
|
+
const db = await open();
|
|
1136
|
+
try {
|
|
1137
|
+
return await new Promise((resolve, reject) => {
|
|
1138
|
+
const request = body(db.transaction(storeName, mode).objectStore(storeName));
|
|
1139
|
+
request.onsuccess = () => resolve(request.result);
|
|
1140
|
+
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("indexedDB request failed"));
|
|
1141
|
+
});
|
|
1142
|
+
} finally {
|
|
1143
|
+
db.close();
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
return {
|
|
1147
|
+
list: async () => {
|
|
1148
|
+
const keys = await run("readonly", (store) => store.getAllKeys());
|
|
1149
|
+
const values = await run("readonly", (store) => store.getAll());
|
|
1150
|
+
return keys.map((key, i) => ({
|
|
1151
|
+
id: String(key),
|
|
1152
|
+
bytes: new Uint8Array(values[i])
|
|
1153
|
+
}));
|
|
1154
|
+
},
|
|
1155
|
+
put: (id, bytes) => run("readwrite", (store) => store.put(new Uint8Array(bytes), id)).then(() => void 0),
|
|
1156
|
+
delete: (id) => run("readwrite", (store) => store.delete(id)).then(() => void 0)
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
//#endregion
|
|
1160
|
+
export { attachSelectionHandle, bindPaintedImage, computeReleaseVelocity, copySelection, createDefaultActionsUiAdapter, createStageGestureController, createStageSurface, indexedDbByteStore, observeClientGeometry, pickFile, pickImageFile, positionAnchoredRect, projectAnchoredTarget, sanitizeExternalUri, saveFile, svgCursor, vibrationFeedback, wheelZoomFactor, wireSelectionClipboard, wkFeedback };
|
|
1123
1161
|
|
|
1124
1162
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["w"],"sources":["../src/file-picker.ts","../src/clipboard.ts","../src/anchored-position.ts","../src/cursor.ts","../src/external-uri.ts","../src/actions-ui.ts","../src/painted-image.ts","../src/feedback.ts","../src/wheel.ts","../src/stage-gestures.ts","../src/selection-handles.ts","../src/stage-surface.ts"],"sourcesContent":["/**\n * A one-shot file dialog. Opens a throwaway `<input type=file>`, resolves the\n * chosen `File`, or `null` when the dialog is dismissed. MUST be called\n * synchronously from a user gesture (a click) or the browser refuses to open it.\n *\n * A `File` is a `Blob`, which is a `BinarySource` everywhere in the stack, so the\n * caller can hand the result straight to the engine — this module stays a pure\n * DOM utility with no EmbedPDF types.\n */\nconst IMAGE_ACCEPT = 'image/png,image/jpeg,application/pdf';\n\nexport interface PickFileOptions {\n /** The dialog's `accept` filter. Defaults to PNG / JPEG / PDF. */\n accept?: string;\n /** Allow multiple selection (returns the first file). Defaults to false. */\n multiple?: boolean;\n}\n\n/** Open the file dialog; resolve the picked file, or null if cancelled. */\nexport function pickImageFile(options: PickFileOptions = {}): Promise<File | null> {\n const { accept = IMAGE_ACCEPT, multiple = false } = options;\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n input.multiple = multiple;\n input.style.display = 'none';\n\n let settled = false;\n const finish = (value: File | null): void => {\n if (settled) return;\n settled = true;\n window.removeEventListener('focus', onFocus);\n input.remove();\n resolve(value);\n };\n\n // A window refocus means the dialog closed. Give a queued `change` a tick to\n // win, then treat a bare refocus as a cancel — the fallback for browsers that\n // predate the `cancel` event.\n const onFocus = (): void => {\n setTimeout(() => finish(null), 300);\n };\n\n input.addEventListener('change', () => finish(input.files?.[0] ?? null), { once: true });\n input.addEventListener('cancel', () => finish(null), { once: true });\n window.addEventListener('focus', onFocus, { once: true });\n\n document.body.appendChild(input);\n input.click();\n });\n}\n\n/** The same dialog with NO type filter — any file (attachments). */\nexport const pickFile = (options: PickFileOptions = {}): Promise<File | null> =>\n pickImageFile({ accept: '*/*', ...options });\n\n/**\n * Hand bytes to the user as a browser download (a one-shot `<a download>`).\n * The DOM half of \"download this attachment\" — pair with the annotation\n * capability's `downloadAttachment` or the document `attachments.download`.\n */\nexport function saveFile(bytes: Uint8Array | Blob, name: string, mimeType?: string): void {\n const blob =\n bytes instanceof Blob\n ? bytes\n : new Blob([bytes as BlobPart], { type: mimeType ?? 'application/octet-stream' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = name;\n a.style.display = 'none';\n document.body.appendChild(a);\n a.click();\n a.remove();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n","/**\n * Clipboard wiring for text selection — the environmental half of copy.\n *\n * The selection plugin is deliberately DOM-free: its public capability ends\n * at `readText()` (data). Everything that touches `navigator.clipboard` or\n * the native `copy` event lives HERE, structurally typed so this module\n * stays a pure DOM utility with no EmbedPDF dependencies (the selection\n * capability satisfies {@link ClipboardSelectionSource} as-is).\n */\n\n/** The slice of the selection capability clipboard wiring needs. */\nexport interface ClipboardSelectionSource {\n hasSelection(): boolean;\n canCopy(): boolean;\n readText(): Promise<string>;\n onChange(cb: () => void): () => void;\n onCommit(cb: () => void): () => void;\n}\n\nexport interface SelectionClipboardOptions {\n /**\n * Prefetch the selected text when the selection settles (default true).\n * This is what makes the NATIVE copy path work: a `copy` event handler\n * must call `clipboardData.setData` synchronously — awaiting a page-text\n * read inside it is too late — and it also keeps the async path instant.\n * The fetch is one versioned, cached, permission-gated read per page;\n * pass false to make copying strictly on-demand.\n */\n prefetch?: boolean;\n /** Event target to listen on. Defaults to `document`. */\n target?: Document;\n}\n\nconst PREFETCH_DEBOUNCE_MS = 150;\n\n/**\n * Wire clipboard copy for a selection. Two delivery paths, both fed by the\n * prefetched text:\n *\n * - the native `copy` event (menu Edit→Copy, or ctrl/cmd+C while the page\n * has a DOM selection or focused editable — e.g. the viewer shell's\n * focus sink): answered SYNCHRONOUSLY from the cache;\n * - ctrl/cmd+C with no DOM selection (canvas-rendered viewers usually\n * have none, and browsers don't dispatch `copy` then): a keydown\n * fallback writes via the async Clipboard API inside the keystroke's\n * user-activation window.\n *\n * Returns the unwire function. Mount once per document view (not per page).\n */\nexport function wireSelectionClipboard(\n selection: ClipboardSelectionSource,\n options: SelectionClipboardOptions = {},\n): () => void {\n const target = options.target ?? document;\n const prefetch = options.prefetch ?? true;\n let cached: string | null = null;\n let generation = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const refresh = (): void => {\n cached = null;\n if (!prefetch || !selection.hasSelection() || !selection.canCopy()) return;\n const gen = ++generation;\n selection.readText().then(\n (text) => {\n if (gen === generation) cached = text;\n },\n () => {}, // denied / failed — the async path will surface it if used\n );\n };\n\n // Commit (gesture end) refreshes immediately; change (drag, programmatic\n // select) debounces so a drag doesn't fetch per pointer move.\n const offCommit = selection.onCommit(() => {\n if (timer) clearTimeout(timer);\n timer = null;\n refresh();\n });\n const offChange = selection.onChange(() => {\n generation++;\n cached = null;\n if (timer) clearTimeout(timer);\n timer = setTimeout(refresh, PREFETCH_DEBOUNCE_MS);\n });\n\n const onCopy = (e: ClipboardEvent): void => {\n if (cached == null || cached === '' || !selection.hasSelection()) return;\n e.clipboardData?.setData('text/plain', cached);\n e.preventDefault();\n };\n\n const onKeyDown = (e: KeyboardEvent): void => {\n if (!(e.metaKey || e.ctrlKey) || e.key.toLowerCase() !== 'c' || e.shiftKey || e.altKey) {\n return;\n }\n if (!selection.hasSelection() || !selection.canCopy()) return;\n // A live DOM selection means the browser will dispatch `copy` itself and\n // our synchronous handler answers — don't double-write.\n const dom = target.getSelection?.();\n if (dom && !dom.isCollapsed) return;\n void copySelection(selection); // instant when prefetched; still inside user activation\n };\n\n target.addEventListener('copy', onCopy);\n target.addEventListener('keydown', onKeyDown);\n return () => {\n offCommit();\n offChange();\n if (timer) clearTimeout(timer);\n target.removeEventListener('copy', onCopy);\n target.removeEventListener('keydown', onKeyDown);\n };\n}\n\n/**\n * Toolbar/menu path: read the selected text and write it to the clipboard\n * via the async Clipboard API. Call from a user-gesture handler (browsers\n * require a secure context + user activation for clipboard writes). Resolves\n * with what was copied ('' when nothing was — nothing is written then, so an\n * empty selection never clobbers the user's clipboard).\n */\nexport async function copySelection(selection: ClipboardSelectionSource): Promise<string> {\n const text = await selection.readText();\n if (text !== '') await navigator.clipboard.writeText(text);\n return text;\n}\n","/**\n * Anchored-overlay geometry for UI floating over page content (selection\n * menus, draft menus, popovers): the projector SNAPSHOT contract, the\n * anchor shape, and the placement math. Pure values and pure functions over\n * structural rect/point shapes — no EmbedPDF types, no framework, no\n * reactive lifecycle — so every framework adapter (React, Vue, Svelte,\n * Angular) shares ONE implementation and binds it with its NATIVE\n * reactivity (context revision, signals, computed, $derived).\n *\n * The boundary laws:\n * - Placement policy lives HERE, above the projector seam: a projector\n * only knows geometry — never what is being positioned nor where it\n * prefers to sit.\n * - A projector describes the CURRENT projection; it never implements a\n * framework-style reactive lifecycle. State-driven changes (the Stage\n * camera) must reach consumers through the framework's own render\n * cycle so surface and overlay commit TOGETHER; only genuinely\n * browser-driven changes (a PageView moving because the document\n * scrolled) use an external listener ({@link observeClientGeometry}).\n */\n\nexport interface AnchoredRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface AnchoredPoint {\n x: number;\n y: number;\n}\n\nexport type AnchoredPlacement = 'top' | 'right' | 'bottom' | 'left';\n\n/** CSS-ready output: absolute/fixed `left`/`top` plus a centering transform. */\nexport interface AnchoredPosition {\n left: number;\n top: number;\n transform: string;\n}\n\n/**\n * Place an upright element around `box` (screen px). `avoid` is a screen\n * point the element must clear (e.g. the rotate knob): the element extends\n * ONLY the edge it sits on, and ONLY when the point protrudes past that\n * edge — so it clears the obstacle without ever shifting off-centre on the\n * other axis. When the point is on another side (e.g. a 90° shape, knob at\n * mid-height for a `top` placement) the edge is untouched and the element\n * stays centred on `box`.\n */\n/** What anchored UI attaches to: a content-space rect on a page, plus\n * optional content-space points the UI must clear (e.g. a rotate knob).\n * Structural — plugin anchor reads satisfy it without importing this\n * package. */\nexport interface AnchorTarget {\n pon: number;\n bounds: AnchoredRect;\n avoid?: AnchoredPoint[];\n}\n\n/**\n * A page surface's PROJECTION SNAPSHOT: how a content-space rect on a page\n * becomes screen coordinates, right now. Provided by `<Stage>`\n * (camera-driven, pure state, no DOM reads) and `<PageView>` (DOM-measured).\n * Deliberately NO subscribe here — when projection changes is a framework\n * binding concern (see the module doc), not part of the snapshot.\n */\nexport interface ViewProjector {\n /**\n * Which coordinate space `toScreen` speaks, and therefore how anchored\n * content mounts:\n * - 'overlay': coords are relative to the surface's own positioned\n * container → rendered in place, position:absolute. (Stage)\n * - 'client': coords are viewport px → portalled to the document body,\n * position:fixed, so no ancestor overflow can clip. (PageView)\n */\n space: 'overlay' | 'client';\n /** Content-space rect on a page → coords in `space`. Null: not projectable\n * right now (page not shown / not measurable yet). */\n toScreen(pon: number, rect: AnchoredRect): AnchoredRect | null;\n toScreenPoint(pon: number, at: AnchoredPoint): AnchoredPoint | null;\n /** The page's live view facts (for anchor reads that need them — e.g. the\n * screen-constant rotate-knob stalk), or null when the page isn't shown. */\n viewEnv(pon: number): {\n scale: number;\n rotation: 0 | 90 | 180 | 270;\n zoom: number;\n } | null;\n}\n\n/**\n * THE anchored projection: anchor → screen position, in one pure call —\n * projection, avoid-point transform, and the shared placement policy.\n * Frameworks recompute this inside their native reactive primitive\n * (a React render, a Vue `computed`, an Angular signal, a Svelte\n * `$derived`); nothing here schedules anything.\n */\nexport function projectAnchoredTarget(\n projector: ViewProjector,\n anchor: AnchorTarget,\n placement: AnchoredPlacement,\n gap: number,\n): AnchoredPosition | null {\n const box = projector.toScreen(anchor.pon, anchor.bounds);\n if (!box) return null;\n const avoid = anchor.avoid?.length\n ? projector.toScreenPoint(anchor.pon, anchor.avoid[0])\n : null;\n return positionAnchoredRect(box, placement, gap, avoid);\n}\n\n/**\n * The one genuinely BROWSER-driven invalidation: an element in normal\n * document flow moves when the document scrolls or the window resizes —\n * no state change announces it. `<PageView>`'s binding registers this;\n * the Stage never does (its camera is state, delivered through render).\n */\nexport function observeClientGeometry(callback: () => void): () => void {\n window.addEventListener('scroll', callback, true);\n window.addEventListener('resize', callback);\n return () => {\n window.removeEventListener('scroll', callback, true);\n window.removeEventListener('resize', callback);\n };\n}\n\nexport function positionAnchoredRect(\n box: AnchoredRect,\n placement: AnchoredPlacement,\n gap: number,\n avoid?: AnchoredPoint | null,\n): AnchoredPosition {\n const cx = box.x + box.width / 2;\n const cy = box.y + box.height / 2;\n\n switch (placement) {\n case 'bottom': {\n const edge = Math.max(box.y + box.height, avoid ? avoid.y : -Infinity);\n return { left: cx, top: edge + gap, transform: 'translate(-50%, 0)' };\n }\n case 'left': {\n const edge = Math.min(box.x, avoid ? avoid.x : Infinity);\n return { left: edge - gap, top: cy, transform: 'translate(-100%, -50%)' };\n }\n case 'right': {\n const edge = Math.max(box.x + box.width, avoid ? avoid.x : -Infinity);\n return { left: edge + gap, top: cy, transform: 'translate(0, -50%)' };\n }\n case 'top':\n default: {\n const edge = Math.min(box.y, avoid ? avoid.y : Infinity);\n return { left: cx, top: edge - gap, transform: 'translate(-50%, -100%)' };\n }\n }\n}\n","/**\n * SVG → CSS cursor string. The armed-tool indicator is the CURSOR itself (the\n * only zero-latency pointer-locked pixel the web has — the OS composites it),\n * so an app hands its toolbar icon here and assigns the result via the\n * interaction hub's `setToolCursor`.\n *\n * Browser rules baked into this module:\n * - The SVG must carry explicit `width`/`height` attributes (not just a\n * viewBox) or Chromium/WebKit ignore it — enforced here.\n * - The hard ceiling is 128×128 CSS px (bigger falls back to the keyword).\n * Above 32×32, Chromium temporarily resets the cursor to default while it\n * would overlap browser UI (anti-spoofing) — fine mid-viewport, so ~40px\n * compositions are common; stay ≤128.\n * - The keyword fallback covers browsers that refuse SVG cursor images.\n *\n * Pure string work with no EmbedPDF types — like the file picker, this module\n * is a plain web utility.\n */\n\nexport interface SvgCursorOptions {\n /** Complete `<svg>` markup, colors baked in (no `currentColor` — a cursor\n * image has no cascade to inherit from). */\n svg: string;\n /** The click point inside the image, CSS px from the top-left. Default 0,0\n * (arrow-style: the image's corner is the tip). */\n hotspot?: { x: number; y: number };\n /** Keyword cursor when the image can't render. Default `'crosshair'`. */\n fallback?: string;\n}\n\n/** Build the CSS cursor value: `url(\"data:image/svg+xml,…\") x y, fallback`. */\nexport function svgCursor({ svg, hotspot, fallback = 'crosshair' }: SvgCursorOptions): string {\n // Scan only the opening `<svg …>` tag (bounded slice) rather than running\n // `/<svg[^>]*\\swidth\\s*=/` over the whole string — the unbounded form is\n // quadratic on pathological inputs (many `<svg` fragments with no `>`),\n // while accepting exactly the same markup: `[^>]*` could never cross a\n // `>` either.\n const tagStart = svg.indexOf('<svg');\n const tagEnd = tagStart >= 0 ? svg.indexOf('>', tagStart) : -1;\n const openTag = tagEnd >= 0 ? svg.slice(tagStart, tagEnd + 1) : '';\n if (!/\\swidth\\s*=/.test(openTag) || !/\\sheight\\s*=/.test(openTag)) {\n throw new Error(\n '[web] svgCursor: the <svg> needs explicit width/height attributes — ' +\n 'browsers ignore cursor images without an intrinsic size',\n );\n }\n const uri = `data:image/svg+xml,${encodeURIComponent(svg)}`;\n const spot = hotspot ? ` ${Math.round(hotspot.x)} ${Math.round(hotspot.y)}` : '';\n return `url(\"${uri}\")${spot}, ${fallback}`;\n}\n","/**\n * External-URI hygiene for PDF link annotations — written ONCE here so every\n * framework adapter (react, vue, svelte, angular) shares the same policy.\n *\n * PDFs are untrusted input: a link's `/URI` can carry anything, including\n * `javascript:` payloads. The default allowlist covers what a document link\n * legitimately opens; everything else (javascript:, file:, data:, vbscript:,\n * custom app schemes) resolves to `null` and the adapters render the link\n * WITHOUT an href — activation still reports through the plugin, so an\n * embedder that needs a custom scheme can handle it in `onActivate`.\n */\nconst DEFAULT_ALLOWED_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:']);\n\n/**\n * Validate a link URI for opening in the browser. Returns the URI when its\n * scheme is allowed (scheme-relative and bare URIs resolve as https), else\n * `null`. Never throws — a malformed URI is just an unopenable one.\n */\nexport function sanitizeExternalUri(\n uri: string,\n allowedSchemes: Iterable<string> = DEFAULT_ALLOWED_SCHEMES,\n): string | null {\n const trimmed = uri.trim();\n if (!trimmed) return null;\n const allowed = new Set(\n [...allowedSchemes].map((s) => (s.endsWith(':') ? s.toLowerCase() : `${s.toLowerCase()}:`)),\n );\n try {\n // Base resolves scheme-relative (\"//host\") and bare (\"example.com/x\" is a\n // relative path → https) forms; an absolute scheme wins over the base.\n const url = new URL(trimmed, 'https://invalid.example/');\n return allowed.has(url.protocol) ? trimmed : null;\n } catch {\n return null;\n }\n}\n","/**\n * The DEFAULT actions UI adapter — the origin×phase visibility matrix plus\n * the browser fallbacks, written ONCE here so every framework binding\n * (react, angular, …) ships the SAME policy instead of forking it per\n * framework. Types are structural twins of `@embedpdf/plugin-actions`'\n * adapter contract (declared here so this package stays plugin-free, per\n * the layering law); assignability is checked where a binding installs it.\n *\n * The matrix (defaults only — an embedder override receives EVERY effect\n * with its context attached and decides for itself):\n * - URIs open through `sanitizeExternalUri` in a new tab (the dispatcher\n * already policy-gated the node; blocked schemes are dropped here).\n * - Print: hover/lifecycle-origin requests never open the dialog; user\n * requests fall back to `globalThis.print`. The `doc.print` AUTHORITY\n * gate is upstream in the plugin and not overridable.\n * - Alert: document-open nags (lifecycle origin, boot phase) never reach\n * `window.alert` — the boot-nag gap, closed.\n * - gotoPage: delegates to the binding-supplied navigator (the stage).\n */\nimport { sanitizeExternalUri } from './external-uri';\n\n/** Structural twin of plugin-actions' `ActionOrigin`. */\nexport type ActionsUiOrigin = 'user' | 'hover' | 'lifecycle';\n\n/** Structural twin of plugin-actions' `ActionUiContext`. */\nexport interface ActionsUiEffectContext {\n origin: ActionsUiOrigin;\n phase: 'boot' | 'user';\n}\n\n/** Structural twin of plugin-actions' `ActionUiAdapter`. */\nexport interface ActionsUiAdapterShape {\n openUri(uri: string, opts: { isMap: boolean; origin: ActionsUiOrigin }): void;\n print(opts?: ActionsUiEffectContext): void;\n alert?(message: string, opts: ActionsUiEffectContext & { icon: number; title?: string }): void;\n gotoPage?(page: number, opts: ActionsUiEffectContext): void;\n}\n\nexport interface DefaultActionsUiAdapterOptions {\n /**\n * Per-handler overrides. Pass a FUNCTION for late binding (a binding's\n * mutable handlers ref) — it is consulted on every effect, so a fresh\n * closure never needs a reinstall.\n */\n overrides?: Partial<ActionsUiAdapterShape> | (() => Partial<ActionsUiAdapterShape> | undefined);\n /** Navigation default for script `this.pageNum = n` requests. */\n goToPage?: (page: number) => void;\n}\n\nexport const createDefaultActionsUiAdapter = (\n options: DefaultActionsUiAdapterOptions = {},\n): ActionsUiAdapterShape => {\n const overridesOf = (): Partial<ActionsUiAdapterShape> | undefined =>\n typeof options.overrides === 'function' ? options.overrides() : options.overrides;\n return {\n openUri: (uri, opts) => {\n const current = overridesOf();\n if (current?.openUri) {\n current.openUri(uri, opts);\n return;\n }\n const href = sanitizeExternalUri(uri);\n if (href && typeof window !== 'undefined') {\n window.open(href, '_blank', 'noopener,noreferrer');\n }\n },\n print: (opts) => {\n const current = overridesOf();\n if (current?.print) current.print(opts);\n else if (opts && opts.origin !== 'user') {\n // Hover/lifecycle scripts never open the print dialog by default.\n } else if (typeof globalThis.print === 'function') globalThis.print();\n },\n alert: (message, opts) => {\n const current = overridesOf();\n if (current?.alert) current.alert(message, opts);\n else if (opts.origin === 'lifecycle' || opts.phase === 'boot') {\n // Document-open nags (Adobe version checks, lifecycle scripts)\n // never alert by default — the boot-nag gap, closed.\n } else if (typeof globalThis.alert === 'function') globalThis.alert(message);\n },\n gotoPage: (page, opts) => {\n const current = overridesOf();\n if (current?.gotoPage) current.gotoPage(page, opts);\n else options.goToPage?.(page);\n },\n };\n};\n","/** A browser image source whose object URL has an explicit lifetime. */\nexport interface ObjectUrlImageSource {\n objectUrl(signal?: AbortSignal): Promise<{ url: string; revoke(): void }>;\n}\n\nexport interface PaintedImageCallbacks {\n onPainted(): void;\n onUnpainted(): void;\n}\n\n/**\n * Bind a raster source to an <img> without ever painting its incomplete-image\n * placeholder. The source becomes visible on load; retained coverage is\n * released only after the browser has had one full frame to present it.\n *\n * Call this from the framework's pre-paint mount hook and invoke the returned\n * cleanup on unmount. Keeping this DOM lifecycle here lets every framework\n * adapter remain a ref plus one bind/unbind hook.\n */\nexport function bindPaintedImage(\n image: HTMLImageElement,\n source: ObjectUrlImageSource,\n callbacks: PaintedImageCallbacks,\n): () => void {\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n let frame = 0;\n let painted = false;\n\n // The element is already committed when a mount hook runs. Hide it before\n // that commit can paint, then reveal only once the bitmap has loaded.\n image.style.visibility = 'hidden';\n\n const onLoad = () => {\n image.style.visibility = 'visible';\n\n // rAF callbacks run before a frame paints. The first frame presents the\n // newly visible image while retained coverage remains; the second reports\n // that presentation opportunity and permits the retained source to leave.\n frame = requestAnimationFrame(() => {\n frame = requestAnimationFrame(() => {\n frame = 0;\n painted = true;\n callbacks.onPainted();\n });\n });\n };\n\n image.addEventListener('load', onLoad, { once: true });\n\n void source.objectUrl(controller.signal).then(\n (objectUrl) => {\n if (controller.signal.aborted) {\n objectUrl.revoke();\n return;\n }\n revoke = objectUrl.revoke;\n image.src = objectUrl.url;\n },\n () => {\n // Aborted or unavailable: remain hidden and retain fallback coverage.\n },\n );\n\n return () => {\n controller.abort();\n if (frame) cancelAnimationFrame(frame);\n image.removeEventListener('load', onLoad);\n image.style.visibility = 'hidden';\n image.removeAttribute('src');\n revoke?.();\n if (painted) callbacks.onUnpainted();\n };\n}\n","/**\n * Platform-feedback providers — the browser implementations of the interaction\n * hub's haptic port (structurally `PlatformFeedback` from\n * `@embedpdf/plugin-interaction`; declared structurally here so this package\n * stays plugin-free, per the layering law).\n *\n * Honesty about reach: `navigator.vibrate` works on Android browsers and is\n * NOT implemented in iOS Safari — there the default provider is a silent\n * no-op until Apple ships a haptics API. (The community switch-control hacks\n * were evaluated and deliberately NOT shipped: Apple patched programmatic\n * triggering in iOS 26.5, leaving only tap-on-control tricks that don't fit a\n * viewer's gesture moments.) The real iPhone haptic today comes through a\n * native shell: a WKWebView embedder installs a script message handler and\n * passes {@link wkFeedback} (or their own provider) instead.\n */\n\n/** Structural twin of plugin-interaction's `PlatformFeedback`. */\nexport interface WebPlatformFeedback {\n selection(): void;\n impact(weight?: 'light' | 'medium' | 'heavy'): void;\n notify(kind: 'success' | 'warning' | 'error'): void;\n}\n\nconst vibrate = (pattern: number | number[]): void => {\n if (typeof navigator !== 'undefined') navigator.vibrate?.(pattern);\n};\n\n/**\n * The default web provider: the Vibration API, mapped onto the three haptic\n * families (selection = the shortest tick; impact scales with weight; notify\n * uses patterns). A no-op wherever vibration is unsupported — notably iOS\n * Safari — so it is always safe to install.\n */\nexport const vibrationFeedback: WebPlatformFeedback = {\n selection: () => vibrate(8),\n impact: (weight = 'light') => vibrate(weight === 'heavy' ? 30 : weight === 'medium' ? 20 : 10),\n notify: (kind) =>\n vibrate(kind === 'error' ? [12, 60, 12, 60, 12] : kind === 'warning' ? [15, 70, 15] : [10, 50, 10]),\n};\n\ninterface WKMessageHandler {\n postMessage(message: unknown): void;\n}\n\n/**\n * A provider for native iOS shells: forwards each call to a WKWebView script\n * message handler the HOST APP installed under `handlerName` — an explicit\n * contract between the embedder and their own app, never a global this library\n * sniffs on its own. The message shape is\n * `{ family: 'selection' } | { family: 'impact', weight } | { family: 'notify', kind }`;\n * the native side maps families onto the matching `UIFeedbackGenerator`.\n * Calls fall back to `fallback` (default: {@link vibrationFeedback}) when the\n * handler is absent, so one provider serves a hybrid web/app deployment.\n */\nexport const wkFeedback = (\n handlerName: string,\n fallback: WebPlatformFeedback = vibrationFeedback,\n): WebPlatformFeedback => {\n const handler = (): WKMessageHandler | undefined => {\n if (typeof window === 'undefined') return undefined;\n const w = window as Window & {\n webkit?: { messageHandlers?: Record<string, WKMessageHandler | undefined> };\n };\n return w.webkit?.messageHandlers?.[handlerName];\n };\n return {\n selection: () => {\n const h = handler();\n if (h) h.postMessage({ family: 'selection' });\n else fallback.selection();\n },\n impact: (weight = 'light') => {\n const h = handler();\n if (h) h.postMessage({ family: 'impact', weight });\n else fallback.impact(weight);\n },\n notify: (kind) => {\n const h = handler();\n if (h) h.postMessage({ family: 'notify', kind });\n else fallback.notify(kind);\n },\n };\n};\n","/**\n * Wheel-input classification for ambient zoom — pure, so every framework\n * adapter shares one feel and the mapping is unit-testable.\n *\n * A ctrl/cmd wheel event is one of three physically different inputs that\n * happen to share an event type, each with its own unit system:\n *\n * • trackpad PINCH — browsers (Chrome/Edge/Firefox) synthesize ctrl+wheel\n * px-deltas scaled to the de-facto convention that `exp(-Δ/100)` tracks\n * the physical finger scale 1:1. Anything weaker feels dead (the content\n * must track the fingers — the Figma/maps feel).\n * • mouse NOTCHES — coarse steps: ±100/±120 px in Chrome, lines/pages mode\n * in Firefox. The pinch mapping would triple per click; a notch instead\n * steps by 1.2, the same ratio as the zoom buttons.\n * • cmd + continuous SCROLL — real scroll px, big and streaming (with\n * momentum): a gentle scrub coefficient. A cmd+mouse notch (±120 px)\n * lands within 0.3% of the 1.2 button step, so it needs no special case.\n *\n * (Safari never synthesizes ctrl+wheel for pinch — it fires proprietary\n * gesture events with an absolute scale; that wiring lives in the adapters.)\n */\nexport interface WheelSample {\n deltaY: number;\n /** WheelEvent.deltaMode: 0 = pixels, 1 = lines, 2 = pages. */\n deltaMode: number;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\n/** The per-notch step for discrete wheels — the zoom buttons' ratio. */\nconst NOTCH = 1.2;\n\n/** Zoom factor for a ctrl/cmd wheel event (the caller decides zoom vs pan). */\nexport function wheelZoomFactor(e: WheelSample): number {\n // Lines/pages mode (Firefox mouse wheels): one button-step per notch.\n if (e.deltaMode !== 0) return Math.pow(NOTCH, -Math.sign(e.deltaY));\n if (e.ctrlKey) {\n // px deltas with ctrl: a real mouse notch arrives big (±100/±120) and\n // steps like the buttons; anything smaller is a synthesized pinch.\n if (Math.abs(e.deltaY) >= 100) return Math.pow(NOTCH, -Math.sign(e.deltaY));\n return Math.exp(-e.deltaY / 100);\n }\n // cmd + continuous scroll: the gentle scrub.\n return Math.exp(-e.deltaY * 0.0015);\n}\n","/**\n * Stage gesture controller — the ONE DOM input binding for a Stage surface,\n * shared by every framework adapter (React, Angular, …) so the feel can never\n * drift between them.\n *\n * The premise (see the stage plugin's camera doctrine): desktop inputs arrive\n * with physics already applied by the OS — momentum lives in the wheel stream,\n * a trackpad pinch is one pre-arbitrated gesture stream. Touch arrives RAW:\n * with `touch-action: none` the platform scroller is out of the loop, so the\n * ballistics (velocity → fling) and the arbitration (is this contact a scroll,\n * a pinch, a tap, or a tool gesture?) must be synthesized here.\n *\n * Arbitration is MODALITY-AWARE:\n * - touch — navigation-first: one finger pans (whatever tool is armed), two\n * fingers pinch-zoom around their centroid, release velocity flings, a tap\n * forwards as a click, a double-tap zoom-toggles, a long-press hands the\n * gesture to the interaction hub (text selection), and a second finger\n * landing mid-tool-gesture CANCELS it into a pinch (the Notes/Procreate\n * convention).\n * - mouse/pen — tool-first, exactly the pre-existing behavior: with a hub,\n * every down/move/up forwards (pan is the pan tool's job); without one,\n * dragging pans. Wheel and Safari-trackpad gesture events are unchanged.\n *\n * Camera writes are rAF-COALESCED: pointer events only update gesture state;\n * one animation-frame tick applies at most one pan and one zoom per frame,\n * inside the host's begin/endGesture transaction. Events may arrive at 120 Hz;\n * the camera moves at display rate.\n *\n * Dependency note: this module speaks to the stage through the STRUCTURAL\n * {@link StageGestureHost} interface (satisfied by `StageCapability`) and to\n * the interaction hub through {@link StageGestureSink} (a closure the adapter\n * builds) — @embedpdf/web stays free of plugin imports, per the layering law.\n */\n\nimport { wheelZoomFactor } from './wheel';\n\nexport type StagePointerKind = 'mouse' | 'pen' | 'touch';\n\n/** What the controller needs from the camera — `StageCapability` satisfies it. */\nexport interface StageGestureHost {\n panBy(dxScreen: number, dyScreen: number): void;\n zoomAround(screenPt: { x: number; y: number }, factor: number): void;\n beginGesture(options?: { elastic?: boolean }): void;\n endGesture(): void;\n /** Momentum pan from a release velocity in screen px/s. */\n fling(velocityX: number, velocityY: number): void;\n /** True while a tween/fling runs — a touch-down then is a \"catch\", not a tap. */\n cameraInMotion(): boolean;\n doubleTapZoom(screenPt: { x: number; y: number }): void;\n}\n\n/**\n * Where non-navigation gestures go — the adapter's bridge to the interaction\n * hub. Every callback receives the ORIGINAL PointerEvent so the adapter can\n * resolve pages/points exactly as it always has. Omit the sink entirely for a\n * hub-less (built-in pan) stage.\n */\nexport interface StageGestureSink {\n down(e: PointerEvent, clickCount: number): void;\n move(e: PointerEvent): void;\n up(e: PointerEvent): void;\n /** The gesture was taken over by navigation (second finger → pinch) or\n * cancelled by the system — abort, don't commit. */\n cancel(e: PointerEvent): void;\n /** Pointer travel with no gesture in flight — cursor feedback. */\n hover(e: PointerEvent): void;\n /** A touch press held still: hand the gesture to the hub (the adapter\n * typically forwards it as a word-select down). Subsequent move/up arrive\n * via {@link move}/{@link up}. */\n longPress(e: PointerEvent): void;\n /**\n * Touch-consent pre-flight, asked at touch-down BEFORE the contact is\n * classified: does a tool have standing to own this contact? True when the\n * armed tool takes fingers wholesale (a drawing tool) or something under\n * the point claims its drags (a selected annotation's body or handles).\n * True routes the whole contact to {@link down}/{@link move}/{@link up} —\n * where a second finger still cancels it into a pinch. Must be a pure\n * read. Absent = never; the contact navigates.\n */\n claimsPoint?(e: PointerEvent): boolean;\n}\n\n/** The wheel fields the zoom classifier reads (see `./wheel`'s `WheelSample`). */\nexport interface StageWheelSample {\n deltaY: number;\n deltaMode: number;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\nexport interface StageGestureOptions {\n /** Ambient zoom (wheel-zoom, pinch-zoom, double-tap zoom). Off: zoom wheels\n * fall through to pan and pinches pan without zooming — but are still\n * swallowed, never page-zooming the browser. Default true. */\n zoomGestures?: boolean;\n /** The wheel → zoom-factor classifier. Defaults to this package's\n * `wheelZoomFactor` (browser wheel classification lives HERE, with the rest\n * of the browser input handling); inject to override or to fake in tests. */\n wheelZoomFactor?: (sample: StageWheelSample) => number;\n /** Tool routing for non-navigation gestures; omit for built-in-pan stages. */\n sink?: StageGestureSink | null;\n /** Touch press duration that becomes a long-press (ms). Default 450. */\n longPressMs?: number;\n /** Finger travel below which a touch stays a tap/press (px). Default 10. */\n tapSlopPx?: number;\n /** Max gap between taps for a double-tap (ms). Default 300. */\n doubleTapMs?: number;\n /** Release speed below which no fling starts (px/s). Default 50. */\n flingMinVelocity?: number;\n}\n\n/**\n * Release velocity from a trail of pointer samples: the mean velocity over the\n * trailing `windowMs` (first-to-last inside the window). Null when the trail is\n * too thin or too stale to trust — the standard \"held still, then let go\"\n * case, which must NOT fling. Pure; exported for tests.\n */\nexport function computeReleaseVelocity(\n samples: ReadonlyArray<{ t: number; x: number; y: number }>,\n now: number,\n windowMs = 100,\n): { vx: number; vy: number } | null {\n let firstIdx = -1;\n for (let i = 0; i < samples.length; i++) {\n if (now - samples[i].t <= windowMs) {\n firstIdx = i;\n break;\n }\n }\n if (firstIdx < 0 || firstIdx === samples.length - 1) return null;\n const first = samples[firstIdx];\n const last = samples[samples.length - 1];\n const dt = last.t - first.t;\n if (dt < 10) return null;\n return { vx: ((last.x - first.x) / dt) * 1000, vy: ((last.y - first.y) / dt) * 1000 };\n}\n\ninterface Tracked {\n id: number;\n kind: StagePointerKind;\n x: number;\n y: number;\n downX: number;\n downY: number;\n}\n\ntype Mode = 'idle' | 'pending' | 'pan' | 'pinch' | 'tool' | 'mousedrag';\n\n/** Attach the gesture controller to a Stage container. Returns the detach fn. */\nexport function createStageGestureController(\n el: HTMLElement,\n host: StageGestureHost,\n options: StageGestureOptions,\n): () => void {\n const zoomGestures = options.zoomGestures ?? true;\n const wheelZoom = options.wheelZoomFactor ?? wheelZoomFactor;\n const sink = options.sink ?? null;\n const LONG_PRESS_MS = options.longPressMs ?? 450;\n const SLOP = options.tapSlopPx ?? 10;\n const DOUBLE_TAP_MS = options.doubleTapMs ?? 300;\n const DOUBLE_TAP_RADIUS = 25;\n const FLING_MIN = options.flingMinVelocity ?? 50;\n const MIN_PINCH_SPAN = 20; // px — below this a span ratio is mostly noise\n const PINCH_RELEASE_GRACE_MS = 160; // leftover finger must OUTLIVE the release\n const SETTLE_SPEED = 0.12; // px/ms — below this the leftover finger has SETTLED\n\n const pointers = new Map<number, Tracked>();\n let mode: Mode = 'idle';\n let began = false; // a host gesture transaction is open\n let suppressTap = false; // this contact CAUGHT a moving camera — never a tap\n let touchToolGesture = false; // 'tool' mode entered via touch long-press\n let downEvent: PointerEvent | null = null; // first touch's down, for tap/long-press forwarding\n let longPressTimer: ReturnType<typeof setTimeout> | null = null;\n let panId = -1;\n // A pan is ARMED once its finger has crossed slop. Fresh touches arm on the\n // pending→pan transition; the finger LEFT OVER when a pinch ends starts\n // DISARMED — a gesture's identity persists through its release, so the\n // leftover contact must re-earn pan-hood exactly like a new finger would.\n // While disarmed, its micro-rolls neither pan nor pollute the velocity\n // trail (which still holds the pinch's CENTROID motion — the momentum a\n // compound gesture actually has). For a GRACE window after the transition\n // the slop base FOLLOWS the finger: in a fast release the leftover finger\n // is still genuinely moving, and distance alone cannot tell that from a\n // deliberate continuation — only outliving the release window can.\n let panArmed = true;\n let panGraceUntil = 0;\n let graceSample = { t: 0, x: 0, y: 0 }; // leftover finger's last observed sample\n // iOS-visible seam: real WebKit can deliver a trailing gesturechange after\n // the last pointerup of a pinch — with no touches left, the desktop-trackpad\n // path would apply a stray end-of-pinch zoom. Any RECENT touch activity\n // therefore suppresses gesture events; desktop trackpads never have any.\n let lastTouchAt = -Infinity;\n\n // rAF application state\n let frame = 0;\n let dirty = false;\n let lastApplied = { x: 0, y: 0 }; // pan focal point, client px\n let lastSpan = 0;\n\n // velocity trail of the pan focal point (finger, or pinch centroid)\n let trail: Array<{ t: number; x: number; y: number }> = [];\n // the pinch's SPAN trail, sampled beside the centroid: at release it decides\n // the gesture's CHARACTER. A fast pinch release has asymmetric finger\n // speeds (the lifting finger flicks away), which moves the TRUE centroid at\n // hundreds of px/s — physically real, but zoom-release noise the platform\n // ignores. Fling only when the centroid rate DOMINATES the span rate\n // (a two-finger pan); a zoom-dominant release ends still.\n let spanTrail: Array<{ t: number; x: number; y: number }> = [];\n\n // tap-pair state for double-tap\n let lastTapT = 0;\n let lastTapX = 0;\n let lastTapY = 0;\n\n // multi-click counter for mouse/pen tool downs (parity with the adapters'\n // previous createClickCounter: 400 ms / 6 px)\n let mLast = 0;\n let mX = 0;\n let mY = 0;\n let mCount = 0;\n const clickCount = (e: PointerEvent): number => {\n const now = Date.now();\n mCount =\n now - mLast <= 400 && Math.hypot(e.clientX - mX, e.clientY - mY) <= 6 ? mCount + 1 : 1;\n mLast = now;\n mX = e.clientX;\n mY = e.clientY;\n return mCount;\n };\n\n const vpt = (clientX: number, clientY: number) => {\n const r = el.getBoundingClientRect();\n return { x: clientX - r.left, y: clientY - r.top };\n };\n const touches = (): Tracked[] => {\n const out: Tracked[] = [];\n pointers.forEach((p) => {\n if (p.kind === 'touch') out.push(p);\n });\n return out;\n };\n\n const clearLongPress = () => {\n if (longPressTimer !== null) {\n clearTimeout(longPressTimer);\n longPressTimer = null;\n }\n };\n const stopFrames = () => {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n };\n const begin = (elastic = false) => {\n if (!began) {\n began = true;\n // touch contacts are elastic (rubber-band past the clamp); mouse drags\n // stay rigid — the desktop convention\n host.beginGesture(elastic ? { elastic: true } : undefined);\n }\n };\n const end = () => {\n if (began) {\n began = false;\n host.endGesture();\n }\n };\n const toIdle = () => {\n mode = 'idle';\n downEvent = null;\n touchToolGesture = false;\n trail = [];\n spanTrail = [];\n dirty = false;\n panId = -1;\n panArmed = true;\n panGraceUntil = 0;\n clearLongPress();\n stopFrames();\n };\n const pushSample = (x: number, y: number) => {\n trail.push({ t: performance.now(), x, y });\n if (trail.length > 16) trail.shift();\n };\n const maybeFling = () => {\n const v = computeReleaseVelocity(trail, performance.now());\n if (v && Math.hypot(v.vx, v.vy) >= FLING_MIN) host.fling(v.vx, v.vy);\n };\n\n // ── the application step, shared by the frame loop AND the release paths —\n // a release must flush whatever the last tick hadn't applied yet, or a fast\n // flick loses its final sub-frame of travel and a pinch its last span step.\n const flushPanTo = (x: number, y: number) => {\n const dx = x - lastApplied.x;\n const dy = y - lastApplied.y;\n if (dx !== 0 || dy !== 0) host.panBy(dx, dy);\n lastApplied = { x, y };\n dirty = false;\n };\n const flushPinch = (ax: number, ay: number, bx: number, by: number) => {\n const cx = (ax + bx) / 2;\n const cy = (ay + by) / 2;\n const span = Math.hypot(ax - bx, ay - by);\n const dx = cx - lastApplied.x;\n const dy = cy - lastApplied.y;\n if (dx !== 0 || dy !== 0) host.panBy(dx, dy);\n if (zoomGestures && span > MIN_PINCH_SPAN && lastSpan > MIN_PINCH_SPAN && span !== lastSpan) {\n host.zoomAround(vpt(cx, cy), span / lastSpan);\n }\n // The centroid + span trails sample HERE — once per applied frame, where\n // both fingers are read coherently. Per-event sampling zig-zags (fingers\n // report sequentially, so each single-finger move fakes a half-step of\n // centroid motion) and manufactures phantom release velocity. The span\n // trail is the release gate's evidence of the gesture's CHARACTER.\n pushSample(cx, cy);\n spanTrail.push({ t: performance.now(), x: span, y: 0 });\n if (spanTrail.length > 16) spanTrail.shift();\n lastApplied = { x: cx, y: cy };\n lastSpan = span;\n dirty = false;\n };\n\n // ── the per-frame application (one camera write per frame) ────────────────\n const tick = () => {\n frame = 0;\n if (mode === 'pan' || mode === 'mousedrag') {\n const p = pointers.get(panId);\n if (p && dirty) flushPanTo(p.x, p.y);\n frame = requestAnimationFrame(tick);\n } else if (mode === 'pinch') {\n const [a, b] = touches();\n if (a && b && dirty) flushPinch(a.x, a.y, b.x, b.y);\n frame = requestAnimationFrame(tick);\n }\n };\n const ensureFrames = () => {\n if (!frame) frame = requestAnimationFrame(tick);\n };\n\n const enterPinch = () => {\n const [a, b] = touches();\n if (!a || !b) return;\n mode = 'pinch';\n lastApplied = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n lastSpan = Math.hypot(a.x - b.x, a.y - b.y);\n trail = [];\n dirty = false;\n ensureFrames();\n };\n\n const onLongPress = () => {\n longPressTimer = null;\n if (mode !== 'pending' || !downEvent || !sink) return;\n // The camera transaction closes; the tool owns the rest of this contact.\n end();\n mode = 'tool';\n touchToolGesture = true;\n sink.longPress(downEvent);\n };\n\n // ── listeners ─────────────────────────────────────────────────────────────\n const onDown = (e: PointerEvent) => {\n const kind = (e.pointerType || 'mouse') as StagePointerKind;\n if (kind === 'touch') lastTouchAt = performance.now();\n if (kind !== 'touch') {\n if (kind === 'mouse' && e.button !== 0) return;\n if (mode !== 'idle') return; // an active gesture owns the surface\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n if (sink) {\n // tool-first, exactly the pre-existing hub behavior\n mode = 'tool';\n sink.down(e, clickCount(e));\n } else {\n mode = 'mousedrag';\n begin();\n panId = e.pointerId;\n lastApplied = { x: e.clientX, y: e.clientY };\n ensureFrames();\n }\n return;\n }\n\n // touch\n switch (mode) {\n case 'idle': {\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n // Consent pre-flight — but a MOVING camera always catches first: while\n // content flies under the finger, the touch means \"stop\", never \"grab\n // whatever happens to pass beneath it\".\n const moving = host.cameraInMotion();\n if (!moving && sink?.claimsPoint?.(e)) {\n // A tool owns this contact from the first pixel (selected-annotation\n // drag, or an armed drawing tool). No camera transaction — this is\n // not a navigation gesture; a second finger converts it to a pinch\n // via the 'tool' branch (sink.cancel), exactly like a long-press.\n mode = 'tool';\n touchToolGesture = true;\n sink.down(e, 1);\n break;\n }\n mode = 'pending';\n downEvent = e;\n suppressTap = moving; // a catch, not a tap\n begin(true); // stops any fling/tween under the finger\n trail = [];\n pushSample(e.clientX, e.clientY);\n clearLongPress();\n if (sink && !suppressTap) longPressTimer = setTimeout(onLongPress, LONG_PRESS_MS);\n break;\n }\n case 'pending':\n case 'pan': {\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n clearLongPress();\n enterPinch();\n break;\n }\n case 'tool': {\n // A second finger during a TOUCH tool gesture cancels it into a pinch\n // (the platform convention). Mouse tool gestures ignore stray touches.\n if (!touchToolGesture) return;\n sink?.cancel(e);\n touchToolGesture = false;\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n begin(true);\n enterPinch();\n break;\n }\n case 'pinch':\n case 'mousedrag':\n return; // ignore extra contacts\n }\n };\n\n const onWindowMove = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n if (p.kind === 'touch') lastTouchAt = performance.now();\n p.x = e.clientX;\n p.y = e.clientY;\n switch (mode) {\n case 'pending': {\n pushSample(p.x, p.y);\n if (Math.hypot(p.x - p.downX, p.y - p.downY) > SLOP) {\n clearLongPress();\n mode = 'pan';\n panId = p.id;\n lastApplied = { x: p.x, y: p.y }; // absorb the slop, like a scroller\n ensureFrames();\n }\n break;\n }\n case 'pan':\n if (p.id !== panId) break;\n if (!panArmed) {\n // pinch-leftover contact. Inside the release-grace window the slop\n // base follows the finger — a fast release keeps moving and must\n // never accumulate distance; only a contact that OUTLIVES the\n // window can re-earn panning by crossing slop from where it\n // settled.\n const nowT = performance.now();\n if (nowT < panGraceUntil) {\n // a finger that SETTLES (speed drops) inside the window is a\n // continuation taking hold — end the grace early so a deliberate\n // pause-then-drag stays responsive\n const dt = nowT - graceSample.t;\n if (dt >= 8) {\n const speed = Math.hypot(p.x - graceSample.x, p.y - graceSample.y) / dt;\n graceSample = { t: nowT, x: p.x, y: p.y };\n if (speed < SETTLE_SPEED) panGraceUntil = 0;\n }\n p.downX = p.x;\n p.downY = p.y;\n break;\n }\n if (Math.hypot(p.x - p.downX, p.y - p.downY) <= SLOP) break;\n panArmed = true;\n lastApplied = { x: p.x, y: p.y }; // absorb, like any fresh pan\n trail = [];\n spanTrail = [];\n }\n pushSample(p.x, p.y);\n dirty = true;\n break;\n case 'pinch':\n // centroid samples are taken per applied FRAME (see flushPinch) —\n // per-event sampling here would zig-zag between the two fingers\n dirty = true;\n break;\n case 'tool':\n sink?.move(e);\n break;\n case 'mousedrag':\n if (p.id === panId) dirty = true;\n break;\n case 'idle':\n break;\n }\n };\n\n // Hover (cursor feedback) — only with no gesture in flight, and only from\n // the element itself, matching the previous adapters.\n const onHoverMove = (e: PointerEvent) => {\n if (mode === 'idle' && sink) sink.hover(e);\n };\n\n const backToSingleFinger = (): boolean => {\n const rest = touches();\n if (rest.length !== 1) return false;\n mode = 'pan';\n panId = rest[0].id;\n panArmed = false; // leftover contact re-earns pan-hood via slop\n panGraceUntil = performance.now() + PINCH_RELEASE_GRACE_MS;\n graceSample = { t: performance.now(), x: rest[0].x, y: rest[0].y };\n rest[0].downX = rest[0].x;\n rest[0].downY = rest[0].y;\n lastApplied = { x: rest[0].x, y: rest[0].y };\n trail = [];\n dirty = false;\n return true;\n };\n\n const onUp = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n if (p.kind === 'touch') lastTouchAt = performance.now();\n pointers.delete(e.pointerId);\n switch (mode) {\n case 'pending': {\n // Slop never exceeded, timer never fired: a tap (or a catch).\n clearLongPress();\n end();\n const now = performance.now();\n const isDouble =\n now - lastTapT <= DOUBLE_TAP_MS &&\n Math.hypot(e.clientX - lastTapX, e.clientY - lastTapY) <= DOUBLE_TAP_RADIUS;\n if (suppressTap) {\n lastTapT = 0; // a catch never counts toward a double-tap\n } else if (zoomGestures && isDouble) {\n lastTapT = 0;\n host.doubleTapZoom(vpt(e.clientX, e.clientY));\n } else {\n lastTapT = now;\n lastTapX = e.clientX;\n lastTapY = e.clientY;\n if (sink && downEvent) {\n // The whole click, delivered at release — tools see exactly the\n // down/up pair they would from a mouse.\n sink.down(downEvent, 1);\n sink.up(e);\n }\n }\n toIdle();\n break;\n }\n case 'pan': {\n if (!panArmed) {\n // The pinch's OTHER finger leaving: the gesture ends as a pinch.\n // No flush (release-rolls are noise). Glide is CHARACTER-gated: a\n // human fast release moves the true centroid (the lifting finger\n // flicks away), so centroid velocity alone lies — the glide fires\n // only when the centroid rate DOMINATES the span rate (a\n // two-finger pan), never on a zoom-dominant release.\n const now = performance.now();\n const v = computeReleaseVelocity(trail, now);\n const vs = computeReleaseVelocity(spanTrail, now);\n end(); // close the bracket first — endGesture owns the elastic settle\n if (v) {\n const speed = Math.hypot(v.vx, v.vy);\n if (speed >= FLING_MIN && speed > Math.abs(vs?.vx ?? 0)) host.fling(v.vx, v.vy);\n }\n toIdle();\n break;\n }\n flushPanTo(e.clientX, e.clientY); // apply the final sub-frame of travel\n pushSample(e.clientX, e.clientY);\n end();\n maybeFling();\n toIdle();\n break;\n }\n case 'pinch': {\n const rest = touches();\n if (rest.length === 1) {\n // The pinch ends AT ITS LAST COHERENT FRAME. No flush here: the\n // lifting finger's release position is fresh but the other one's is\n // stale (its move for this window may not have arrived), and a\n // centroid of two instants is fiction — in a fast pinch that skewed\n // write was the visible end-of-pinch hop, and its poisoned trail\n // sample the phantom fling. Only coherent finger-pairs write the\n // camera; the sub-frame remainder is discarded, as the platform\n // recognizers do. The remaining contact gets a DISARMED pan with\n // the centroid trail preserved and a release-grace window armed.\n mode = 'pan';\n panId = rest[0].id;\n panArmed = false;\n panGraceUntil = performance.now() + PINCH_RELEASE_GRACE_MS;\n graceSample = { t: performance.now(), x: rest[0].x, y: rest[0].y };\n rest[0].downX = rest[0].x;\n rest[0].downY = rest[0].y;\n lastApplied = { x: rest[0].x, y: rest[0].y };\n dirty = false;\n break;\n }\n end();\n maybeFling();\n toIdle();\n break;\n }\n case 'tool': {\n sink?.up(e);\n toIdle();\n break;\n }\n case 'mousedrag': {\n if (p.id !== panId) break;\n flushPanTo(e.clientX, e.clientY);\n end();\n toIdle();\n break;\n }\n case 'idle':\n break;\n }\n };\n\n const onCancel = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n pointers.delete(e.pointerId);\n if (mode === 'tool') {\n sink?.cancel(e);\n toIdle();\n return;\n }\n if (mode === 'pinch' && backToSingleFinger()) return;\n end();\n toIdle();\n };\n\n // Wheel is ambient navigation in BOTH modes: ctrl/meta zooms (classified per\n // input by the injected wheelZoomFactor), else scrolls. With zoom gestures\n // off, a zoom-wheel falls through to ordinary pan.\n const onWheel = (e: WheelEvent) => {\n e.preventDefault();\n if (zoomGestures && (e.ctrlKey || e.metaKey)) {\n host.zoomAround(vpt(e.clientX, e.clientY), wheelZoom(e));\n } else {\n const dx = e.shiftKey ? e.deltaY : e.deltaX;\n const dy = e.shiftKey ? e.deltaX : e.deltaY;\n host.panBy(-dx, -dy);\n }\n };\n\n // Safari's proprietary gesture events. On DESKTOP Safari they are the only\n // trace of a trackpad pinch — convert the absolute scale to per-event ratios.\n // On iOS they fire ALONGSIDE per-finger pointer events; there the pointer\n // path owns the pinch and these are preventDefault-ed only (a pinch over the\n // stage must never page-zoom Safari). The guard is live touch contacts.\n let lastScale = 1;\n const onGestureStart = (e: Event) => {\n e.preventDefault();\n lastScale = (e as unknown as { scale?: number }).scale ?? 1;\n };\n const onGestureChange = (e: Event) => {\n e.preventDefault();\n // iOS: the pointer path owns touch pinches — and a TRAILING gesturechange\n // can arrive after the last pointerup, so suppression keys on RECENT\n // touch activity, not just live contacts. Desktop trackpads have none.\n if (touches().length > 0 || performance.now() - lastTouchAt < 500) return;\n const g = e as unknown as { scale?: number; clientX: number; clientY: number };\n const scale = g.scale ?? 1;\n if (zoomGestures && scale > 0) {\n host.zoomAround(vpt(g.clientX, g.clientY), scale / lastScale);\n }\n lastScale = scale;\n };\n\n el.addEventListener('pointerdown', onDown);\n el.addEventListener('pointermove', onHoverMove);\n window.addEventListener('pointermove', onWindowMove);\n window.addEventListener('pointerup', onUp);\n window.addEventListener('pointercancel', onCancel);\n el.addEventListener('wheel', onWheel, { passive: false });\n const hasGestureEvents = 'GestureEvent' in window;\n if (hasGestureEvents) {\n el.addEventListener('gesturestart', onGestureStart);\n el.addEventListener('gesturechange', onGestureChange);\n el.addEventListener('gestureend', onGestureStart); // reset the base\n }\n\n return () => {\n el.removeEventListener('pointerdown', onDown);\n el.removeEventListener('pointermove', onHoverMove);\n window.removeEventListener('pointermove', onWindowMove);\n window.removeEventListener('pointerup', onUp);\n window.removeEventListener('pointercancel', onCancel);\n el.removeEventListener('wheel', onWheel);\n if (hasGestureEvents) {\n el.removeEventListener('gesturestart', onGestureStart);\n el.removeEventListener('gesturechange', onGestureChange);\n el.removeEventListener('gestureend', onGestureStart);\n }\n clearLongPress();\n stopFrames();\n end(); // balance an open transaction on unmount\n };\n}\n","/**\n * Selection-handle DOM binding — the listener mechanics of dragging one\n * handle element, shared by every framework adapter (the policy — geometry\n * and the drag session — lives in `@embedpdf/plugin-selection`'s pure\n * `handles` module; this file never imports it, per the layering law:\n * structural interfaces only).\n *\n * The load-bearing subtlety this module owns: the pointer-DOWN shield must be\n * a NATIVE listener. The stage's gesture controller listens natively on the\n * container, so a framework-synthetic stopPropagation (React runs its\n * handlers at the root, AFTER the container's native ones) would be too late\n * — the stage would already be panning underneath the handle drag. Once the\n * down is captured here, movement tracks by CLIENT DELTAS from the handle's\n * own base point: no DOM geometry reads, and the client↔overlay conversion\n * cancels out.\n */\n\ninterface SurfacePoint {\n x: number;\n y: number;\n}\n\n/** The armed drag the adapter hands back — plugin-selection's\n * `SelectionHandleDragSession` satisfies it. */\nexport interface SelectionHandleSession {\n move(overlay: SurfacePoint): void;\n end(): void;\n}\n\nexport interface AttachSelectionHandleOptions {\n /**\n * Called at pointer-down to arm a drag: return the drag session plus the\n * handle's current BASE point in overlay space (the bar's midpoint — the\n * point the user grabbed). Return null to decline (endpoint vanished, page\n * not laid out) — the press then does nothing and nothing is captured.\n */\n arm(): { base: SurfacePoint; session: SelectionHandleSession } | null;\n}\n\n/** Bind one handle element. Returns the detach fn. */\nexport function attachSelectionHandle(\n el: HTMLElement,\n options: AttachSelectionHandleOptions,\n): () => void {\n let active: {\n session: SelectionHandleSession;\n base: SurfacePoint;\n baseClient: SurfacePoint;\n pointerId: number;\n } | null = null;\n\n const onMove = (e: PointerEvent) => {\n if (!active || e.pointerId !== active.pointerId) return;\n active.session.move({\n x: active.base.x + (e.clientX - active.baseClient.x),\n y: active.base.y + (e.clientY - active.baseClient.y),\n });\n };\n // Release and system-cancel settle identically: the selection's own commit\n // semantics live in the session; there is no half-state to revert here.\n const onUp = (e: PointerEvent) => {\n if (!active || e.pointerId !== active.pointerId) return;\n const s = active.session;\n active = null;\n s.end();\n };\n const onDown = (e: PointerEvent) => {\n if (active) return;\n const armed = options.arm();\n if (!armed) return;\n e.preventDefault();\n e.stopPropagation(); // native: fires BEFORE the stage controller's listener\n try {\n el.setPointerCapture(e.pointerId);\n } catch {\n // best-effort, like the scrollbar: an already-released pointer\n // (pen/touch races, synthetic events in tests) throws — the drag must\n // still arm, and the window-less capture path still delivers to `el`.\n }\n active = {\n session: armed.session,\n base: armed.base,\n baseClient: { x: e.clientX, y: e.clientY },\n pointerId: e.pointerId,\n };\n };\n\n el.addEventListener('pointerdown', onDown);\n el.addEventListener('pointermove', onMove);\n el.addEventListener('pointerup', onUp);\n el.addEventListener('pointercancel', onUp);\n return () => {\n el.removeEventListener('pointerdown', onDown);\n el.removeEventListener('pointermove', onMove);\n el.removeEventListener('pointerup', onUp);\n el.removeEventListener('pointercancel', onUp);\n active = null;\n };\n}\n","/**\n * Stage surface binding — everything DOM about attaching one element to one\n * stage lens, shared by every framework adapter (React, Angular, …) so the\n * binding can never drift between them:\n *\n * • ResizeObserver → `setViewport` (the shell only reports; placement is the\n * stage plugin's job)\n * • device-pixel-ratio observation → `setDevicePixelRatio` (dppx changes\n * re-subscribe, since the media query's value itself moves)\n * • pointer-sample normalization — the ONE place a PointerEvent becomes a\n * page-resolved, source-stamped sample for the interaction hub\n * • the gesture controller (wheel, Safari trackpad gestures, synthesized\n * touch physics) from `./stage-gestures`\n *\n * The framework adapters keep only their reactive glue: refs/effects, page\n * surfaces, cursor-style subscription, overlay slots.\n *\n * Dependency note: like the gesture controller, this module speaks to the\n * stage and the hub through STRUCTURAL interfaces ({@link StageSurfaceHost},\n * {@link StageSurfaceHub}) — satisfied by `StageCapability` and\n * `InteractionCapability`, imported by neither. @embedpdf/web stays free of\n * plugin imports, per the layering law.\n *\n * Browser-only by nature (ResizeObserver, matchMedia): call from a mounted\n * effect / browser platform check, exactly like the gesture controller.\n */\nimport { createStageGestureController } from './stage-gestures';\nimport type { StageGestureHost, StageGestureSink, StageWheelSample } from './stage-gestures';\n\ninterface SurfacePoint {\n x: number;\n y: number;\n}\n\n/** What the binding needs from the stage — `StageCapability` satisfies it. */\nexport interface StageSurfaceHost extends StageGestureHost {\n setViewport(size: { width: number; height: number }): void;\n setDevicePixelRatio(ratio: number): void;\n /** Viewport point → the page under it (with per-page display context), or null over a gap. */\n pageAt(screen: SurfacePoint): {\n pon: number;\n point: SurfacePoint;\n scale?: number;\n rotation?: 0 | 90 | 180 | 270;\n zoom?: number;\n } | null;\n /** Viewport point → a SPECIFIC page's content space, unclamped (frame-stable projection). */\n pointOnPage(pon: number, screen: SurfacePoint): SurfacePoint | null;\n}\n\n/**\n * The page-resolved sample the binding hands the hub — structurally a\n * `PointerSample` (plugin-interaction). `source` carries the lens identity so\n * the hub can route lens-scoped handlers; with several stages on one document,\n * a drag on one lens must never be captured by another lens's handler.\n */\nexport interface StageSurfaceSample {\n phase: 'down' | 'move' | 'up' | 'cancel';\n viewport: SurfacePoint;\n page?: {\n pon: number;\n point: SurfacePoint;\n scale?: number;\n rotation?: 0 | 90 | 180 | 270;\n zoom?: number;\n };\n project: (pon: number) => SurfacePoint | null;\n modifiers: { shift: boolean; alt: boolean; ctrl: boolean; meta: boolean };\n clickCount: number;\n pointerType: 'mouse' | 'pen' | 'touch';\n gesture?: 'long-press';\n source?: string;\n}\n\n/** What the binding needs from the interaction hub — `InteractionCapability` satisfies it. */\nexport interface StageSurfaceHub {\n dispatch(sample: StageSurfaceSample): void;\n activeTool(): { touchDirect?: boolean };\n wouldClaimTouch(sample: StageSurfaceSample): boolean;\n}\n\nexport interface StageSurfaceOptions {\n /** Route pointer input to the interaction hub (page-resolved samples) instead\n * of built-in drag-to-pan. Omit/null for a hub-less stage. */\n hub?: StageSurfaceHub | null;\n /** The lens identity stamped on every sample (`StageCapability.lensId()`),\n * so lens-scoped handlers only see their own stage's input. */\n source?: string;\n /** Ambient zoom gestures (see {@link StageGestureOptions}). Default true. */\n zoomGestures?: boolean;\n /** Override the wheel → zoom-factor classifier (defaults to `./wheel`). */\n wheelZoomFactor?: (sample: StageWheelSample) => number;\n}\n\n/** Attach the full surface binding. Returns the detach fn. */\nexport function createStageSurface(\n el: HTMLElement,\n stage: StageSurfaceHost,\n options: StageSurfaceOptions = {},\n): () => void {\n const hub = options.hub ?? null;\n const cleanups: Array<() => void> = [];\n\n // Only report the viewport size. Initial placement (home) is the stage\n // plugin's job — it places when it first learns a real size (and a\n // higher-priority initial-view provider can override). The shell stays dumb.\n const setViewport = () => stage.setViewport({ width: el.clientWidth, height: el.clientHeight });\n const ro = new ResizeObserver(setViewport);\n ro.observe(el);\n setViewport();\n cleanups.push(() => ro.disconnect());\n\n // Report the device pixel ratio so page transforms render crisp. dppx changes\n // (browser zoom, dragging between monitors) fire the media query;\n // re-subscribe each time since the query value itself moves.\n let mq: MediaQueryList | null = null;\n const reportDpr = () => {\n stage.setDevicePixelRatio(window.devicePixelRatio || 1);\n mq?.removeEventListener('change', reportDpr);\n mq = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);\n mq.addEventListener('change', reportDpr);\n };\n reportDpr();\n cleanups.push(() => mq?.removeEventListener('change', reportDpr));\n\n // The sink is the hub bridge: it converts events to page-resolved samples\n // (`pageAt` per event, so a drag can cross pages) and stamps the lens source.\n const sampleOf = (\n phase: StageSurfaceSample['phase'],\n e: PointerEvent,\n clickCount = 1,\n gesture?: StageSurfaceSample['gesture'],\n ): StageSurfaceSample => {\n const r = el.getBoundingClientRect();\n const viewport = { x: e.clientX - r.left, y: e.clientY - r.top };\n return {\n phase,\n viewport,\n page: stage.pageAt(viewport) ?? undefined,\n // Page-anchored gestures (annotation move/resize) track the origin\n // page's frame through this even when the cursor is off that page.\n project: (pon) => stage.pointOnPage(pon, viewport),\n modifiers: { shift: e.shiftKey, alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey },\n clickCount,\n pointerType: (e.pointerType || 'mouse') as StageSurfaceSample['pointerType'],\n ...(gesture ? { gesture } : {}),\n ...(options.source ? { source: options.source } : {}),\n };\n };\n const forward = (\n phase: StageSurfaceSample['phase'],\n e: PointerEvent,\n clickCount = 1,\n gesture?: StageSurfaceSample['gesture'],\n ) => {\n hub?.dispatch(sampleOf(phase, e, clickCount, gesture));\n };\n const sink: StageGestureSink | null = hub\n ? {\n down: (e, clickCount) => forward('down', e, clickCount),\n move: (e) => forward('move', e),\n up: (e) => forward('up', e),\n cancel: (e) => forward('cancel', e),\n hover: (e) => forward('move', e), // no owner → the hub routes to onHover\n // Touch long-press = a word-select down (clickCount 2 keeps the\n // word-selection contract; the `gesture` marker is the honest\n // long-press signal for haptics/pickup handlers).\n longPress: (e) => forward('down', e, 2, 'long-press'),\n // Touch consent: an armed drawing/markup tool takes fingers wholesale;\n // otherwise per-point claims (a selected annotation's body or handles)\n // decide. A pure pre-flight — nothing captures.\n claimsPoint: (e) =>\n !!hub.activeTool().touchDirect || hub.wouldClaimTouch(sampleOf('down', e)),\n }\n : null;\n cleanups.push(\n createStageGestureController(el, stage, {\n zoomGestures: options.zoomGestures,\n wheelZoomFactor: options.wheelZoomFactor,\n sink,\n }),\n );\n\n return () => {\n for (const fn of cleanups) fn();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["w"],"sources":["../src/file-picker.ts","../src/clipboard.ts","../src/anchored-position.ts","../src/cursor.ts","../src/external-uri.ts","../src/actions-ui.ts","../src/painted-image.ts","../src/feedback.ts","../src/wheel.ts","../src/stage-gestures.ts","../src/selection-handles.ts","../src/stage-surface.ts","../src/byte-store.ts"],"sourcesContent":["/**\n * A one-shot file dialog. Opens a throwaway `<input type=file>`, resolves the\n * chosen `File`, or `null` when the dialog is dismissed. MUST be called\n * synchronously from a user gesture (a click) or the browser refuses to open it.\n *\n * A `File` is a `Blob`, which is a `BinarySource` everywhere in the stack, so the\n * caller can hand the result straight to the engine — this module stays a pure\n * DOM utility with no EmbedPDF types.\n */\nconst IMAGE_ACCEPT = 'image/png,image/jpeg,application/pdf';\n\nexport interface PickFileOptions {\n /** The dialog's `accept` filter. Defaults to PNG / JPEG / PDF. */\n accept?: string;\n /** Allow multiple selection (returns the first file). Defaults to false. */\n multiple?: boolean;\n}\n\n/** Open the file dialog; resolve the picked file, or null if cancelled. */\nexport function pickImageFile(options: PickFileOptions = {}): Promise<File | null> {\n const { accept = IMAGE_ACCEPT, multiple = false } = options;\n return new Promise((resolve) => {\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n input.multiple = multiple;\n input.style.display = 'none';\n\n let settled = false;\n const finish = (value: File | null): void => {\n if (settled) return;\n settled = true;\n window.removeEventListener('focus', onFocus);\n input.remove();\n resolve(value);\n };\n\n // A window refocus means the dialog closed. Give a queued `change` a tick to\n // win, then treat a bare refocus as a cancel — the fallback for browsers that\n // predate the `cancel` event.\n const onFocus = (): void => {\n setTimeout(() => finish(null), 300);\n };\n\n input.addEventListener('change', () => finish(input.files?.[0] ?? null), { once: true });\n input.addEventListener('cancel', () => finish(null), { once: true });\n window.addEventListener('focus', onFocus, { once: true });\n\n document.body.appendChild(input);\n input.click();\n });\n}\n\n/** The same dialog with NO type filter — any file (attachments). */\nexport const pickFile = (options: PickFileOptions = {}): Promise<File | null> =>\n pickImageFile({ accept: '*/*', ...options });\n\n/**\n * Hand bytes to the user as a browser download (a one-shot `<a download>`).\n * The DOM half of \"download this attachment\" — pair with the annotation\n * capability's `downloadAttachment` or the document `attachments.download`.\n */\nexport function saveFile(bytes: Uint8Array | Blob, name: string, mimeType?: string): void {\n const blob =\n bytes instanceof Blob\n ? bytes\n : new Blob([bytes as BlobPart], { type: mimeType ?? 'application/octet-stream' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = name;\n a.style.display = 'none';\n document.body.appendChild(a);\n a.click();\n a.remove();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n","/**\n * Clipboard wiring for text selection — the environmental half of copy.\n *\n * The selection plugin is deliberately DOM-free: its public capability ends\n * at `readText()` (data). Everything that touches `navigator.clipboard` or\n * the native `copy` event lives HERE, structurally typed so this module\n * stays a pure DOM utility with no EmbedPDF dependencies (the selection\n * capability satisfies {@link ClipboardSelectionSource} as-is).\n */\n\n/** The slice of the selection capability clipboard wiring needs. */\nexport interface ClipboardSelectionSource {\n hasSelection(): boolean;\n canCopy(): boolean;\n readText(): Promise<string>;\n onChange(cb: () => void): () => void;\n onCommit(cb: () => void): () => void;\n}\n\nexport interface SelectionClipboardOptions {\n /**\n * Prefetch the selected text when the selection settles (default true).\n * This is what makes the NATIVE copy path work: a `copy` event handler\n * must call `clipboardData.setData` synchronously — awaiting a page-text\n * read inside it is too late — and it also keeps the async path instant.\n * The fetch is one versioned, cached, permission-gated read per page;\n * pass false to make copying strictly on-demand.\n */\n prefetch?: boolean;\n /** Event target to listen on. Defaults to `document`. */\n target?: Document;\n}\n\nconst PREFETCH_DEBOUNCE_MS = 150;\n\n/**\n * Wire clipboard copy for a selection. Two delivery paths, both fed by the\n * prefetched text:\n *\n * - the native `copy` event (menu Edit→Copy, or ctrl/cmd+C while the page\n * has a DOM selection or focused editable — e.g. the viewer shell's\n * focus sink): answered SYNCHRONOUSLY from the cache;\n * - ctrl/cmd+C with no DOM selection (canvas-rendered viewers usually\n * have none, and browsers don't dispatch `copy` then): a keydown\n * fallback writes via the async Clipboard API inside the keystroke's\n * user-activation window.\n *\n * Returns the unwire function. Mount once per document view (not per page).\n */\nexport function wireSelectionClipboard(\n selection: ClipboardSelectionSource,\n options: SelectionClipboardOptions = {},\n): () => void {\n const target = options.target ?? document;\n const prefetch = options.prefetch ?? true;\n let cached: string | null = null;\n let generation = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const refresh = (): void => {\n cached = null;\n if (!prefetch || !selection.hasSelection() || !selection.canCopy()) return;\n const gen = ++generation;\n selection.readText().then(\n (text) => {\n if (gen === generation) cached = text;\n },\n () => {}, // denied / failed — the async path will surface it if used\n );\n };\n\n // Commit (gesture end) refreshes immediately; change (drag, programmatic\n // select) debounces so a drag doesn't fetch per pointer move.\n const offCommit = selection.onCommit(() => {\n if (timer) clearTimeout(timer);\n timer = null;\n refresh();\n });\n const offChange = selection.onChange(() => {\n generation++;\n cached = null;\n if (timer) clearTimeout(timer);\n timer = setTimeout(refresh, PREFETCH_DEBOUNCE_MS);\n });\n\n const onCopy = (e: ClipboardEvent): void => {\n if (cached == null || cached === '' || !selection.hasSelection()) return;\n e.clipboardData?.setData('text/plain', cached);\n e.preventDefault();\n };\n\n const onKeyDown = (e: KeyboardEvent): void => {\n if (!(e.metaKey || e.ctrlKey) || e.key.toLowerCase() !== 'c' || e.shiftKey || e.altKey) {\n return;\n }\n if (!selection.hasSelection() || !selection.canCopy()) return;\n // A live DOM selection means the browser will dispatch `copy` itself and\n // our synchronous handler answers — don't double-write.\n const dom = target.getSelection?.();\n if (dom && !dom.isCollapsed) return;\n void copySelection(selection); // instant when prefetched; still inside user activation\n };\n\n target.addEventListener('copy', onCopy);\n target.addEventListener('keydown', onKeyDown);\n return () => {\n offCommit();\n offChange();\n if (timer) clearTimeout(timer);\n target.removeEventListener('copy', onCopy);\n target.removeEventListener('keydown', onKeyDown);\n };\n}\n\n/**\n * Toolbar/menu path: read the selected text and write it to the clipboard\n * via the async Clipboard API. Call from a user-gesture handler (browsers\n * require a secure context + user activation for clipboard writes). Resolves\n * with what was copied ('' when nothing was — nothing is written then, so an\n * empty selection never clobbers the user's clipboard).\n */\nexport async function copySelection(selection: ClipboardSelectionSource): Promise<string> {\n const text = await selection.readText();\n if (text !== '') await navigator.clipboard.writeText(text);\n return text;\n}\n","/**\n * Anchored-overlay geometry for UI floating over page content (selection\n * menus, draft menus, popovers): the projector SNAPSHOT contract, the\n * anchor shape, and the placement math. Pure values and pure functions over\n * structural rect/point shapes — no EmbedPDF types, no framework, no\n * reactive lifecycle — so every framework adapter (React, Vue, Svelte,\n * Angular) shares ONE implementation and binds it with its NATIVE\n * reactivity (context revision, signals, computed, $derived).\n *\n * The boundary laws:\n * - Placement policy lives HERE, above the projector seam: a projector\n * only knows geometry — never what is being positioned nor where it\n * prefers to sit.\n * - A projector describes the CURRENT projection; it never implements a\n * framework-style reactive lifecycle. State-driven changes (the Stage\n * camera) must reach consumers through the framework's own render\n * cycle so surface and overlay commit TOGETHER; only genuinely\n * browser-driven changes (a PageView moving because the document\n * scrolled) use an external listener ({@link observeClientGeometry}).\n */\n\nexport interface AnchoredRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface AnchoredPoint {\n x: number;\n y: number;\n}\n\nexport type AnchoredPlacement = 'top' | 'right' | 'bottom' | 'left';\n\n/** CSS-ready output: absolute/fixed `left`/`top` plus a centering transform. */\nexport interface AnchoredPosition {\n left: number;\n top: number;\n transform: string;\n}\n\n/**\n * Place an upright element around `box` (screen px). `avoid` is a screen\n * point the element must clear (e.g. the rotate knob): the element extends\n * ONLY the edge it sits on, and ONLY when the point protrudes past that\n * edge — so it clears the obstacle without ever shifting off-centre on the\n * other axis. When the point is on another side (e.g. a 90° shape, knob at\n * mid-height for a `top` placement) the edge is untouched and the element\n * stays centred on `box`.\n */\n/** What anchored UI attaches to: a content-space rect on a page, plus\n * optional content-space points the UI must clear (e.g. a rotate knob).\n * Structural — plugin anchor reads satisfy it without importing this\n * package. */\nexport interface AnchorTarget {\n pon: number;\n bounds: AnchoredRect;\n avoid?: AnchoredPoint[];\n}\n\n/**\n * A page surface's PROJECTION SNAPSHOT: how a content-space rect on a page\n * becomes screen coordinates, right now. Provided by `<Stage>`\n * (camera-driven, pure state, no DOM reads) and `<PageView>` (DOM-measured).\n * Deliberately NO subscribe here — when projection changes is a framework\n * binding concern (see the module doc), not part of the snapshot.\n */\nexport interface ViewProjector {\n /**\n * Which coordinate space `toScreen` speaks, and therefore how anchored\n * content mounts:\n * - 'overlay': coords are relative to the surface's own positioned\n * container → rendered in place, position:absolute. (Stage)\n * - 'client': coords are viewport px → portalled to the document body,\n * position:fixed, so no ancestor overflow can clip. (PageView)\n */\n space: 'overlay' | 'client';\n /** Content-space rect on a page → coords in `space`. Null: not projectable\n * right now (page not shown / not measurable yet). */\n toScreen(pon: number, rect: AnchoredRect): AnchoredRect | null;\n toScreenPoint(pon: number, at: AnchoredPoint): AnchoredPoint | null;\n /** The page's live view facts (for anchor reads that need them — e.g. the\n * screen-constant rotate-knob stalk), or null when the page isn't shown. */\n viewEnv(pon: number): {\n scale: number;\n rotation: 0 | 90 | 180 | 270;\n zoom: number;\n } | null;\n}\n\n/**\n * THE anchored projection: anchor → screen position, in one pure call —\n * projection, avoid-point transform, and the shared placement policy.\n * Frameworks recompute this inside their native reactive primitive\n * (a React render, a Vue `computed`, an Angular signal, a Svelte\n * `$derived`); nothing here schedules anything.\n */\nexport function projectAnchoredTarget(\n projector: ViewProjector,\n anchor: AnchorTarget,\n placement: AnchoredPlacement,\n gap: number,\n): AnchoredPosition | null {\n const box = projector.toScreen(anchor.pon, anchor.bounds);\n if (!box) return null;\n const avoid = anchor.avoid?.length\n ? projector.toScreenPoint(anchor.pon, anchor.avoid[0])\n : null;\n return positionAnchoredRect(box, placement, gap, avoid);\n}\n\n/**\n * The one genuinely BROWSER-driven invalidation: an element in normal\n * document flow moves when the document scrolls or the window resizes —\n * no state change announces it. `<PageView>`'s binding registers this;\n * the Stage never does (its camera is state, delivered through render).\n */\nexport function observeClientGeometry(callback: () => void): () => void {\n window.addEventListener('scroll', callback, true);\n window.addEventListener('resize', callback);\n return () => {\n window.removeEventListener('scroll', callback, true);\n window.removeEventListener('resize', callback);\n };\n}\n\nexport function positionAnchoredRect(\n box: AnchoredRect,\n placement: AnchoredPlacement,\n gap: number,\n avoid?: AnchoredPoint | null,\n): AnchoredPosition {\n const cx = box.x + box.width / 2;\n const cy = box.y + box.height / 2;\n\n switch (placement) {\n case 'bottom': {\n const edge = Math.max(box.y + box.height, avoid ? avoid.y : -Infinity);\n return { left: cx, top: edge + gap, transform: 'translate(-50%, 0)' };\n }\n case 'left': {\n const edge = Math.min(box.x, avoid ? avoid.x : Infinity);\n return { left: edge - gap, top: cy, transform: 'translate(-100%, -50%)' };\n }\n case 'right': {\n const edge = Math.max(box.x + box.width, avoid ? avoid.x : -Infinity);\n return { left: edge + gap, top: cy, transform: 'translate(0, -50%)' };\n }\n case 'top':\n default: {\n const edge = Math.min(box.y, avoid ? avoid.y : Infinity);\n return { left: cx, top: edge - gap, transform: 'translate(-50%, -100%)' };\n }\n }\n}\n","/**\n * SVG → CSS cursor string. The armed-tool indicator is the CURSOR itself (the\n * only zero-latency pointer-locked pixel the web has — the OS composites it),\n * so an app hands its toolbar icon here and assigns the result via the\n * interaction hub's `setToolCursor`.\n *\n * Browser rules baked into this module:\n * - The SVG must carry explicit `width`/`height` attributes (not just a\n * viewBox) or Chromium/WebKit ignore it — enforced here.\n * - The hard ceiling is 128×128 CSS px (bigger falls back to the keyword).\n * Above 32×32, Chromium temporarily resets the cursor to default while it\n * would overlap browser UI (anti-spoofing) — fine mid-viewport, so ~40px\n * compositions are common; stay ≤128.\n * - The keyword fallback covers browsers that refuse SVG cursor images.\n *\n * Pure string work with no EmbedPDF types — like the file picker, this module\n * is a plain web utility.\n */\n\nexport interface SvgCursorOptions {\n /** Complete `<svg>` markup, colors baked in (no `currentColor` — a cursor\n * image has no cascade to inherit from). */\n svg: string;\n /** The click point inside the image, CSS px from the top-left. Default 0,0\n * (arrow-style: the image's corner is the tip). */\n hotspot?: { x: number; y: number };\n /** Keyword cursor when the image can't render. Default `'crosshair'`. */\n fallback?: string;\n}\n\n/** Build the CSS cursor value: `url(\"data:image/svg+xml,…\") x y, fallback`. */\nexport function svgCursor({ svg, hotspot, fallback = 'crosshair' }: SvgCursorOptions): string {\n // Scan only the opening `<svg …>` tag (bounded slice) rather than running\n // `/<svg[^>]*\\swidth\\s*=/` over the whole string — the unbounded form is\n // quadratic on pathological inputs (many `<svg` fragments with no `>`),\n // while accepting exactly the same markup: `[^>]*` could never cross a\n // `>` either.\n const tagStart = svg.indexOf('<svg');\n const tagEnd = tagStart >= 0 ? svg.indexOf('>', tagStart) : -1;\n const openTag = tagEnd >= 0 ? svg.slice(tagStart, tagEnd + 1) : '';\n if (!/\\swidth\\s*=/.test(openTag) || !/\\sheight\\s*=/.test(openTag)) {\n throw new Error(\n '[web] svgCursor: the <svg> needs explicit width/height attributes — ' +\n 'browsers ignore cursor images without an intrinsic size',\n );\n }\n const uri = `data:image/svg+xml,${encodeURIComponent(svg)}`;\n const spot = hotspot ? ` ${Math.round(hotspot.x)} ${Math.round(hotspot.y)}` : '';\n return `url(\"${uri}\")${spot}, ${fallback}`;\n}\n","/**\n * External-URI hygiene for PDF link annotations — written ONCE here so every\n * framework adapter (react, vue, svelte, angular) shares the same policy.\n *\n * PDFs are untrusted input: a link's `/URI` can carry anything, including\n * `javascript:` payloads. The default allowlist covers what a document link\n * legitimately opens; everything else (javascript:, file:, data:, vbscript:,\n * custom app schemes) resolves to `null` and the adapters render the link\n * WITHOUT an href — activation still reports through the plugin, so an\n * embedder that needs a custom scheme can handle it in `onActivate`.\n */\nconst DEFAULT_ALLOWED_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:']);\n\n/**\n * Validate a link URI for opening in the browser. Returns the URI when its\n * scheme is allowed (scheme-relative and bare URIs resolve as https), else\n * `null`. Never throws — a malformed URI is just an unopenable one.\n */\nexport function sanitizeExternalUri(\n uri: string,\n allowedSchemes: Iterable<string> = DEFAULT_ALLOWED_SCHEMES,\n): string | null {\n const trimmed = uri.trim();\n if (!trimmed) return null;\n const allowed = new Set(\n [...allowedSchemes].map((s) => (s.endsWith(':') ? s.toLowerCase() : `${s.toLowerCase()}:`)),\n );\n try {\n // Base resolves scheme-relative (\"//host\") and bare (\"example.com/x\" is a\n // relative path → https) forms; an absolute scheme wins over the base.\n const url = new URL(trimmed, 'https://invalid.example/');\n return allowed.has(url.protocol) ? trimmed : null;\n } catch {\n return null;\n }\n}\n","/**\n * The DEFAULT actions UI adapter — the origin×phase visibility matrix plus\n * the browser fallbacks, written ONCE here so every framework binding\n * (react, angular, …) ships the SAME policy instead of forking it per\n * framework. Types are structural twins of `@embedpdf/plugin-actions`'\n * adapter contract (declared here so this package stays plugin-free, per\n * the layering law); assignability is checked where a binding installs it.\n *\n * The matrix (defaults only — an embedder override receives EVERY effect\n * with its context attached and decides for itself):\n * - URIs open through `sanitizeExternalUri` in a new tab (the dispatcher\n * already policy-gated the node; blocked schemes are dropped here).\n * - Print: hover/lifecycle-origin requests never open the dialog; user\n * requests fall back to `globalThis.print`. The `doc.print` AUTHORITY\n * gate is upstream in the plugin and not overridable.\n * - Alert: document-open nags (lifecycle origin, boot phase) never reach\n * `window.alert` — the boot-nag gap, closed.\n * - gotoPage: delegates to the binding-supplied navigator (the stage).\n */\nimport { sanitizeExternalUri } from './external-uri';\n\n/** Structural twin of plugin-actions' `ActionOrigin`. */\nexport type ActionsUiOrigin = 'user' | 'hover' | 'lifecycle';\n\n/** Structural twin of plugin-actions' `ActionUiContext`. */\nexport interface ActionsUiEffectContext {\n origin: ActionsUiOrigin;\n phase: 'boot' | 'user';\n}\n\n/** Structural twin of plugin-actions' `ActionUiAdapter`. */\nexport interface ActionsUiAdapterShape {\n openUri(uri: string, opts: { isMap: boolean; origin: ActionsUiOrigin }): void;\n print(opts?: ActionsUiEffectContext): void;\n alert?(message: string, opts: ActionsUiEffectContext & { icon: number; title?: string }): void;\n gotoPage?(page: number, opts: ActionsUiEffectContext): void;\n}\n\nexport interface DefaultActionsUiAdapterOptions {\n /**\n * Per-handler overrides. Pass a FUNCTION for late binding (a binding's\n * mutable handlers ref) — it is consulted on every effect, so a fresh\n * closure never needs a reinstall.\n */\n overrides?: Partial<ActionsUiAdapterShape> | (() => Partial<ActionsUiAdapterShape> | undefined);\n /** Navigation default for script `this.pageNum = n` requests. */\n goToPage?: (page: number) => void;\n}\n\nexport const createDefaultActionsUiAdapter = (\n options: DefaultActionsUiAdapterOptions = {},\n): ActionsUiAdapterShape => {\n const overridesOf = (): Partial<ActionsUiAdapterShape> | undefined =>\n typeof options.overrides === 'function' ? options.overrides() : options.overrides;\n return {\n openUri: (uri, opts) => {\n const current = overridesOf();\n if (current?.openUri) {\n current.openUri(uri, opts);\n return;\n }\n const href = sanitizeExternalUri(uri);\n if (href && typeof window !== 'undefined') {\n window.open(href, '_blank', 'noopener,noreferrer');\n }\n },\n print: (opts) => {\n const current = overridesOf();\n if (current?.print) current.print(opts);\n else if (opts && opts.origin !== 'user') {\n // Hover/lifecycle scripts never open the print dialog by default.\n } else if (typeof globalThis.print === 'function') globalThis.print();\n },\n alert: (message, opts) => {\n const current = overridesOf();\n if (current?.alert) current.alert(message, opts);\n else if (opts.origin === 'lifecycle' || opts.phase === 'boot') {\n // Document-open nags (Adobe version checks, lifecycle scripts)\n // never alert by default — the boot-nag gap, closed.\n } else if (typeof globalThis.alert === 'function') globalThis.alert(message);\n },\n gotoPage: (page, opts) => {\n const current = overridesOf();\n if (current?.gotoPage) current.gotoPage(page, opts);\n else options.goToPage?.(page);\n },\n };\n};\n","/** A browser image source whose object URL has an explicit lifetime. */\nexport interface ObjectUrlImageSource {\n objectUrl(signal?: AbortSignal): Promise<{ url: string; revoke(): void }>;\n}\n\nexport interface PaintedImageCallbacks {\n onPainted(): void;\n onUnpainted(): void;\n}\n\n/**\n * Bind a raster source to an <img> without ever painting its incomplete-image\n * placeholder. The source becomes visible on load; retained coverage is\n * released only after the browser has had one full frame to present it.\n *\n * Call this from the framework's pre-paint mount hook and invoke the returned\n * cleanup on unmount. Keeping this DOM lifecycle here lets every framework\n * adapter remain a ref plus one bind/unbind hook.\n */\nexport function bindPaintedImage(\n image: HTMLImageElement,\n source: ObjectUrlImageSource,\n callbacks: PaintedImageCallbacks,\n): () => void {\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n let frame = 0;\n let painted = false;\n\n // The element is already committed when a mount hook runs. Hide it before\n // that commit can paint, then reveal only once the bitmap has loaded.\n image.style.visibility = 'hidden';\n\n const onLoad = () => {\n image.style.visibility = 'visible';\n\n // rAF callbacks run before a frame paints. The first frame presents the\n // newly visible image while retained coverage remains; the second reports\n // that presentation opportunity and permits the retained source to leave.\n frame = requestAnimationFrame(() => {\n frame = requestAnimationFrame(() => {\n frame = 0;\n painted = true;\n callbacks.onPainted();\n });\n });\n };\n\n image.addEventListener('load', onLoad, { once: true });\n\n void source.objectUrl(controller.signal).then(\n (objectUrl) => {\n if (controller.signal.aborted) {\n objectUrl.revoke();\n return;\n }\n revoke = objectUrl.revoke;\n image.src = objectUrl.url;\n },\n () => {\n // Aborted or unavailable: remain hidden and retain fallback coverage.\n },\n );\n\n return () => {\n controller.abort();\n if (frame) cancelAnimationFrame(frame);\n image.removeEventListener('load', onLoad);\n image.style.visibility = 'hidden';\n image.removeAttribute('src');\n revoke?.();\n if (painted) callbacks.onUnpainted();\n };\n}\n","/**\n * Platform-feedback providers — the browser implementations of the interaction\n * hub's haptic port (structurally `PlatformFeedback` from\n * `@embedpdf/plugin-interaction`; declared structurally here so this package\n * stays plugin-free, per the layering law).\n *\n * Honesty about reach: `navigator.vibrate` works on Android browsers and is\n * NOT implemented in iOS Safari — there the default provider is a silent\n * no-op until Apple ships a haptics API. (The community switch-control hacks\n * were evaluated and deliberately NOT shipped: Apple patched programmatic\n * triggering in iOS 26.5, leaving only tap-on-control tricks that don't fit a\n * viewer's gesture moments.) The real iPhone haptic today comes through a\n * native shell: a WKWebView embedder installs a script message handler and\n * passes {@link wkFeedback} (or their own provider) instead.\n */\n\n/** Structural twin of plugin-interaction's `PlatformFeedback`. */\nexport interface WebPlatformFeedback {\n selection(): void;\n impact(weight?: 'light' | 'medium' | 'heavy'): void;\n notify(kind: 'success' | 'warning' | 'error'): void;\n}\n\nconst vibrate = (pattern: number | number[]): void => {\n if (typeof navigator !== 'undefined') navigator.vibrate?.(pattern);\n};\n\n/**\n * The default web provider: the Vibration API, mapped onto the three haptic\n * families (selection = the shortest tick; impact scales with weight; notify\n * uses patterns). A no-op wherever vibration is unsupported — notably iOS\n * Safari — so it is always safe to install.\n */\nexport const vibrationFeedback: WebPlatformFeedback = {\n selection: () => vibrate(8),\n impact: (weight = 'light') => vibrate(weight === 'heavy' ? 30 : weight === 'medium' ? 20 : 10),\n notify: (kind) =>\n vibrate(kind === 'error' ? [12, 60, 12, 60, 12] : kind === 'warning' ? [15, 70, 15] : [10, 50, 10]),\n};\n\ninterface WKMessageHandler {\n postMessage(message: unknown): void;\n}\n\n/**\n * A provider for native iOS shells: forwards each call to a WKWebView script\n * message handler the HOST APP installed under `handlerName` — an explicit\n * contract between the embedder and their own app, never a global this library\n * sniffs on its own. The message shape is\n * `{ family: 'selection' } | { family: 'impact', weight } | { family: 'notify', kind }`;\n * the native side maps families onto the matching `UIFeedbackGenerator`.\n * Calls fall back to `fallback` (default: {@link vibrationFeedback}) when the\n * handler is absent, so one provider serves a hybrid web/app deployment.\n */\nexport const wkFeedback = (\n handlerName: string,\n fallback: WebPlatformFeedback = vibrationFeedback,\n): WebPlatformFeedback => {\n const handler = (): WKMessageHandler | undefined => {\n if (typeof window === 'undefined') return undefined;\n const w = window as Window & {\n webkit?: { messageHandlers?: Record<string, WKMessageHandler | undefined> };\n };\n return w.webkit?.messageHandlers?.[handlerName];\n };\n return {\n selection: () => {\n const h = handler();\n if (h) h.postMessage({ family: 'selection' });\n else fallback.selection();\n },\n impact: (weight = 'light') => {\n const h = handler();\n if (h) h.postMessage({ family: 'impact', weight });\n else fallback.impact(weight);\n },\n notify: (kind) => {\n const h = handler();\n if (h) h.postMessage({ family: 'notify', kind });\n else fallback.notify(kind);\n },\n };\n};\n","/**\n * Wheel-input classification for ambient zoom — pure, so every framework\n * adapter shares one feel and the mapping is unit-testable.\n *\n * A ctrl/cmd wheel event is one of three physically different inputs that\n * happen to share an event type, each with its own unit system:\n *\n * • trackpad PINCH — browsers (Chrome/Edge/Firefox) synthesize ctrl+wheel\n * px-deltas scaled to the de-facto convention that `exp(-Δ/100)` tracks\n * the physical finger scale 1:1. Anything weaker feels dead (the content\n * must track the fingers — the Figma/maps feel).\n * • mouse NOTCHES — coarse steps: ±100/±120 px in Chrome, lines/pages mode\n * in Firefox. The pinch mapping would triple per click; a notch instead\n * steps by 1.2, the same ratio as the zoom buttons.\n * • cmd + continuous SCROLL — real scroll px, big and streaming (with\n * momentum): a gentle scrub coefficient. A cmd+mouse notch (±120 px)\n * lands within 0.3% of the 1.2 button step, so it needs no special case.\n *\n * (Safari never synthesizes ctrl+wheel for pinch — it fires proprietary\n * gesture events with an absolute scale; that wiring lives in the adapters.)\n */\nexport interface WheelSample {\n deltaY: number;\n /** WheelEvent.deltaMode: 0 = pixels, 1 = lines, 2 = pages. */\n deltaMode: number;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\n/** The per-notch step for discrete wheels — the zoom buttons' ratio. */\nconst NOTCH = 1.2;\n\n/** Zoom factor for a ctrl/cmd wheel event (the caller decides zoom vs pan). */\nexport function wheelZoomFactor(e: WheelSample): number {\n // Lines/pages mode (Firefox mouse wheels): one button-step per notch.\n if (e.deltaMode !== 0) return Math.pow(NOTCH, -Math.sign(e.deltaY));\n if (e.ctrlKey) {\n // px deltas with ctrl: a real mouse notch arrives big (±100/±120) and\n // steps like the buttons; anything smaller is a synthesized pinch.\n if (Math.abs(e.deltaY) >= 100) return Math.pow(NOTCH, -Math.sign(e.deltaY));\n return Math.exp(-e.deltaY / 100);\n }\n // cmd + continuous scroll: the gentle scrub.\n return Math.exp(-e.deltaY * 0.0015);\n}\n","/**\n * Stage gesture controller — the ONE DOM input binding for a Stage surface,\n * shared by every framework adapter (React, Angular, …) so the feel can never\n * drift between them.\n *\n * The premise (see the stage plugin's camera doctrine): desktop inputs arrive\n * with physics already applied by the OS — momentum lives in the wheel stream,\n * a trackpad pinch is one pre-arbitrated gesture stream. Touch arrives RAW:\n * with `touch-action: none` the platform scroller is out of the loop, so the\n * ballistics (velocity → fling) and the arbitration (is this contact a scroll,\n * a pinch, a tap, or a tool gesture?) must be synthesized here.\n *\n * Arbitration is MODALITY-AWARE:\n * - touch — navigation-first: one finger pans (whatever tool is armed), two\n * fingers pinch-zoom around their centroid, release velocity flings, a tap\n * forwards as a click, a double-tap zoom-toggles, a long-press hands the\n * gesture to the interaction hub (text selection), and a second finger\n * landing mid-tool-gesture CANCELS it into a pinch (the Notes/Procreate\n * convention).\n * - mouse/pen — tool-first, exactly the pre-existing behavior: with a hub,\n * every down/move/up forwards (pan is the pan tool's job); without one,\n * dragging pans. Wheel and Safari-trackpad gesture events are unchanged.\n *\n * Camera writes are rAF-COALESCED: pointer events only update gesture state;\n * one animation-frame tick applies at most one pan and one zoom per frame,\n * inside the host's begin/endGesture transaction. Events may arrive at 120 Hz;\n * the camera moves at display rate.\n *\n * Dependency note: this module speaks to the stage through the STRUCTURAL\n * {@link StageGestureHost} interface (satisfied by `StageCapability`) and to\n * the interaction hub through {@link StageGestureSink} (a closure the adapter\n * builds) — @embedpdf/web stays free of plugin imports, per the layering law.\n */\n\nimport { wheelZoomFactor } from './wheel';\n\nexport type StagePointerKind = 'mouse' | 'pen' | 'touch';\n\n/** What the controller needs from the camera — `StageCapability` satisfies it. */\nexport interface StageGestureHost {\n panBy(dxScreen: number, dyScreen: number): void;\n zoomAround(screenPt: { x: number; y: number }, factor: number): void;\n beginGesture(options?: { elastic?: boolean }): void;\n endGesture(): void;\n /** Momentum pan from a release velocity in screen px/s. */\n fling(velocityX: number, velocityY: number): void;\n /** True while a tween/fling runs — a touch-down then is a \"catch\", not a tap. */\n cameraInMotion(): boolean;\n doubleTapZoom(screenPt: { x: number; y: number }): void;\n}\n\n/**\n * Where non-navigation gestures go — the adapter's bridge to the interaction\n * hub. Every callback receives the ORIGINAL PointerEvent so the adapter can\n * resolve pages/points exactly as it always has. Omit the sink entirely for a\n * hub-less (built-in pan) stage.\n */\nexport interface StageGestureSink {\n down(e: PointerEvent, clickCount: number): void;\n move(e: PointerEvent): void;\n up(e: PointerEvent): void;\n /** The gesture was taken over by navigation (second finger → pinch) or\n * cancelled by the system — abort, don't commit. */\n cancel(e: PointerEvent): void;\n /** Pointer travel with no gesture in flight — cursor feedback. */\n hover(e: PointerEvent): void;\n /** A touch press held still: hand the gesture to the hub (the adapter\n * typically forwards it as a word-select down). Subsequent move/up arrive\n * via {@link move}/{@link up}. */\n longPress(e: PointerEvent): void;\n /**\n * Touch-consent pre-flight, asked at touch-down BEFORE the contact is\n * classified: does a tool have standing to own this contact? True when the\n * armed tool takes fingers wholesale (a drawing tool) or something under\n * the point claims its drags (a selected annotation's body or handles).\n * True routes the whole contact to {@link down}/{@link move}/{@link up} —\n * where a second finger still cancels it into a pinch. Must be a pure\n * read. Absent = never; the contact navigates.\n */\n claimsPoint?(e: PointerEvent): boolean;\n}\n\n/** The wheel fields the zoom classifier reads (see `./wheel`'s `WheelSample`). */\nexport interface StageWheelSample {\n deltaY: number;\n deltaMode: number;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\nexport interface StageGestureOptions {\n /** Ambient zoom (wheel-zoom, pinch-zoom, double-tap zoom). Off: zoom wheels\n * fall through to pan and pinches pan without zooming — but are still\n * swallowed, never page-zooming the browser. Default true. */\n zoomGestures?: boolean;\n /** The wheel → zoom-factor classifier. Defaults to this package's\n * `wheelZoomFactor` (browser wheel classification lives HERE, with the rest\n * of the browser input handling); inject to override or to fake in tests. */\n wheelZoomFactor?: (sample: StageWheelSample) => number;\n /** Tool routing for non-navigation gestures; omit for built-in-pan stages. */\n sink?: StageGestureSink | null;\n /** Touch press duration that becomes a long-press (ms). Default 450. */\n longPressMs?: number;\n /** Finger travel below which a touch stays a tap/press (px). Default 10. */\n tapSlopPx?: number;\n /** Max gap between taps for a double-tap (ms). Default 300. */\n doubleTapMs?: number;\n /** Release speed below which no fling starts (px/s). Default 50. */\n flingMinVelocity?: number;\n}\n\n/**\n * Release velocity from a trail of pointer samples: the mean velocity over the\n * trailing `windowMs` (first-to-last inside the window). Null when the trail is\n * too thin or too stale to trust — the standard \"held still, then let go\"\n * case, which must NOT fling. Pure; exported for tests.\n */\nexport function computeReleaseVelocity(\n samples: ReadonlyArray<{ t: number; x: number; y: number }>,\n now: number,\n windowMs = 100,\n): { vx: number; vy: number } | null {\n let firstIdx = -1;\n for (let i = 0; i < samples.length; i++) {\n if (now - samples[i].t <= windowMs) {\n firstIdx = i;\n break;\n }\n }\n if (firstIdx < 0 || firstIdx === samples.length - 1) return null;\n const first = samples[firstIdx];\n const last = samples[samples.length - 1];\n const dt = last.t - first.t;\n if (dt < 10) return null;\n return { vx: ((last.x - first.x) / dt) * 1000, vy: ((last.y - first.y) / dt) * 1000 };\n}\n\ninterface Tracked {\n id: number;\n kind: StagePointerKind;\n x: number;\n y: number;\n downX: number;\n downY: number;\n}\n\ntype Mode = 'idle' | 'pending' | 'pan' | 'pinch' | 'tool' | 'mousedrag';\n\n/** Attach the gesture controller to a Stage container. Returns the detach fn. */\nexport function createStageGestureController(\n el: HTMLElement,\n host: StageGestureHost,\n options: StageGestureOptions,\n): () => void {\n const zoomGestures = options.zoomGestures ?? true;\n const wheelZoom = options.wheelZoomFactor ?? wheelZoomFactor;\n const sink = options.sink ?? null;\n const LONG_PRESS_MS = options.longPressMs ?? 450;\n const SLOP = options.tapSlopPx ?? 10;\n const DOUBLE_TAP_MS = options.doubleTapMs ?? 300;\n const DOUBLE_TAP_RADIUS = 25;\n const FLING_MIN = options.flingMinVelocity ?? 50;\n const MIN_PINCH_SPAN = 20; // px — below this a span ratio is mostly noise\n const PINCH_RELEASE_GRACE_MS = 160; // leftover finger must OUTLIVE the release\n const SETTLE_SPEED = 0.12; // px/ms — below this the leftover finger has SETTLED\n\n const pointers = new Map<number, Tracked>();\n let mode: Mode = 'idle';\n let began = false; // a host gesture transaction is open\n let suppressTap = false; // this contact CAUGHT a moving camera — never a tap\n let touchToolGesture = false; // 'tool' mode entered via touch long-press\n let downEvent: PointerEvent | null = null; // first touch's down, for tap/long-press forwarding\n let longPressTimer: ReturnType<typeof setTimeout> | null = null;\n let panId = -1;\n // A pan is ARMED once its finger has crossed slop. Fresh touches arm on the\n // pending→pan transition; the finger LEFT OVER when a pinch ends starts\n // DISARMED — a gesture's identity persists through its release, so the\n // leftover contact must re-earn pan-hood exactly like a new finger would.\n // While disarmed, its micro-rolls neither pan nor pollute the velocity\n // trail (which still holds the pinch's CENTROID motion — the momentum a\n // compound gesture actually has). For a GRACE window after the transition\n // the slop base FOLLOWS the finger: in a fast release the leftover finger\n // is still genuinely moving, and distance alone cannot tell that from a\n // deliberate continuation — only outliving the release window can.\n let panArmed = true;\n let panGraceUntil = 0;\n let graceSample = { t: 0, x: 0, y: 0 }; // leftover finger's last observed sample\n // iOS-visible seam: real WebKit can deliver a trailing gesturechange after\n // the last pointerup of a pinch — with no touches left, the desktop-trackpad\n // path would apply a stray end-of-pinch zoom. Any RECENT touch activity\n // therefore suppresses gesture events; desktop trackpads never have any.\n let lastTouchAt = -Infinity;\n\n // rAF application state\n let frame = 0;\n let dirty = false;\n let lastApplied = { x: 0, y: 0 }; // pan focal point, client px\n let lastSpan = 0;\n\n // velocity trail of the pan focal point (finger, or pinch centroid)\n let trail: Array<{ t: number; x: number; y: number }> = [];\n // the pinch's SPAN trail, sampled beside the centroid: at release it decides\n // the gesture's CHARACTER. A fast pinch release has asymmetric finger\n // speeds (the lifting finger flicks away), which moves the TRUE centroid at\n // hundreds of px/s — physically real, but zoom-release noise the platform\n // ignores. Fling only when the centroid rate DOMINATES the span rate\n // (a two-finger pan); a zoom-dominant release ends still.\n let spanTrail: Array<{ t: number; x: number; y: number }> = [];\n\n // tap-pair state for double-tap\n let lastTapT = 0;\n let lastTapX = 0;\n let lastTapY = 0;\n\n // multi-click counter for mouse/pen tool downs (parity with the adapters'\n // previous createClickCounter: 400 ms / 6 px)\n let mLast = 0;\n let mX = 0;\n let mY = 0;\n let mCount = 0;\n const clickCount = (e: PointerEvent): number => {\n const now = Date.now();\n mCount =\n now - mLast <= 400 && Math.hypot(e.clientX - mX, e.clientY - mY) <= 6 ? mCount + 1 : 1;\n mLast = now;\n mX = e.clientX;\n mY = e.clientY;\n return mCount;\n };\n\n const vpt = (clientX: number, clientY: number) => {\n const r = el.getBoundingClientRect();\n return { x: clientX - r.left, y: clientY - r.top };\n };\n const touches = (): Tracked[] => {\n const out: Tracked[] = [];\n pointers.forEach((p) => {\n if (p.kind === 'touch') out.push(p);\n });\n return out;\n };\n\n const clearLongPress = () => {\n if (longPressTimer !== null) {\n clearTimeout(longPressTimer);\n longPressTimer = null;\n }\n };\n const stopFrames = () => {\n if (frame) {\n cancelAnimationFrame(frame);\n frame = 0;\n }\n };\n const begin = (elastic = false) => {\n if (!began) {\n began = true;\n // touch contacts are elastic (rubber-band past the clamp); mouse drags\n // stay rigid — the desktop convention\n host.beginGesture(elastic ? { elastic: true } : undefined);\n }\n };\n const end = () => {\n if (began) {\n began = false;\n host.endGesture();\n }\n };\n const toIdle = () => {\n mode = 'idle';\n downEvent = null;\n touchToolGesture = false;\n trail = [];\n spanTrail = [];\n dirty = false;\n panId = -1;\n panArmed = true;\n panGraceUntil = 0;\n clearLongPress();\n stopFrames();\n };\n const pushSample = (x: number, y: number) => {\n trail.push({ t: performance.now(), x, y });\n if (trail.length > 16) trail.shift();\n };\n const maybeFling = () => {\n const v = computeReleaseVelocity(trail, performance.now());\n if (v && Math.hypot(v.vx, v.vy) >= FLING_MIN) host.fling(v.vx, v.vy);\n };\n\n // ── the application step, shared by the frame loop AND the release paths —\n // a release must flush whatever the last tick hadn't applied yet, or a fast\n // flick loses its final sub-frame of travel and a pinch its last span step.\n const flushPanTo = (x: number, y: number) => {\n const dx = x - lastApplied.x;\n const dy = y - lastApplied.y;\n if (dx !== 0 || dy !== 0) host.panBy(dx, dy);\n lastApplied = { x, y };\n dirty = false;\n };\n const flushPinch = (ax: number, ay: number, bx: number, by: number) => {\n const cx = (ax + bx) / 2;\n const cy = (ay + by) / 2;\n const span = Math.hypot(ax - bx, ay - by);\n const dx = cx - lastApplied.x;\n const dy = cy - lastApplied.y;\n if (dx !== 0 || dy !== 0) host.panBy(dx, dy);\n if (zoomGestures && span > MIN_PINCH_SPAN && lastSpan > MIN_PINCH_SPAN && span !== lastSpan) {\n host.zoomAround(vpt(cx, cy), span / lastSpan);\n }\n // The centroid + span trails sample HERE — once per applied frame, where\n // both fingers are read coherently. Per-event sampling zig-zags (fingers\n // report sequentially, so each single-finger move fakes a half-step of\n // centroid motion) and manufactures phantom release velocity. The span\n // trail is the release gate's evidence of the gesture's CHARACTER.\n pushSample(cx, cy);\n spanTrail.push({ t: performance.now(), x: span, y: 0 });\n if (spanTrail.length > 16) spanTrail.shift();\n lastApplied = { x: cx, y: cy };\n lastSpan = span;\n dirty = false;\n };\n\n // ── the per-frame application (one camera write per frame) ────────────────\n const tick = () => {\n frame = 0;\n if (mode === 'pan' || mode === 'mousedrag') {\n const p = pointers.get(panId);\n if (p && dirty) flushPanTo(p.x, p.y);\n frame = requestAnimationFrame(tick);\n } else if (mode === 'pinch') {\n const [a, b] = touches();\n if (a && b && dirty) flushPinch(a.x, a.y, b.x, b.y);\n frame = requestAnimationFrame(tick);\n }\n };\n const ensureFrames = () => {\n if (!frame) frame = requestAnimationFrame(tick);\n };\n\n const enterPinch = () => {\n const [a, b] = touches();\n if (!a || !b) return;\n mode = 'pinch';\n lastApplied = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n lastSpan = Math.hypot(a.x - b.x, a.y - b.y);\n trail = [];\n dirty = false;\n ensureFrames();\n };\n\n const onLongPress = () => {\n longPressTimer = null;\n if (mode !== 'pending' || !downEvent || !sink) return;\n // The camera transaction closes; the tool owns the rest of this contact.\n end();\n mode = 'tool';\n touchToolGesture = true;\n sink.longPress(downEvent);\n };\n\n // ── listeners ─────────────────────────────────────────────────────────────\n const onDown = (e: PointerEvent) => {\n const kind = (e.pointerType || 'mouse') as StagePointerKind;\n if (kind === 'touch') lastTouchAt = performance.now();\n if (kind !== 'touch') {\n if (kind === 'mouse' && e.button !== 0) return;\n if (mode !== 'idle') return; // an active gesture owns the surface\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n if (sink) {\n // tool-first, exactly the pre-existing hub behavior\n mode = 'tool';\n sink.down(e, clickCount(e));\n } else {\n mode = 'mousedrag';\n begin();\n panId = e.pointerId;\n lastApplied = { x: e.clientX, y: e.clientY };\n ensureFrames();\n }\n return;\n }\n\n // touch\n switch (mode) {\n case 'idle': {\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n // Consent pre-flight — but a MOVING camera always catches first: while\n // content flies under the finger, the touch means \"stop\", never \"grab\n // whatever happens to pass beneath it\".\n const moving = host.cameraInMotion();\n if (!moving && sink?.claimsPoint?.(e)) {\n // A tool owns this contact from the first pixel (selected-annotation\n // drag, or an armed drawing tool). No camera transaction — this is\n // not a navigation gesture; a second finger converts it to a pinch\n // via the 'tool' branch (sink.cancel), exactly like a long-press.\n mode = 'tool';\n touchToolGesture = true;\n sink.down(e, 1);\n break;\n }\n mode = 'pending';\n downEvent = e;\n suppressTap = moving; // a catch, not a tap\n begin(true); // stops any fling/tween under the finger\n trail = [];\n pushSample(e.clientX, e.clientY);\n clearLongPress();\n if (sink && !suppressTap) longPressTimer = setTimeout(onLongPress, LONG_PRESS_MS);\n break;\n }\n case 'pending':\n case 'pan': {\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n clearLongPress();\n enterPinch();\n break;\n }\n case 'tool': {\n // A second finger during a TOUCH tool gesture cancels it into a pinch\n // (the platform convention). Mouse tool gestures ignore stray touches.\n if (!touchToolGesture) return;\n sink?.cancel(e);\n touchToolGesture = false;\n pointers.set(e.pointerId, {\n id: e.pointerId,\n kind,\n x: e.clientX,\n y: e.clientY,\n downX: e.clientX,\n downY: e.clientY,\n });\n begin(true);\n enterPinch();\n break;\n }\n case 'pinch':\n case 'mousedrag':\n return; // ignore extra contacts\n }\n };\n\n const onWindowMove = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n if (p.kind === 'touch') lastTouchAt = performance.now();\n p.x = e.clientX;\n p.y = e.clientY;\n switch (mode) {\n case 'pending': {\n pushSample(p.x, p.y);\n if (Math.hypot(p.x - p.downX, p.y - p.downY) > SLOP) {\n clearLongPress();\n mode = 'pan';\n panId = p.id;\n lastApplied = { x: p.x, y: p.y }; // absorb the slop, like a scroller\n ensureFrames();\n }\n break;\n }\n case 'pan':\n if (p.id !== panId) break;\n if (!panArmed) {\n // pinch-leftover contact. Inside the release-grace window the slop\n // base follows the finger — a fast release keeps moving and must\n // never accumulate distance; only a contact that OUTLIVES the\n // window can re-earn panning by crossing slop from where it\n // settled.\n const nowT = performance.now();\n if (nowT < panGraceUntil) {\n // a finger that SETTLES (speed drops) inside the window is a\n // continuation taking hold — end the grace early so a deliberate\n // pause-then-drag stays responsive\n const dt = nowT - graceSample.t;\n if (dt >= 8) {\n const speed = Math.hypot(p.x - graceSample.x, p.y - graceSample.y) / dt;\n graceSample = { t: nowT, x: p.x, y: p.y };\n if (speed < SETTLE_SPEED) panGraceUntil = 0;\n }\n p.downX = p.x;\n p.downY = p.y;\n break;\n }\n if (Math.hypot(p.x - p.downX, p.y - p.downY) <= SLOP) break;\n panArmed = true;\n lastApplied = { x: p.x, y: p.y }; // absorb, like any fresh pan\n trail = [];\n spanTrail = [];\n }\n pushSample(p.x, p.y);\n dirty = true;\n break;\n case 'pinch':\n // centroid samples are taken per applied FRAME (see flushPinch) —\n // per-event sampling here would zig-zag between the two fingers\n dirty = true;\n break;\n case 'tool':\n sink?.move(e);\n break;\n case 'mousedrag':\n if (p.id === panId) dirty = true;\n break;\n case 'idle':\n break;\n }\n };\n\n // Hover (cursor feedback) — only with no gesture in flight, and only from\n // the element itself, matching the previous adapters.\n const onHoverMove = (e: PointerEvent) => {\n if (mode === 'idle' && sink) sink.hover(e);\n };\n\n const backToSingleFinger = (): boolean => {\n const rest = touches();\n if (rest.length !== 1) return false;\n mode = 'pan';\n panId = rest[0].id;\n panArmed = false; // leftover contact re-earns pan-hood via slop\n panGraceUntil = performance.now() + PINCH_RELEASE_GRACE_MS;\n graceSample = { t: performance.now(), x: rest[0].x, y: rest[0].y };\n rest[0].downX = rest[0].x;\n rest[0].downY = rest[0].y;\n lastApplied = { x: rest[0].x, y: rest[0].y };\n trail = [];\n dirty = false;\n return true;\n };\n\n const onUp = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n if (p.kind === 'touch') lastTouchAt = performance.now();\n pointers.delete(e.pointerId);\n switch (mode) {\n case 'pending': {\n // Slop never exceeded, timer never fired: a tap (or a catch).\n clearLongPress();\n end();\n const now = performance.now();\n const isDouble =\n now - lastTapT <= DOUBLE_TAP_MS &&\n Math.hypot(e.clientX - lastTapX, e.clientY - lastTapY) <= DOUBLE_TAP_RADIUS;\n if (suppressTap) {\n lastTapT = 0; // a catch never counts toward a double-tap\n } else if (zoomGestures && isDouble) {\n lastTapT = 0;\n host.doubleTapZoom(vpt(e.clientX, e.clientY));\n } else {\n lastTapT = now;\n lastTapX = e.clientX;\n lastTapY = e.clientY;\n if (sink && downEvent) {\n // The whole click, delivered at release — tools see exactly the\n // down/up pair they would from a mouse.\n sink.down(downEvent, 1);\n sink.up(e);\n }\n }\n toIdle();\n break;\n }\n case 'pan': {\n if (!panArmed) {\n // The pinch's OTHER finger leaving: the gesture ends as a pinch.\n // No flush (release-rolls are noise). Glide is CHARACTER-gated: a\n // human fast release moves the true centroid (the lifting finger\n // flicks away), so centroid velocity alone lies — the glide fires\n // only when the centroid rate DOMINATES the span rate (a\n // two-finger pan), never on a zoom-dominant release.\n const now = performance.now();\n const v = computeReleaseVelocity(trail, now);\n const vs = computeReleaseVelocity(spanTrail, now);\n end(); // close the bracket first — endGesture owns the elastic settle\n if (v) {\n const speed = Math.hypot(v.vx, v.vy);\n if (speed >= FLING_MIN && speed > Math.abs(vs?.vx ?? 0)) host.fling(v.vx, v.vy);\n }\n toIdle();\n break;\n }\n flushPanTo(e.clientX, e.clientY); // apply the final sub-frame of travel\n pushSample(e.clientX, e.clientY);\n end();\n maybeFling();\n toIdle();\n break;\n }\n case 'pinch': {\n const rest = touches();\n if (rest.length === 1) {\n // The pinch ends AT ITS LAST COHERENT FRAME. No flush here: the\n // lifting finger's release position is fresh but the other one's is\n // stale (its move for this window may not have arrived), and a\n // centroid of two instants is fiction — in a fast pinch that skewed\n // write was the visible end-of-pinch hop, and its poisoned trail\n // sample the phantom fling. Only coherent finger-pairs write the\n // camera; the sub-frame remainder is discarded, as the platform\n // recognizers do. The remaining contact gets a DISARMED pan with\n // the centroid trail preserved and a release-grace window armed.\n mode = 'pan';\n panId = rest[0].id;\n panArmed = false;\n panGraceUntil = performance.now() + PINCH_RELEASE_GRACE_MS;\n graceSample = { t: performance.now(), x: rest[0].x, y: rest[0].y };\n rest[0].downX = rest[0].x;\n rest[0].downY = rest[0].y;\n lastApplied = { x: rest[0].x, y: rest[0].y };\n dirty = false;\n break;\n }\n end();\n maybeFling();\n toIdle();\n break;\n }\n case 'tool': {\n sink?.up(e);\n toIdle();\n break;\n }\n case 'mousedrag': {\n if (p.id !== panId) break;\n flushPanTo(e.clientX, e.clientY);\n end();\n toIdle();\n break;\n }\n case 'idle':\n break;\n }\n };\n\n const onCancel = (e: PointerEvent) => {\n const p = pointers.get(e.pointerId);\n if (!p) return;\n pointers.delete(e.pointerId);\n if (mode === 'tool') {\n sink?.cancel(e);\n toIdle();\n return;\n }\n if (mode === 'pinch' && backToSingleFinger()) return;\n end();\n toIdle();\n };\n\n // Wheel is ambient navigation in BOTH modes: ctrl/meta zooms (classified per\n // input by the injected wheelZoomFactor), else scrolls. With zoom gestures\n // off, a zoom-wheel falls through to ordinary pan.\n const onWheel = (e: WheelEvent) => {\n e.preventDefault();\n if (zoomGestures && (e.ctrlKey || e.metaKey)) {\n host.zoomAround(vpt(e.clientX, e.clientY), wheelZoom(e));\n } else {\n const dx = e.shiftKey ? e.deltaY : e.deltaX;\n const dy = e.shiftKey ? e.deltaX : e.deltaY;\n host.panBy(-dx, -dy);\n }\n };\n\n // Safari's proprietary gesture events. On DESKTOP Safari they are the only\n // trace of a trackpad pinch — convert the absolute scale to per-event ratios.\n // On iOS they fire ALONGSIDE per-finger pointer events; there the pointer\n // path owns the pinch and these are preventDefault-ed only (a pinch over the\n // stage must never page-zoom Safari). The guard is live touch contacts.\n let lastScale = 1;\n const onGestureStart = (e: Event) => {\n e.preventDefault();\n lastScale = (e as unknown as { scale?: number }).scale ?? 1;\n };\n const onGestureChange = (e: Event) => {\n e.preventDefault();\n // iOS: the pointer path owns touch pinches — and a TRAILING gesturechange\n // can arrive after the last pointerup, so suppression keys on RECENT\n // touch activity, not just live contacts. Desktop trackpads have none.\n if (touches().length > 0 || performance.now() - lastTouchAt < 500) return;\n const g = e as unknown as { scale?: number; clientX: number; clientY: number };\n const scale = g.scale ?? 1;\n if (zoomGestures && scale > 0) {\n host.zoomAround(vpt(g.clientX, g.clientY), scale / lastScale);\n }\n lastScale = scale;\n };\n\n el.addEventListener('pointerdown', onDown);\n el.addEventListener('pointermove', onHoverMove);\n window.addEventListener('pointermove', onWindowMove);\n window.addEventListener('pointerup', onUp);\n window.addEventListener('pointercancel', onCancel);\n el.addEventListener('wheel', onWheel, { passive: false });\n const hasGestureEvents = 'GestureEvent' in window;\n if (hasGestureEvents) {\n el.addEventListener('gesturestart', onGestureStart);\n el.addEventListener('gesturechange', onGestureChange);\n el.addEventListener('gestureend', onGestureStart); // reset the base\n }\n\n return () => {\n el.removeEventListener('pointerdown', onDown);\n el.removeEventListener('pointermove', onHoverMove);\n window.removeEventListener('pointermove', onWindowMove);\n window.removeEventListener('pointerup', onUp);\n window.removeEventListener('pointercancel', onCancel);\n el.removeEventListener('wheel', onWheel);\n if (hasGestureEvents) {\n el.removeEventListener('gesturestart', onGestureStart);\n el.removeEventListener('gesturechange', onGestureChange);\n el.removeEventListener('gestureend', onGestureStart);\n }\n clearLongPress();\n stopFrames();\n end(); // balance an open transaction on unmount\n };\n}\n","/**\n * Selection-handle DOM binding — the listener mechanics of dragging one\n * handle element, shared by every framework adapter (the policy — geometry\n * and the drag session — lives in `@embedpdf/plugin-selection`'s pure\n * `handles` module; this file never imports it, per the layering law:\n * structural interfaces only).\n *\n * The load-bearing subtlety this module owns: the pointer-DOWN shield must be\n * a NATIVE listener. The stage's gesture controller listens natively on the\n * container, so a framework-synthetic stopPropagation (React runs its\n * handlers at the root, AFTER the container's native ones) would be too late\n * — the stage would already be panning underneath the handle drag. Once the\n * down is captured here, movement tracks by CLIENT DELTAS from the handle's\n * own base point: no DOM geometry reads, and the client↔overlay conversion\n * cancels out.\n */\n\ninterface SurfacePoint {\n x: number;\n y: number;\n}\n\n/** The armed drag the adapter hands back — plugin-selection's\n * `SelectionHandleDragSession` satisfies it. */\nexport interface SelectionHandleSession {\n move(overlay: SurfacePoint): void;\n end(): void;\n}\n\nexport interface AttachSelectionHandleOptions {\n /**\n * Called at pointer-down to arm a drag: return the drag session plus the\n * handle's current BASE point in overlay space (the bar's midpoint — the\n * point the user grabbed). Return null to decline (endpoint vanished, page\n * not laid out) — the press then does nothing and nothing is captured.\n */\n arm(): { base: SurfacePoint; session: SelectionHandleSession } | null;\n}\n\n/** Bind one handle element. Returns the detach fn. */\nexport function attachSelectionHandle(\n el: HTMLElement,\n options: AttachSelectionHandleOptions,\n): () => void {\n let active: {\n session: SelectionHandleSession;\n base: SurfacePoint;\n baseClient: SurfacePoint;\n pointerId: number;\n } | null = null;\n\n const onMove = (e: PointerEvent) => {\n if (!active || e.pointerId !== active.pointerId) return;\n active.session.move({\n x: active.base.x + (e.clientX - active.baseClient.x),\n y: active.base.y + (e.clientY - active.baseClient.y),\n });\n };\n // Release and system-cancel settle identically: the selection's own commit\n // semantics live in the session; there is no half-state to revert here.\n const onUp = (e: PointerEvent) => {\n if (!active || e.pointerId !== active.pointerId) return;\n const s = active.session;\n active = null;\n s.end();\n };\n const onDown = (e: PointerEvent) => {\n if (active) return;\n const armed = options.arm();\n if (!armed) return;\n e.preventDefault();\n e.stopPropagation(); // native: fires BEFORE the stage controller's listener\n try {\n el.setPointerCapture(e.pointerId);\n } catch {\n // best-effort, like the scrollbar: an already-released pointer\n // (pen/touch races, synthetic events in tests) throws — the drag must\n // still arm, and the window-less capture path still delivers to `el`.\n }\n active = {\n session: armed.session,\n base: armed.base,\n baseClient: { x: e.clientX, y: e.clientY },\n pointerId: e.pointerId,\n };\n };\n\n el.addEventListener('pointerdown', onDown);\n el.addEventListener('pointermove', onMove);\n el.addEventListener('pointerup', onUp);\n el.addEventListener('pointercancel', onUp);\n return () => {\n el.removeEventListener('pointerdown', onDown);\n el.removeEventListener('pointermove', onMove);\n el.removeEventListener('pointerup', onUp);\n el.removeEventListener('pointercancel', onUp);\n active = null;\n };\n}\n","/**\n * Stage surface binding — everything DOM about attaching one element to one\n * stage lens, shared by every framework adapter (React, Angular, …) so the\n * binding can never drift between them:\n *\n * • ResizeObserver → `setViewport` (the shell only reports; placement is the\n * stage plugin's job)\n * • device-pixel-ratio observation → `setDevicePixelRatio` (dppx changes\n * re-subscribe, since the media query's value itself moves)\n * • pointer-sample normalization — the ONE place a PointerEvent becomes a\n * page-resolved, source-stamped sample for the interaction hub\n * • the gesture controller (wheel, Safari trackpad gestures, synthesized\n * touch physics) from `./stage-gestures`\n *\n * The framework adapters keep only their reactive glue: refs/effects, page\n * surfaces, cursor-style subscription, overlay slots.\n *\n * Dependency note: like the gesture controller, this module speaks to the\n * stage and the hub through STRUCTURAL interfaces ({@link StageSurfaceHost},\n * {@link StageSurfaceHub}) — satisfied by `StageCapability` and\n * `InteractionCapability`, imported by neither. @embedpdf/web stays free of\n * plugin imports, per the layering law.\n *\n * Browser-only by nature (ResizeObserver, matchMedia): call from a mounted\n * effect / browser platform check, exactly like the gesture controller.\n */\nimport { createStageGestureController } from './stage-gestures';\nimport type { StageGestureHost, StageGestureSink, StageWheelSample } from './stage-gestures';\n\ninterface SurfacePoint {\n x: number;\n y: number;\n}\n\n/** What the binding needs from the stage — `StageCapability` satisfies it. */\nexport interface StageSurfaceHost extends StageGestureHost {\n setViewport(size: { width: number; height: number }): void;\n setDevicePixelRatio(ratio: number): void;\n /** Viewport point → the page under it (with per-page display context), or null over a gap. */\n pageAt(screen: SurfacePoint): {\n pon: number;\n point: SurfacePoint;\n scale?: number;\n rotation?: 0 | 90 | 180 | 270;\n zoom?: number;\n } | null;\n /** Viewport point → a SPECIFIC page's content space, unclamped (frame-stable projection). */\n pointOnPage(pon: number, screen: SurfacePoint): SurfacePoint | null;\n}\n\n/**\n * The page-resolved sample the binding hands the hub — structurally a\n * `PointerSample` (plugin-interaction). `source` carries the lens identity so\n * the hub can route lens-scoped handlers; with several stages on one document,\n * a drag on one lens must never be captured by another lens's handler.\n */\nexport interface StageSurfaceSample {\n phase: 'down' | 'move' | 'up' | 'cancel';\n viewport: SurfacePoint;\n page?: {\n pon: number;\n point: SurfacePoint;\n scale?: number;\n rotation?: 0 | 90 | 180 | 270;\n zoom?: number;\n };\n project: (pon: number) => SurfacePoint | null;\n modifiers: { shift: boolean; alt: boolean; ctrl: boolean; meta: boolean };\n clickCount: number;\n pointerType: 'mouse' | 'pen' | 'touch';\n gesture?: 'long-press';\n source?: string;\n}\n\n/** What the binding needs from the interaction hub — `InteractionCapability` satisfies it. */\nexport interface StageSurfaceHub {\n dispatch(sample: StageSurfaceSample): void;\n activeTool(): { touchDirect?: boolean };\n wouldClaimTouch(sample: StageSurfaceSample): boolean;\n}\n\nexport interface StageSurfaceOptions {\n /** Route pointer input to the interaction hub (page-resolved samples) instead\n * of built-in drag-to-pan. Omit/null for a hub-less stage. */\n hub?: StageSurfaceHub | null;\n /** The lens identity stamped on every sample (`StageCapability.lensId()`),\n * so lens-scoped handlers only see their own stage's input. */\n source?: string;\n /** Ambient zoom gestures (see {@link StageGestureOptions}). Default true. */\n zoomGestures?: boolean;\n /** Override the wheel → zoom-factor classifier (defaults to `./wheel`). */\n wheelZoomFactor?: (sample: StageWheelSample) => number;\n}\n\n/** Attach the full surface binding. Returns the detach fn. */\nexport function createStageSurface(\n el: HTMLElement,\n stage: StageSurfaceHost,\n options: StageSurfaceOptions = {},\n): () => void {\n const hub = options.hub ?? null;\n const cleanups: Array<() => void> = [];\n\n // Only report the viewport size. Initial placement (home) is the stage\n // plugin's job — it places when it first learns a real size (and a\n // higher-priority initial-view provider can override). The shell stays dumb.\n const setViewport = () => stage.setViewport({ width: el.clientWidth, height: el.clientHeight });\n const ro = new ResizeObserver(setViewport);\n ro.observe(el);\n setViewport();\n cleanups.push(() => ro.disconnect());\n\n // Report the device pixel ratio so page transforms render crisp. dppx changes\n // (browser zoom, dragging between monitors) fire the media query;\n // re-subscribe each time since the query value itself moves.\n let mq: MediaQueryList | null = null;\n const reportDpr = () => {\n stage.setDevicePixelRatio(window.devicePixelRatio || 1);\n mq?.removeEventListener('change', reportDpr);\n mq = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);\n mq.addEventListener('change', reportDpr);\n };\n reportDpr();\n cleanups.push(() => mq?.removeEventListener('change', reportDpr));\n\n // The sink is the hub bridge: it converts events to page-resolved samples\n // (`pageAt` per event, so a drag can cross pages) and stamps the lens source.\n const sampleOf = (\n phase: StageSurfaceSample['phase'],\n e: PointerEvent,\n clickCount = 1,\n gesture?: StageSurfaceSample['gesture'],\n ): StageSurfaceSample => {\n const r = el.getBoundingClientRect();\n const viewport = { x: e.clientX - r.left, y: e.clientY - r.top };\n return {\n phase,\n viewport,\n page: stage.pageAt(viewport) ?? undefined,\n // Page-anchored gestures (annotation move/resize) track the origin\n // page's frame through this even when the cursor is off that page.\n project: (pon) => stage.pointOnPage(pon, viewport),\n modifiers: { shift: e.shiftKey, alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey },\n clickCount,\n pointerType: (e.pointerType || 'mouse') as StageSurfaceSample['pointerType'],\n ...(gesture ? { gesture } : {}),\n ...(options.source ? { source: options.source } : {}),\n };\n };\n const forward = (\n phase: StageSurfaceSample['phase'],\n e: PointerEvent,\n clickCount = 1,\n gesture?: StageSurfaceSample['gesture'],\n ) => {\n hub?.dispatch(sampleOf(phase, e, clickCount, gesture));\n };\n const sink: StageGestureSink | null = hub\n ? {\n down: (e, clickCount) => forward('down', e, clickCount),\n move: (e) => forward('move', e),\n up: (e) => forward('up', e),\n cancel: (e) => forward('cancel', e),\n hover: (e) => forward('move', e), // no owner → the hub routes to onHover\n // Touch long-press = a word-select down (clickCount 2 keeps the\n // word-selection contract; the `gesture` marker is the honest\n // long-press signal for haptics/pickup handlers).\n longPress: (e) => forward('down', e, 2, 'long-press'),\n // Touch consent: an armed drawing/markup tool takes fingers wholesale;\n // otherwise per-point claims (a selected annotation's body or handles)\n // decide. A pure pre-flight — nothing captures.\n claimsPoint: (e) =>\n !!hub.activeTool().touchDirect || hub.wouldClaimTouch(sampleOf('down', e)),\n }\n : null;\n cleanups.push(\n createStageGestureController(el, stage, {\n zoomGestures: options.zoomGestures,\n wheelZoomFactor: options.wheelZoomFactor,\n sink,\n }),\n );\n\n return () => {\n for (const fn of cleanups) fn();\n };\n}\n","/**\n * A bytes-by-id store over IndexedDB — the browser's answer to \"keep these\n * blobs for me\". Structurally the store port a plugin declares for its own\n * persistence (e.g. `StampLibraryStore` in `@embedpdf/plugin-stamp`); declared\n * here as a twin so this package stays plugin-free, per the layering law.\n *\n * IndexedDB rather than localStorage: a library or an image is binary, and a\n * localStorage string store caps at a few MB. One database, one object store,\n * keys are the caller's ids, values are the bytes as stored.\n */\nexport interface ByteStore {\n list(): Promise<Array<{ id: string; bytes: Uint8Array }>>;\n put(id: string, bytes: Uint8Array): Promise<void>;\n delete(id: string): Promise<void>;\n}\n\nexport interface IndexedDbByteStoreOptions {\n /** Object store name inside the database. Default `'bytes'`. */\n storeName?: string;\n}\n\n/** Open (creating on first use) one IndexedDB object store keyed by id. */\nexport function indexedDbByteStore(\n dbName: string,\n opts: IndexedDbByteStoreOptions = {},\n): ByteStore {\n const storeName = opts.storeName ?? 'bytes';\n const open = (): Promise<IDBDatabase> =>\n new Promise((resolve, reject) => {\n const request = indexedDB.open(dbName, 1);\n request.onupgradeneeded = () => {\n if (!request.result.objectStoreNames.contains(storeName)) {\n request.result.createObjectStore(storeName);\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error('indexedDB.open failed'));\n });\n const run = async <T>(\n mode: IDBTransactionMode,\n body: (store: IDBObjectStore) => IDBRequest<T>,\n ): Promise<T> => {\n const db = await open();\n try {\n return await new Promise<T>((resolve, reject) => {\n const request = body(db.transaction(storeName, mode).objectStore(storeName));\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error('indexedDB request failed'));\n });\n } finally {\n db.close();\n }\n };\n return {\n list: async () => {\n const keys = await run<IDBValidKey[]>('readonly', (store) => store.getAllKeys());\n const values = await run<Uint8Array[]>('readonly', (store) => store.getAll());\n return keys.map((key, i) => ({ id: String(key), bytes: new Uint8Array(values[i]) }));\n },\n put: (id, bytes) =>\n run('readwrite', (store) => store.put(new Uint8Array(bytes), id)).then(() => undefined),\n delete: (id) => run('readwrite', (store) => store.delete(id)).then(() => undefined),\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@embedpdf/web",
|
|
3
|
-
"version": "3.0.0-next.
|
|
3
|
+
"version": "3.0.0-next.12",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Framework-free browser adapters for EmbedPDF v3 — the one home for code that touches window/document (file dialog, clipboard, print…). Imported by the framework layers; never by a plugin or a *-core package, which stay DOM-free.",
|
|
6
6
|
"type": "module",
|