@embedpdf/react 3.0.0-next.0 → 3.0.0-next.10

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.
Files changed (104) hide show
  1. package/dist/AnnotationListSnapshot-DDxHpJQU-BG32YXxG.d.cts +347 -0
  2. package/dist/AnnotationListSnapshot-DDxHpJQU-BG32YXxG.d.ts +347 -0
  3. package/dist/anchored.cjs +89 -0
  4. package/dist/anchored.cjs.map +1 -0
  5. package/dist/anchored.d.cts +45 -0
  6. package/dist/anchored.d.ts +45 -0
  7. package/dist/anchored.js +60 -0
  8. package/dist/anchored.js.map +1 -0
  9. package/dist/annotation-menu.cjs +33 -84
  10. package/dist/annotation-menu.cjs.map +1 -1
  11. package/dist/annotation-menu.d.cts +28 -2
  12. package/dist/annotation-menu.d.ts +28 -2
  13. package/dist/annotation-menu.js +27 -78
  14. package/dist/annotation-menu.js.map +1 -1
  15. package/dist/annotation.cjs +752 -16
  16. package/dist/annotation.cjs.map +1 -0
  17. package/dist/annotation.d.cts +159 -2
  18. package/dist/annotation.d.ts +159 -2
  19. package/dist/annotation.js +737 -3
  20. package/dist/annotation.js.map +1 -0
  21. package/dist/commands.js +1 -0
  22. package/dist/commands.js.map +1 -1
  23. package/dist/form.cjs +3 -3
  24. package/dist/form.cjs.map +1 -1
  25. package/dist/form.d.cts +2 -2
  26. package/dist/form.d.ts +2 -2
  27. package/dist/form.js +3 -3
  28. package/dist/form.js.map +1 -1
  29. package/dist/index.cjs +34 -5
  30. package/dist/index.d.cts +14 -8
  31. package/dist/index.d.ts +14 -8
  32. package/dist/index.js +8 -6
  33. package/dist/interaction.cjs +17 -4
  34. package/dist/interaction.cjs.map +1 -1
  35. package/dist/interaction.d.cts +2 -2
  36. package/dist/interaction.d.ts +2 -2
  37. package/dist/interaction.js +7 -5
  38. package/dist/interaction.js.map +1 -1
  39. package/dist/link.cjs +29 -6
  40. package/dist/link.cjs.map +1 -1
  41. package/dist/link.d.cts +12 -3
  42. package/dist/link.d.ts +12 -3
  43. package/dist/link.js +31 -8
  44. package/dist/link.js.map +1 -1
  45. package/dist/metadata.js +1 -0
  46. package/dist/metadata.js.map +1 -1
  47. package/dist/page-edit.js +1 -0
  48. package/dist/page-edit.js.map +1 -1
  49. package/dist/page-view.cjs +62 -38
  50. package/dist/page-view.cjs.map +1 -1
  51. package/dist/page-view.js +59 -37
  52. package/dist/page-view.js.map +1 -1
  53. package/dist/redaction.js +1 -0
  54. package/dist/redaction.js.map +1 -1
  55. package/dist/render.cjs +67 -50
  56. package/dist/render.cjs.map +1 -1
  57. package/dist/render.d.cts +6 -19
  58. package/dist/render.d.ts +6 -19
  59. package/dist/render.js +71 -52
  60. package/dist/render.js.map +1 -1
  61. package/dist/rolldown-runtime-BBjsoOtd.js +27 -0
  62. package/dist/{runtime-DV98q7I0.d.ts → runtime-Cvi0wQ4d.d.cts} +23 -10
  63. package/dist/{runtime-CMxIuF2O.d.cts → runtime-DXxr8T4L.d.ts} +23 -10
  64. package/dist/runtime.cjs +6 -5
  65. package/dist/runtime.cjs.map +1 -1
  66. package/dist/runtime.d.cts +1 -1
  67. package/dist/runtime.d.ts +1 -1
  68. package/dist/runtime.js +7 -5
  69. package/dist/runtime.js.map +1 -1
  70. package/dist/search.cjs +36 -19
  71. package/dist/search.cjs.map +1 -1
  72. package/dist/search.d.cts +1 -1
  73. package/dist/search.d.ts +1 -1
  74. package/dist/search.js +36 -19
  75. package/dist/search.js.map +1 -1
  76. package/dist/selection.cjs +217 -22
  77. package/dist/selection.cjs.map +1 -1
  78. package/dist/selection.d.cts +60 -2
  79. package/dist/selection.d.ts +60 -2
  80. package/dist/selection.js +214 -27
  81. package/dist/selection.js.map +1 -1
  82. package/dist/shell.js +1 -0
  83. package/dist/shell.js.map +1 -1
  84. package/dist/stage.cjs +47 -146
  85. package/dist/stage.cjs.map +1 -1
  86. package/dist/stage.d.cts +18 -5
  87. package/dist/stage.d.ts +18 -5
  88. package/dist/stage.js +50 -148
  89. package/dist/stage.js.map +1 -1
  90. package/dist/stamp.js +1 -0
  91. package/dist/stamp.js.map +1 -1
  92. package/dist/toolbar.js +1 -0
  93. package/dist/toolbar.js.map +1 -1
  94. package/dist/views.js +1 -0
  95. package/dist/views.js.map +1 -1
  96. package/package.json +27 -26
  97. package/dist/AnnotationListSnapshot-W0RCsyDE-dWna4TTJ.d.cts +0 -2593
  98. package/dist/AnnotationListSnapshot-W0RCsyDE-dWna4TTJ.d.ts +0 -2593
  99. package/dist/annotation-B6jwfTCh.d.cts +0 -173
  100. package/dist/annotation-Ct6dFtzJ.cjs +0 -913
  101. package/dist/annotation-Ct6dFtzJ.cjs.map +0 -1
  102. package/dist/annotation-DjGWJljY.js +0 -829
  103. package/dist/annotation-DjGWJljY.js.map +0 -1
  104. package/dist/annotation-nUMefyjw.d.ts +0 -173
@@ -1 +1 @@
1
- {"version":3,"file":"stage.cjs","names":["makePageContext","PageProvider","StageToken","useCapability","useOptionalCapability","InteractionToken","useKernelValue","useDocumentId","useSelector","createClickCounter","settingsEqual"],"sources":["../src/stage.tsx"],"sourcesContent":["/**\n * Stage / RenderLayer + facade hooks.\n *\n * <Stage> virtualizes and positions page surfaces by the camera, and hands each\n * one to YOUR render prop — you bring the layers. (The standalone, Stage-free\n * single-page surface lives in `./page-view` so it never pulls the stage plugin.)\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-stage';\nimport * as React from 'react';\nimport { useEffect, useMemo, useRef } from 'react';\nimport { StageToken, settingsEqual, wheelZoomFactor } from '@embedpdf/plugin-stage';\nimport type { StageCapability, VisiblePage } from '@embedpdf/plugin-stage';\nimport type { CapabilityToken } from '@embedpdf/core';\n\n/** Which stage lens to bind to. Defaults to the main StageToken — pass a custom\n * token to drive an additional lens (e.g. a wrapped thumbnail sidebar). */\nexport type StageTokenProp = CapabilityToken<StageCapability>;\nimport type { PageFrame } from '@embedpdf/core-geometry';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport type { PointerSample } from '@embedpdf/plugin-interaction';\nimport { createClickCounter } from './interaction';\nimport {\n makePageContext,\n PageProvider,\n useCapability,\n useDocumentId,\n useKernelValue,\n useOptionalCapability,\n useSelector,\n} from './runtime';\nimport type { PageContextValue } from './runtime';\n\nfunction PageSurface({\n documentId,\n page,\n frame,\n stage,\n render,\n chrome,\n}: {\n documentId: string;\n page: VisiblePage;\n /** Reserved chrome bands around the page (screen px); the layout reserved the\n * matching space, so the outer box tiles into it. */\n frame: PageFrame;\n /** The stage capability — the demand getter reads visibility LIVE off it. */\n stage: StageCapability;\n render: (page: PageContextValue) => React.ReactNode;\n chrome?: (page: PageContextValue) => React.ReactNode;\n}) {\n const ref = useRef<HTMLDivElement>(null);\n const t = page.transform;\n const rotation = page.rotation;\n // All geometry comes from the transform: the DISPLAY footprint (viewWidth/Height,\n // already w↔h-swapped + device-snapped) and the UN-rotated content box\n // (contentWidth/Height). The shell never re-derives `* zoom` / `* dpr` / snapping.\n const outerW = t.viewWidth + frame.left + frame.right;\n const outerH = t.viewHeight + frame.top + frame.bottom;\n // page.screenX/screenY are the device-snapped footprint top-left; the outer box\n // sits one frame further out so the content keeps its scene position.\n const left = page.screenX - frame.left;\n const top = page.screenY - frame.top;\n // Center the (possibly rotated) content box on the display box and rotate about\n // its center — NO translate(), so rotation 0 carries no transform and pixel-snaps\n // like the axis-aligned shadow behind it (no hairline seam).\n const contentLeft = frame.left + (t.viewWidth - t.contentWidth) / 2;\n const contentTop = frame.top + (t.viewHeight - t.contentHeight) / 2;\n // The page-view DEMAND is a PULL: the getter closes over\n // stable references (capability + pon) and reads the stage's live state at\n // call time — visibility is the STAGE's data (`VisiblePage.visibleRect`),\n // not something an adapter re-derives or caches. Absent from the visible\n // set = zero rect (\"want nothing\"), distinct from PageView's undefined\n // getter (\"whole page\").\n const ctx = useMemo(\n () =>\n makePageContext(\n documentId,\n page.pon,\n page.pageIndex,\n frame,\n t,\n () => ref.current!.getBoundingClientRect(),\n () => {\n const live = stage.visiblePages().find((p) => p.pon === page.pon);\n return live\n ? { desiredDeviceWidth: live.transform.deviceWidth, visibleRect: live.visibleRect }\n : {\n desiredDeviceWidth: t.deviceWidth,\n visibleRect: { x: 0, y: 0, width: 0, height: 0 },\n };\n },\n ),\n [documentId, page.pon, page.pageIndex, frame, t, stage],\n );\n return (\n <div style={{ position: 'absolute', left, top, width: outerW, height: outerH }}>\n <PageProvider value={ctx}>\n {/* drop shadow ONLY — axis-aligned at the content box (inset by the frame),\n transparent fill so it can never peek out behind the bitmap, and it\n stays put under rotation. */}\n <div\n style={{\n position: 'absolute',\n left: frame.left,\n top: frame.top,\n width: t.viewWidth,\n height: t.viewHeight,\n boxShadow: '0 6px 18px rgba(0,0,0,.18)',\n }}\n />\n {/* the page: white backing + bitmap as ONE rasterized box, so there is no\n seam between them and nothing white larger than the bitmap to leak.\n The ONLY thing rotation turns; markers/annotations ride it in content\n coordinates. Rotation 0 carries no transform → pixel-snaps cleanly. */}\n <div\n ref={ref}\n style={{\n position: 'absolute',\n left: contentLeft,\n top: contentTop,\n width: t.contentWidth,\n height: t.contentHeight,\n background: '#fff',\n transform: rotation ? `rotate(${rotation}deg)` : undefined,\n // We render our own selection highlights — suppress native text/image\n // selection (and the double-click image grab) on the whole page subtree.\n userSelect: 'none',\n WebkitUserSelect: 'none',\n }}\n >\n {render(ctx)}\n </div>\n {/* box-space chrome — labels, selection border, per-page buttons — fills\n the outer box, NEVER rotates. Bands are plain regions: a label is\n `bottom:0; height: frame.bottom`, a button row `top:0; height: frame.top`. */}\n {chrome?.(ctx)}\n </PageProvider>\n </div>\n );\n}\n\nexport interface StageProps {\n /**\n * PAGE-SPACE content for each visible page (RenderLayer, annotations,\n * markers). Rendered inside the page's content frame, so it ROTATES with the\n * page's display rotation — coordinates are plain PDF points.\n */\n children: (page: PageContextValue) => React.ReactNode;\n /**\n * BOX-SPACE chrome for each visible page (page-number label, selection\n * border, a per-page rotate/delete button). Rendered into the OUTER box\n * (content + reserved `pageFrame`), so it does NOT rotate and the reserved\n * bands are plain regions (`bottom:0; height: page.frame.bottom`). The three\n * coordinate spaces: `children` (page content), `pageChrome` (page box +\n * frame), `overlay` (viewport).\n */\n pageChrome?: (page: PageContextValue) => React.ReactNode;\n /** Viewport-space UI (menus, controls) rendered above the pages. */\n overlay?: React.ReactNode;\n /**\n * Route this Stage's pointer events to the interaction hub (page-resolved via\n * `pageAt`) instead of the built-in drag-to-pan. Pan then becomes the `pan`\n * tool's job and dragging in `pointer` mode selects text (incl. across pages).\n * Pair with `stagePlugin({ interaction: true })`. Default false (built-in pan).\n */\n interaction?: boolean;\n /**\n * Ambient ZOOM gestures on this stage: ctrl/cmd+wheel and trackpad pinch\n * (Safari gesture events included). Default true. Turn OFF for follower\n * lenses with a fixed magnification — a thumbnail rail should scroll under\n * cmd+wheel, not zoom — so a zoom-wheel falls through to ordinary wheel\n * pan, and pinches are still swallowed (they never page-zoom the browser).\n */\n zoomGestures?: boolean;\n /** The stage lens to drive (default: the main StageToken). */\n token?: StageTokenProp;\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport function Stage({\n children,\n pageChrome,\n overlay,\n interaction = false,\n zoomGestures = true,\n token = StageToken,\n className,\n style,\n}: StageProps) {\n const stage = useCapability(token);\n const ix = useOptionalCapability(InteractionToken);\n const useHub = interaction && !!ix;\n // The hub's resolved cursor (text/grab/…), applied to the viewport when driving.\n const hubCursor = useKernelValue(() => ix?.cursor() ?? 'default');\n const ref = useRef<HTMLDivElement>(null);\n const docId = useDocumentId();\n // visiblePages already folds in the camera (each page carries its device-snapped\n // screenX/screenY + transform), so panning re-emits the list — no separate\n // camera subscription needed for positioning.\n const pages = useSelector(token, (c) => c.visiblePages()); // memoized -> stable ref\n // Reserved chrome bands (screen px), uniform across pages — the frame the\n // outer box reserves and `pageChrome` paints into.\n const frame = useSelector(\n token,\n (c) => c.pageFrame(),\n (a, b) => a.top === b.top && a.right === b.right && a.bottom === b.bottom && a.left === b.left,\n );\n\n useEffect(() => {\n const el = ref.current!;\n // Only report the viewport size. Initial placement (home) is the Stage plugin's\n // job — it watches the viewport and homes once it's ready (and a higher-priority\n // initial-view provider can override that). The shell stays dumb.\n const setVp = () => stage.setViewport({ width: el.clientWidth, height: el.clientHeight });\n const ro = new ResizeObserver(setVp);\n ro.observe(el);\n setVp();\n\n // Report the device pixel ratio so page transforms render crisp. dppx changes\n // (zoom, dragging between monitors) fire the media query; re-subscribe each\n // 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\n // Wheel is ambient navigation in BOTH modes: ctrl/meta zooms (classified\n // per input — synthesized pinch, mouse notch, cmd-scroll scrub — by\n // wheelZoomFactor), else scrolls. With zoom gestures off, a zoom-wheel\n // falls through to ordinary pan (a cmd+scroll over a thumbnail rail\n // scrolls the rail).\n const onWheel = (e: WheelEvent) => {\n e.preventDefault();\n const r = el.getBoundingClientRect();\n if (zoomGestures && (e.ctrlKey || e.metaKey)) {\n stage.zoomAround({ x: e.clientX - r.left, y: e.clientY - r.top }, wheelZoomFactor(e));\n } else {\n const dx = e.shiftKey ? e.deltaY : e.deltaX;\n const dy = e.shiftKey ? e.deltaX : e.deltaY;\n stage.panBy(-dx, -dy);\n }\n };\n el.addEventListener('wheel', onWheel, { passive: false });\n\n // Safari never synthesizes ctrl+wheel for trackpad pinch — it fires\n // proprietary gesture events carrying an ABSOLUTE scale; convert to the\n // per-event ratio the camera physics wants. Feature-detected: Chrome and\n // Firefox don't have GestureEvent, so this wiring costs them nothing.\n // preventDefault runs even with zoom gestures off — a pinch over the\n // stage must never zoom the page itself.\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 const g = e as unknown as { scale?: number; clientX: number; clientY: number };\n const scale = g.scale ?? 1;\n if (zoomGestures && scale > 0) {\n const r = el.getBoundingClientRect();\n stage.zoomAround({ x: g.clientX - r.left, y: g.clientY - r.top }, scale / lastScale);\n }\n lastScale = scale;\n };\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 const cleanups: Array<() => void> = [];\n if (useHub && ix) {\n // Forward to the hub: pan/select/etc. become tool-gated handlers. `pageAt`\n // resolves the page per event, so a drag can cross pages (text selection).\n const clicks = createClickCounter();\n const forward = (phase: PointerSample['phase'], e: PointerEvent, clickCount = 1) => {\n const r = el.getBoundingClientRect();\n const vpt = { x: e.clientX - r.left, y: e.clientY - r.top };\n ix.dispatch({\n phase,\n viewport: vpt,\n page: stage.pageAt(vpt) ?? 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, vpt),\n modifiers: { shift: e.shiftKey, alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey },\n clickCount,\n });\n };\n let dragging = false;\n const down = (e: PointerEvent) => {\n if (e.button !== 0) return;\n dragging = true;\n forward('down', e, clicks(Date.now(), e.clientX, e.clientY));\n };\n const hover = (e: PointerEvent) => {\n if (!dragging) forward('move', e); // cursor feedback, no gesture\n };\n const winMove = (e: PointerEvent) => {\n if (dragging) forward('move', e);\n };\n const up = (e: PointerEvent) => {\n if (!dragging) return;\n dragging = false;\n forward('up', e);\n };\n el.addEventListener('pointerdown', down);\n el.addEventListener('pointermove', hover);\n window.addEventListener('pointermove', winMove);\n window.addEventListener('pointerup', up);\n cleanups.push(() => {\n el.removeEventListener('pointerdown', down);\n el.removeEventListener('pointermove', hover);\n window.removeEventListener('pointermove', winMove);\n window.removeEventListener('pointerup', up);\n });\n } else {\n // Built-in drag-to-pan (no interaction hub) — unchanged behaviour.\n let dragging = false;\n let lx = 0;\n let ly = 0;\n const down = (e: PointerEvent) => {\n if (e.button !== 0) return;\n dragging = true;\n lx = e.clientX;\n ly = e.clientY;\n };\n const move = (e: PointerEvent) => {\n if (!dragging) return;\n stage.panBy(e.clientX - lx, e.clientY - ly);\n lx = e.clientX;\n ly = e.clientY;\n };\n const up = () => {\n dragging = false;\n };\n el.addEventListener('pointerdown', down);\n window.addEventListener('pointermove', move);\n window.addEventListener('pointerup', up);\n cleanups.push(() => {\n el.removeEventListener('pointerdown', down);\n window.removeEventListener('pointermove', move);\n window.removeEventListener('pointerup', up);\n });\n }\n\n return () => {\n ro.disconnect();\n mq?.removeEventListener('change', reportDpr);\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 cleanups.forEach((fn) => fn());\n };\n }, [stage, ix, useHub, zoomGestures]);\n\n return (\n <div\n ref={ref}\n className={className}\n style={{\n position: 'relative',\n overflow: 'hidden',\n touchAction: 'none',\n ...(useHub ? { cursor: hubCursor } : null),\n ...style,\n }}\n >\n {pages.map((p) => (\n <PageSurface\n key={p.pageIndex}\n documentId={docId ?? ''}\n page={p}\n frame={frame}\n stage={stage}\n render={children}\n chrome={pageChrome}\n />\n ))}\n {overlay}\n </div>\n );\n}\n\n// ── Facade hooks — thin sugar over the capability + generic binding ───────────\nexport function useStage(token: StageTokenProp = StageToken) {\n return useCapability(token);\n}\nexport function useZoom(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const zoom = useSelector(token, (c) => c.zoomLevel());\n const mode = useSelector(token, (c) => c.zoomMode());\n return {\n zoom,\n /** Active zoom intent: 'automatic' | 'fit-page' | 'fit-width' | 'fit-all' | 'custom'. */\n mode,\n zoomIn: s.zoomIn,\n zoomOut: s.zoomOut,\n fitWidth: s.fitWidth,\n fitPage: s.fitPage,\n fitAll: s.fitAll,\n automatic: s.automatic,\n zoomTo: s.zoomTo,\n };\n}\nexport function usePages(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const currentPage = useSelector(token, (c) => c.currentPage());\n const pageCount = useSelector(token, (c) => c.pageCount());\n return {\n currentPage,\n pageCount,\n goToPage: s.goToPage,\n next: s.next,\n prev: s.prev,\n reveal: s.reveal,\n };\n}\nexport function useLayout(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const flow = useSelector(token, (c) => c.flow());\n const layout = useSelector(token, (c) => c.layout());\n const spread = useSelector(token, (c) => c.spread());\n const sizing = useSelector(token, (c) => c.sizing());\n const bounded = useSelector(token, (c) => c.bounded());\n return {\n flow,\n layout,\n spread,\n sizing,\n bounded,\n setFlow: s.setFlow,\n setLayout: s.setLayout,\n setSpread: s.setSpread,\n setSizing: s.setSizing,\n setBounded: s.setBounded,\n };\n}\n\n/** The document's page list (with PDF labels) + the current item's pages — the\n * data for page thumbnails / worksheet-style page tabs. */\nexport function usePageList(token: StageTokenProp = StageToken) {\n const pages = useSelector(\n token,\n (c) => c.pages(),\n (a, b) =>\n a.length === b.length && a.every((p, i) => p.pon === b[i].pon && p.label === b[i].label),\n );\n const current = useSelector(\n token,\n (c) => c.currentItemPages(),\n (a, b) => a.length === b.length && a.every((x, i) => x === b[i]),\n );\n return { pages, currentItemPages: current };\n}\n\n/**\n * All Stage settings + the batch `update`. This is the seam for \"presets are a\n * customer concern\": keep your own `Partial<StageSettings>` objects and apply them\n * with `update(preset)` (one anchor-preserving change).\n */\nexport function useStageSettings(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n // settingsEqual derives from the plugin's settings registry — a new setting is\n // covered here automatically, without this package spelling out the shape.\n const settings = useSelector(token, (c) => c.settings(), settingsEqual);\n return { settings, update: s.update, reset: s.resetView };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"stage.cjs","names":["makePageContext","PageProvider","StageToken","useCapability","useOptionalCapability","InteractionToken","useKernelValue","useDocumentId","useSelector","ProjectorProvider","settingsEqual"],"sources":["../src/stage.tsx"],"sourcesContent":["/**\n * Stage / RenderLayer + facade hooks.\n *\n * <Stage> virtualizes and positions page surfaces by the camera, and hands each\n * one to YOUR render prop — you bring the layers. (The standalone, Stage-free\n * single-page surface lives in `./page-view` so it never pulls the stage plugin.)\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-stage';\nimport * as React from 'react';\nimport { useLayoutEffect, useMemo, useRef } from 'react';\nimport { StageToken, createScrollHandler, settingsEqual } from '@embedpdf/plugin-stage';\nimport type { StageCapability, VisiblePage } from '@embedpdf/plugin-stage';\nimport type { CapabilityToken } from '@embedpdf/core';\n\n/** Which stage lens to bind to. Defaults to the main StageToken — pass a custom\n * token to drive an additional lens (e.g. a wrapped thumbnail sidebar). */\nexport type StageTokenProp = CapabilityToken<StageCapability>;\nimport type { PageFrame } from '@embedpdf/core-geometry';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport { createStageSurface } from '@embedpdf/web';\nimport { ProjectorProvider, type ProjectorBinding, type ViewProjector } from './anchored';\nimport {\n makePageContext,\n PageProvider,\n useCapability,\n useDocumentId,\n useKernelValue,\n useOptionalCapability,\n useSelector,\n} from './runtime';\nimport type { PageContextValue } from './runtime';\n\nfunction PageSurface({\n documentId,\n page,\n frame,\n stage,\n render,\n chrome,\n}: {\n documentId: string;\n page: VisiblePage;\n /** Reserved chrome bands around the page (screen px); the layout reserved the\n * matching space, so the outer box tiles into it. */\n frame: PageFrame;\n /** The stage capability — the demand getter reads visibility LIVE off it. */\n stage: StageCapability;\n render: (page: PageContextValue) => React.ReactNode;\n chrome?: (page: PageContextValue) => React.ReactNode;\n}) {\n const ref = useRef<HTMLDivElement>(null);\n const t = page.transform;\n const rotation = page.rotation;\n // All geometry comes from the transform: the DISPLAY footprint (viewWidth/Height,\n // already w↔h-swapped + device-snapped) and the UN-rotated content box\n // (contentWidth/Height). The shell never re-derives `* zoom` / `* dpr` / snapping.\n const outerW = t.viewWidth + frame.left + frame.right;\n const outerH = t.viewHeight + frame.top + frame.bottom;\n // page.screenX/screenY are the device-snapped footprint top-left; the outer box\n // sits one frame further out so the content keeps its scene position.\n const left = page.screenX - frame.left;\n const top = page.screenY - frame.top;\n // Center the (possibly rotated) content box on the display box and rotate about\n // its center — NO translate(), so rotation 0 carries no transform and pixel-snaps\n // like the axis-aligned shadow behind it (no hairline seam).\n const contentLeft = frame.left + (t.viewWidth - t.contentWidth) / 2;\n const contentTop = frame.top + (t.viewHeight - t.contentHeight) / 2;\n // The page-view DEMAND is a PULL: the getter closes over\n // stable references (capability + pon) and reads the stage's live state at\n // call time — visibility is the STAGE's data (`VisiblePage.visibleRect`),\n // not something an adapter re-derives or caches. Absent from the visible\n // set = zero rect (\"want nothing\"), distinct from PageView's undefined\n // getter (\"whole page\").\n const ctx = useMemo(\n () =>\n makePageContext(\n documentId,\n // the hosting lens — per-view raster planning keys tile state by it,\n // so a thumbnail rail and the main view never fight over one plan\n stage.lensId(),\n page.pon,\n page.pageIndex,\n frame,\n t,\n () => ref.current!.getBoundingClientRect(),\n () => {\n const live = stage.visiblePages().find((p) => p.pon === page.pon);\n return live\n ? { desiredDeviceWidth: live.transform.deviceWidth, visibleRect: live.visibleRect }\n : {\n desiredDeviceWidth: t.deviceWidth,\n visibleRect: { x: 0, y: 0, width: 0, height: 0 },\n };\n },\n ),\n [documentId, page.pon, page.pageIndex, frame, t, stage],\n );\n return (\n <div style={{ position: 'absolute', left, top, width: outerW, height: outerH }}>\n <PageProvider value={ctx}>\n {/* drop shadow ONLY — axis-aligned at the content box (inset by the frame),\n transparent fill so it can never peek out behind the bitmap, and it\n stays put under rotation. */}\n <div\n style={{\n position: 'absolute',\n left: frame.left,\n top: frame.top,\n width: t.viewWidth,\n height: t.viewHeight,\n // themeable: override via the CSS variable (app stylesheet), no props\n boxShadow: 'var(--epdf-page-shadow, 0 6px 18px rgba(0,0,0,.18))',\n }}\n />\n {/* the page: white backing + bitmap as ONE rasterized box, so there is no\n seam between them and nothing white larger than the bitmap to leak.\n The ONLY thing rotation turns; markers/annotations ride it in content\n coordinates. Rotation 0 carries no transform → pixel-snaps cleanly. */}\n <div\n ref={ref}\n style={{\n position: 'absolute',\n left: contentLeft,\n top: contentTop,\n width: t.contentWidth,\n height: t.contentHeight,\n background: '#fff',\n transform: rotation ? `rotate(${rotation}deg)` : undefined,\n // We render our own selection highlights — suppress native text/image\n // selection (and the double-click image grab) on the whole page subtree.\n userSelect: 'none',\n WebkitUserSelect: 'none',\n }}\n >\n {render(ctx)}\n </div>\n {/* box-space chrome — labels, selection border, per-page buttons — fills\n the outer box, NEVER rotates. Bands are plain regions: a label is\n `bottom:0; height: frame.bottom`, a button row `top:0; height: frame.top`. */}\n {chrome?.(ctx)}\n </PageProvider>\n </div>\n );\n}\n\nexport interface StageProps {\n /**\n * PAGE-SPACE content for each visible page (RenderLayer, annotations,\n * markers). Rendered inside the page's content frame, so it ROTATES with the\n * page's display rotation — coordinates are plain PDF points.\n */\n children: (page: PageContextValue) => React.ReactNode;\n /**\n * BOX-SPACE chrome for each visible page (page-number label, selection\n * border, a per-page rotate/delete button). Rendered into the OUTER box\n * (content + reserved `pageFrame`), so it does NOT rotate and the reserved\n * bands are plain regions (`bottom:0; height: page.frame.bottom`). The three\n * coordinate spaces: `children` (page content), `pageChrome` (page box +\n * frame), `overlay` (viewport).\n */\n pageChrome?: (page: PageContextValue) => React.ReactNode;\n /** Viewport-space UI (menus, controls) rendered above the pages. */\n overlay?: React.ReactNode;\n /**\n * Route this Stage's pointer events to the interaction hub (page-resolved via\n * `pageAt`) — AND register this lens's tool-gated pan-scroll handler with it\n * (lens-scoped, so multiple stages on one document never pan each other).\n * Pan is then the `pan` tool's job and dragging in `pointer` mode selects\n * text (incl. across pages).\n *\n * Default TRUE: registering `interactionPlugin()` is the one opt-in — tools\n * just work; without the hub this is inert and the stage falls back to\n * built-in drag-to-pan, so a hub-less setup costs nothing. Set `false` on\n * SECONDARY lenses (a thumbnail rail) that should stay click-to-navigate\n * instead of feeding the document's tools.\n */\n interaction?: boolean;\n /**\n * With {@link interaction}: let drags over page GAPS pan regardless of the\n * active tool (and show a grab cursor there) — the gutter always pans; there\n * is nothing to draw/select outside a page. Default true.\n */\n panFallback?: boolean;\n /**\n * Ambient ZOOM gestures on this stage: ctrl/cmd+wheel and trackpad pinch\n * (Safari gesture events included). Default true. Turn OFF for follower\n * lenses with a fixed magnification — a thumbnail rail should scroll under\n * cmd+wheel, not zoom — so a zoom-wheel falls through to ordinary wheel\n * pan, and pinches are still swallowed (they never page-zoom the browser).\n */\n zoomGestures?: boolean;\n /** The stage lens to drive (default: the main StageToken). */\n token?: StageTokenProp;\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport function Stage({\n children,\n pageChrome,\n overlay,\n interaction = true,\n panFallback = true,\n zoomGestures = true,\n token = StageToken,\n className,\n style,\n}: StageProps) {\n const stage = useCapability(token);\n const ix = useOptionalCapability(InteractionToken);\n const useHub = interaction && !!ix;\n // The hub's resolved cursor (text/grab/…), applied to the viewport when driving.\n const hubCursor = useKernelValue(() => ix?.cursor() ?? 'default');\n const ref = useRef<HTMLDivElement>(null);\n const docId = useDocumentId();\n // visiblePages already folds in the camera (each page carries its device-snapped\n // screenX/screenY + transform), so panning re-emits the list — no separate\n // camera subscription needed for positioning.\n const pages = useSelector(token, (c) => c.visiblePages()); // memoized -> stable ref\n // Reserved chrome bands (screen px), uniform across pages — the frame the\n // outer box reserves and `pageChrome` paints into.\n const frame = useSelector(\n token,\n (c) => c.pageFrame(),\n (a, b) => a.top === b.top && a.right === b.right && a.bottom === b.bottom && a.left === b.left,\n );\n\n // The Stage's ViewProjector: anchored UI (menus, popovers) positions through\n // the CAMERA — pure state, no DOM reads, no portal, and NO subscription:\n // `pages` (visiblePages) is the binding's revision, so a camera change\n // re-renders the pages AND every anchored consumer in the SAME React\n // commit — surface and overlay can never paint a frame apart.\n const projector = useMemo<ViewProjector>(\n () => ({\n space: 'overlay',\n toScreen: (pon, rect) => stage.pageRectToScreen(pon, rect),\n toScreenPoint: (pon, at) => {\n const r = stage.pageRectToScreen(pon, { x: at.x, y: at.y, width: 0, height: 0 });\n return r ? { x: r.x, y: r.y } : null;\n },\n viewEnv: (pon) => {\n const t = stage.pageRect(pon)?.transform;\n return t ? { scale: t.viewScale, rotation: t.rotation, zoom: t.zoom } : null;\n },\n }),\n [stage],\n );\n const projectorBinding = useMemo<ProjectorBinding>(\n () => ({ projector, revision: pages }),\n [projector, pages],\n );\n\n useLayoutEffect(() => {\n const el = ref.current!;\n // The WHOLE browser binding — viewport/DPR reporting, sample normalization,\n // gesture controller — is the shared @embedpdf/web surface, so every\n // framework adapter has one feel. This component keeps only React glue.\n const detachSurface = createStageSurface(el, stage, {\n hub: useHub ? ix : null,\n source: stage.lensId(),\n zoomGestures,\n });\n // Interaction opt-in lives WITH the sample source: the same knob that\n // forwards this lens's samples also registers its pan-scroll handler,\n // lens-scoped — two stages on one document can never pan each other.\n const offScroll =\n useHub && ix\n ? ix.registerHandler(createScrollHandler(stage, ix, { panFallback }), {\n source: stage.lensId(),\n })\n : null;\n\n return () => {\n offScroll?.();\n detachSurface();\n };\n }, [stage, ix, useHub, zoomGestures, panFallback]);\n\n return (\n <div\n ref={ref}\n className={className}\n style={{\n position: 'relative',\n overflow: 'hidden',\n touchAction: 'none',\n ...(useHub ? { cursor: hubCursor } : null),\n ...style,\n }}\n >\n {pages.map((p) => (\n <PageSurface\n key={p.pon} // durable page identity — survives move/delete (matches Angular's `track p.pon`)\n documentId={docId ?? ''}\n page={p}\n frame={frame}\n stage={stage}\n render={children}\n chrome={pageChrome}\n />\n ))}\n {/* Anchored UI mounts in the overlay: absolute coords here are the\n projector's overlay space (the stage container). */}\n <ProjectorProvider value={projectorBinding}>{overlay}</ProjectorProvider>\n </div>\n );\n}\n\n// ── Facade hooks — thin sugar over the capability + generic binding ───────────\nexport function useStage(token: StageTokenProp = StageToken) {\n return useCapability(token);\n}\nexport function useZoom(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const zoom = useSelector(token, (c) => c.zoomLevel());\n const mode = useSelector(token, (c) => c.zoomMode());\n return {\n zoom,\n /** Active zoom intent: 'automatic' | 'fit-page' | 'fit-width' | 'fit-all' | 'custom'. */\n mode,\n zoomIn: s.zoomIn,\n zoomOut: s.zoomOut,\n fitWidth: s.fitWidth,\n fitPage: s.fitPage,\n fitAll: s.fitAll,\n automatic: s.automatic,\n zoomTo: s.zoomTo,\n };\n}\nexport function usePages(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const currentPage = useSelector(token, (c) => c.currentPage());\n const pageCount = useSelector(token, (c) => c.pageCount());\n return {\n currentPage,\n pageCount,\n goToPage: s.goToPage,\n next: s.next,\n prev: s.prev,\n reveal: s.reveal,\n };\n}\nexport function useLayout(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const flow = useSelector(token, (c) => c.flow());\n const layout = useSelector(token, (c) => c.layout());\n const spread = useSelector(token, (c) => c.spread());\n const sizing = useSelector(token, (c) => c.sizing());\n const bounded = useSelector(token, (c) => c.bounded());\n return {\n flow,\n layout,\n spread,\n sizing,\n bounded,\n setFlow: s.setFlow,\n setLayout: s.setLayout,\n setSpread: s.setSpread,\n setSizing: s.setSizing,\n setBounded: s.setBounded,\n };\n}\n\n/** The document's page list (with PDF labels) + the current item's pages — the\n * data for page thumbnails / worksheet-style page tabs. */\nexport function usePageList(token: StageTokenProp = StageToken) {\n const pages = useSelector(\n token,\n (c) => c.pages(),\n (a, b) =>\n a.length === b.length && a.every((p, i) => p.pon === b[i].pon && p.label === b[i].label),\n );\n const current = useSelector(\n token,\n (c) => c.currentItemPages(),\n (a, b) => a.length === b.length && a.every((x, i) => x === b[i]),\n );\n return { pages, currentItemPages: current };\n}\n\n/**\n * All Stage settings + the batch `update`. This is the seam for \"presets are a\n * customer concern\": keep your own `Partial<StageSettings>` objects and apply them\n * with `update(preset)` (one anchor-preserving change).\n */\nexport function useStageSettings(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n // settingsEqual derives from the plugin's settings registry — a new setting is\n // covered here automatically, without this package spelling out the shape.\n const settings = useSelector(token, (c) => c.settings(), settingsEqual);\n return { settings, update: s.update, reset: s.resetView };\n}\n"],"mappings":"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"}
package/dist/stage.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { o as PageContextValue } from "./runtime-CMxIuF2O.cjs";
1
+ import { o as PageContextValue } from "./runtime-Cvi0wQ4d.cjs";
2
2
  import * as React from "react";
3
3
  import { CapabilityToken } from "@embedpdf/core";
4
4
  import { StageCapability } from "@embedpdf/plugin-stage";
@@ -27,11 +27,24 @@ interface StageProps {
27
27
  overlay?: React.ReactNode;
28
28
  /**
29
29
  * Route this Stage's pointer events to the interaction hub (page-resolved via
30
- * `pageAt`) instead of the built-in drag-to-pan. Pan then becomes the `pan`
31
- * tool's job and dragging in `pointer` mode selects text (incl. across pages).
32
- * Pair with `stagePlugin({ interaction: true })`. Default false (built-in pan).
30
+ * `pageAt`) — AND register this lens's tool-gated pan-scroll handler with it
31
+ * (lens-scoped, so multiple stages on one document never pan each other).
32
+ * Pan is then the `pan` tool's job and dragging in `pointer` mode selects
33
+ * text (incl. across pages).
34
+ *
35
+ * Default TRUE: registering `interactionPlugin()` is the one opt-in — tools
36
+ * just work; without the hub this is inert and the stage falls back to
37
+ * built-in drag-to-pan, so a hub-less setup costs nothing. Set `false` on
38
+ * SECONDARY lenses (a thumbnail rail) that should stay click-to-navigate
39
+ * instead of feeding the document's tools.
33
40
  */
34
41
  interaction?: boolean;
42
+ /**
43
+ * With {@link interaction}: let drags over page GAPS pan regardless of the
44
+ * active tool (and show a grab cursor there) — the gutter always pans; there
45
+ * is nothing to draw/select outside a page. Default true.
46
+ */
47
+ panFallback?: boolean;
35
48
  /**
36
49
  * Ambient ZOOM gestures on this stage: ctrl/cmd+wheel and trackpad pinch
37
50
  * (Safari gesture events included). Default true. Turn OFF for follower
@@ -45,7 +58,7 @@ interface StageProps {
45
58
  className?: string;
46
59
  style?: React.CSSProperties;
47
60
  }
48
- declare function Stage({ children, pageChrome, overlay, interaction, zoomGestures, token, className, style }: StageProps): import("react/jsx-runtime").JSX.Element;
61
+ declare function Stage({ children, pageChrome, overlay, interaction, panFallback, zoomGestures, token, className, style }: StageProps): import("react/jsx-runtime").JSX.Element;
49
62
  declare function useStage(token?: StageTokenProp): StageCapability;
50
63
  declare function useZoom(token?: StageTokenProp): {
51
64
  zoom: number;
package/dist/stage.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { o as PageContextValue } from "./runtime-DV98q7I0.js";
1
+ import { o as PageContextValue } from "./runtime-DXxr8T4L.js";
2
2
  import { CapabilityToken } from "@embedpdf/core";
3
3
  import * as React from "react";
4
4
  import { StageCapability } from "@embedpdf/plugin-stage";
@@ -27,11 +27,24 @@ interface StageProps {
27
27
  overlay?: React.ReactNode;
28
28
  /**
29
29
  * Route this Stage's pointer events to the interaction hub (page-resolved via
30
- * `pageAt`) instead of the built-in drag-to-pan. Pan then becomes the `pan`
31
- * tool's job and dragging in `pointer` mode selects text (incl. across pages).
32
- * Pair with `stagePlugin({ interaction: true })`. Default false (built-in pan).
30
+ * `pageAt`) — AND register this lens's tool-gated pan-scroll handler with it
31
+ * (lens-scoped, so multiple stages on one document never pan each other).
32
+ * Pan is then the `pan` tool's job and dragging in `pointer` mode selects
33
+ * text (incl. across pages).
34
+ *
35
+ * Default TRUE: registering `interactionPlugin()` is the one opt-in — tools
36
+ * just work; without the hub this is inert and the stage falls back to
37
+ * built-in drag-to-pan, so a hub-less setup costs nothing. Set `false` on
38
+ * SECONDARY lenses (a thumbnail rail) that should stay click-to-navigate
39
+ * instead of feeding the document's tools.
33
40
  */
34
41
  interaction?: boolean;
42
+ /**
43
+ * With {@link interaction}: let drags over page GAPS pan regardless of the
44
+ * active tool (and show a grab cursor there) — the gutter always pans; there
45
+ * is nothing to draw/select outside a page. Default true.
46
+ */
47
+ panFallback?: boolean;
35
48
  /**
36
49
  * Ambient ZOOM gestures on this stage: ctrl/cmd+wheel and trackpad pinch
37
50
  * (Safari gesture events included). Default true. Turn OFF for follower
@@ -45,7 +58,7 @@ interface StageProps {
45
58
  className?: string;
46
59
  style?: React.CSSProperties;
47
60
  }
48
- declare function Stage({ children, pageChrome, overlay, interaction, zoomGestures, token, className, style }: StageProps): import("react/jsx-runtime").JSX.Element;
61
+ declare function Stage({ children, pageChrome, overlay, interaction, panFallback, zoomGestures, token, className, style }: StageProps): import("react/jsx-runtime").JSX.Element;
49
62
  declare function useStage(token?: StageTokenProp): StageCapability;
50
63
  declare function useZoom(token?: StageTokenProp): {
51
64
  zoom: number;
package/dist/stage.js CHANGED
@@ -1,8 +1,10 @@
1
+ import "./rolldown-runtime-BBjsoOtd.js";
1
2
  import { PageProvider, makePageContext, useCapability, useDocumentId, useKernelValue, useOptionalCapability, useSelector } from "./runtime.js";
2
- import { createClickCounter } from "./interaction.js";
3
+ import { ProjectorProvider } from "./anchored.js";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
- import { useEffect, useMemo, useRef } from "react";
5
- import { StageToken, settingsEqual, wheelZoomFactor } from "@embedpdf/plugin-stage";
5
+ import { useLayoutEffect, useMemo, useRef } from "react";
6
+ import { createStageSurface } from "@embedpdf/web";
7
+ import { StageToken, createScrollHandler, settingsEqual } from "@embedpdf/plugin-stage";
6
8
  import { InteractionToken } from "@embedpdf/plugin-interaction";
7
9
  export * from "@embedpdf/plugin-stage";
8
10
  //#region src/stage.tsx
@@ -16,7 +18,7 @@ function PageSurface({ documentId, page, frame, stage, render, chrome }) {
16
18
  const top = page.screenY - frame.top;
17
19
  const contentLeft = frame.left + (t.viewWidth - t.contentWidth) / 2;
18
20
  const contentTop = frame.top + (t.viewHeight - t.contentHeight) / 2;
19
- const ctx = useMemo(() => makePageContext(documentId, page.pon, page.pageIndex, frame, t, () => ref.current.getBoundingClientRect(), () => {
21
+ const ctx = useMemo(() => makePageContext(documentId, stage.lensId(), page.pon, page.pageIndex, frame, t, () => ref.current.getBoundingClientRect(), () => {
20
22
  const live = stage.visiblePages().find((p) => p.pon === page.pon);
21
23
  return live ? {
22
24
  desiredDeviceWidth: live.transform.deviceWidth,
@@ -55,7 +57,7 @@ function PageSurface({ documentId, page, frame, stage, render, chrome }) {
55
57
  top: frame.top,
56
58
  width: t.viewWidth,
57
59
  height: t.viewHeight,
58
- boxShadow: "0 6px 18px rgba(0,0,0,.18)"
60
+ boxShadow: "var(--epdf-page-shadow, 0 6px 18px rgba(0,0,0,.18))"
59
61
  } }),
60
62
  /* @__PURE__ */ jsx("div", {
61
63
  ref,
@@ -77,7 +79,7 @@ function PageSurface({ documentId, page, frame, stage, render, chrome }) {
77
79
  })
78
80
  });
79
81
  }
80
- function Stage({ children, pageChrome, overlay, interaction = false, zoomGestures = true, token = StageToken, className, style }) {
82
+ function Stage({ children, pageChrome, overlay, interaction = true, panFallback = true, zoomGestures = true, token = StageToken, className, style }) {
81
83
  const stage = useCapability(token);
82
84
  const ix = useOptionalCapability(InteractionToken);
83
85
  const useHub = interaction && !!ix;
@@ -86,155 +88,52 @@ function Stage({ children, pageChrome, overlay, interaction = false, zoomGesture
86
88
  const docId = useDocumentId();
87
89
  const pages = useSelector(token, (c) => c.visiblePages());
88
90
  const frame = useSelector(token, (c) => c.pageFrame(), (a, b) => a.top === b.top && a.right === b.right && a.bottom === b.bottom && a.left === b.left);
89
- useEffect(() => {
90
- const el = ref.current;
91
- const setVp = () => stage.setViewport({
92
- width: el.clientWidth,
93
- height: el.clientHeight
94
- });
95
- const ro = new ResizeObserver(setVp);
96
- ro.observe(el);
97
- setVp();
98
- let mq = null;
99
- const reportDpr = () => {
100
- stage.setDevicePixelRatio(window.devicePixelRatio || 1);
101
- mq?.removeEventListener("change", reportDpr);
102
- mq = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
103
- mq.addEventListener("change", reportDpr);
104
- };
105
- reportDpr();
106
- const onWheel = (e) => {
107
- e.preventDefault();
108
- const r = el.getBoundingClientRect();
109
- if (zoomGestures && (e.ctrlKey || e.metaKey)) stage.zoomAround({
110
- x: e.clientX - r.left,
111
- y: e.clientY - r.top
112
- }, wheelZoomFactor(e));
113
- else {
114
- const dx = e.shiftKey ? e.deltaY : e.deltaX;
115
- const dy = e.shiftKey ? e.deltaX : e.deltaY;
116
- stage.panBy(-dx, -dy);
117
- }
118
- };
119
- el.addEventListener("wheel", onWheel, { passive: false });
120
- let lastScale = 1;
121
- const onGestureStart = (e) => {
122
- e.preventDefault();
123
- lastScale = e.scale ?? 1;
124
- };
125
- const onGestureChange = (e) => {
126
- e.preventDefault();
127
- const g = e;
128
- const scale = g.scale ?? 1;
129
- if (zoomGestures && scale > 0) {
130
- const r = el.getBoundingClientRect();
131
- stage.zoomAround({
132
- x: g.clientX - r.left,
133
- y: g.clientY - r.top
134
- }, scale / lastScale);
135
- }
136
- lastScale = scale;
137
- };
138
- const hasGestureEvents = "GestureEvent" in window;
139
- if (hasGestureEvents) {
140
- el.addEventListener("gesturestart", onGestureStart);
141
- el.addEventListener("gesturechange", onGestureChange);
142
- el.addEventListener("gestureend", onGestureStart);
143
- }
144
- const cleanups = [];
145
- if (useHub && ix) {
146
- const clicks = createClickCounter();
147
- const forward = (phase, e, clickCount = 1) => {
148
- const r = el.getBoundingClientRect();
149
- const vpt = {
150
- x: e.clientX - r.left,
151
- y: e.clientY - r.top
152
- };
153
- ix.dispatch({
154
- phase,
155
- viewport: vpt,
156
- page: stage.pageAt(vpt) ?? void 0,
157
- project: (pon) => stage.pointOnPage(pon, vpt),
158
- modifiers: {
159
- shift: e.shiftKey,
160
- alt: e.altKey,
161
- ctrl: e.ctrlKey,
162
- meta: e.metaKey
163
- },
164
- clickCount
165
- });
166
- };
167
- let dragging = false;
168
- const down = (e) => {
169
- if (e.button !== 0) return;
170
- dragging = true;
171
- forward("down", e, clicks(Date.now(), e.clientX, e.clientY));
172
- };
173
- const hover = (e) => {
174
- if (!dragging) forward("move", e);
175
- };
176
- const winMove = (e) => {
177
- if (dragging) forward("move", e);
178
- };
179
- const up = (e) => {
180
- if (!dragging) return;
181
- dragging = false;
182
- forward("up", e);
183
- };
184
- el.addEventListener("pointerdown", down);
185
- el.addEventListener("pointermove", hover);
186
- window.addEventListener("pointermove", winMove);
187
- window.addEventListener("pointerup", up);
188
- cleanups.push(() => {
189
- el.removeEventListener("pointerdown", down);
190
- el.removeEventListener("pointermove", hover);
191
- window.removeEventListener("pointermove", winMove);
192
- window.removeEventListener("pointerup", up);
193
- });
194
- } else {
195
- let dragging = false;
196
- let lx = 0;
197
- let ly = 0;
198
- const down = (e) => {
199
- if (e.button !== 0) return;
200
- dragging = true;
201
- lx = e.clientX;
202
- ly = e.clientY;
203
- };
204
- const move = (e) => {
205
- if (!dragging) return;
206
- stage.panBy(e.clientX - lx, e.clientY - ly);
207
- lx = e.clientX;
208
- ly = e.clientY;
209
- };
210
- const up = () => {
211
- dragging = false;
212
- };
213
- el.addEventListener("pointerdown", down);
214
- window.addEventListener("pointermove", move);
215
- window.addEventListener("pointerup", up);
216
- cleanups.push(() => {
217
- el.removeEventListener("pointerdown", down);
218
- window.removeEventListener("pointermove", move);
219
- window.removeEventListener("pointerup", up);
91
+ const projector = useMemo(() => ({
92
+ space: "overlay",
93
+ toScreen: (pon, rect) => stage.pageRectToScreen(pon, rect),
94
+ toScreenPoint: (pon, at) => {
95
+ const r = stage.pageRectToScreen(pon, {
96
+ x: at.x,
97
+ y: at.y,
98
+ width: 0,
99
+ height: 0
220
100
  });
101
+ return r ? {
102
+ x: r.x,
103
+ y: r.y
104
+ } : null;
105
+ },
106
+ viewEnv: (pon) => {
107
+ const t = stage.pageRect(pon)?.transform;
108
+ return t ? {
109
+ scale: t.viewScale,
110
+ rotation: t.rotation,
111
+ zoom: t.zoom
112
+ } : null;
221
113
  }
114
+ }), [stage]);
115
+ const projectorBinding = useMemo(() => ({
116
+ projector,
117
+ revision: pages
118
+ }), [projector, pages]);
119
+ useLayoutEffect(() => {
120
+ const el = ref.current;
121
+ const detachSurface = createStageSurface(el, stage, {
122
+ hub: useHub ? ix : null,
123
+ source: stage.lensId(),
124
+ zoomGestures
125
+ });
126
+ const offScroll = useHub && ix ? ix.registerHandler(createScrollHandler(stage, ix, { panFallback }), { source: stage.lensId() }) : null;
222
127
  return () => {
223
- ro.disconnect();
224
- mq?.removeEventListener("change", reportDpr);
225
- el.removeEventListener("wheel", onWheel);
226
- if (hasGestureEvents) {
227
- el.removeEventListener("gesturestart", onGestureStart);
228
- el.removeEventListener("gesturechange", onGestureChange);
229
- el.removeEventListener("gestureend", onGestureStart);
230
- }
231
- cleanups.forEach((fn) => fn());
128
+ offScroll?.();
129
+ detachSurface();
232
130
  };
233
131
  }, [
234
132
  stage,
235
133
  ix,
236
134
  useHub,
237
- zoomGestures
135
+ zoomGestures,
136
+ panFallback
238
137
  ]);
239
138
  return /* @__PURE__ */ jsxs("div", {
240
139
  ref,
@@ -253,7 +152,10 @@ function Stage({ children, pageChrome, overlay, interaction = false, zoomGesture
253
152
  stage,
254
153
  render: children,
255
154
  chrome: pageChrome
256
- }, p.pageIndex)), overlay]
155
+ }, p.pon)), /* @__PURE__ */ jsx(ProjectorProvider, {
156
+ value: projectorBinding,
157
+ children: overlay
158
+ })]
257
159
  });
258
160
  }
259
161
  function useStage(token = StageToken) {
package/dist/stage.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"stage.js","names":[],"sources":["../src/stage.tsx"],"sourcesContent":["/**\n * Stage / RenderLayer + facade hooks.\n *\n * <Stage> virtualizes and positions page surfaces by the camera, and hands each\n * one to YOUR render prop — you bring the layers. (The standalone, Stage-free\n * single-page surface lives in `./page-view` so it never pulls the stage plugin.)\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-stage';\nimport * as React from 'react';\nimport { useEffect, useMemo, useRef } from 'react';\nimport { StageToken, settingsEqual, wheelZoomFactor } from '@embedpdf/plugin-stage';\nimport type { StageCapability, VisiblePage } from '@embedpdf/plugin-stage';\nimport type { CapabilityToken } from '@embedpdf/core';\n\n/** Which stage lens to bind to. Defaults to the main StageToken — pass a custom\n * token to drive an additional lens (e.g. a wrapped thumbnail sidebar). */\nexport type StageTokenProp = CapabilityToken<StageCapability>;\nimport type { PageFrame } from '@embedpdf/core-geometry';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport type { PointerSample } from '@embedpdf/plugin-interaction';\nimport { createClickCounter } from './interaction';\nimport {\n makePageContext,\n PageProvider,\n useCapability,\n useDocumentId,\n useKernelValue,\n useOptionalCapability,\n useSelector,\n} from './runtime';\nimport type { PageContextValue } from './runtime';\n\nfunction PageSurface({\n documentId,\n page,\n frame,\n stage,\n render,\n chrome,\n}: {\n documentId: string;\n page: VisiblePage;\n /** Reserved chrome bands around the page (screen px); the layout reserved the\n * matching space, so the outer box tiles into it. */\n frame: PageFrame;\n /** The stage capability — the demand getter reads visibility LIVE off it. */\n stage: StageCapability;\n render: (page: PageContextValue) => React.ReactNode;\n chrome?: (page: PageContextValue) => React.ReactNode;\n}) {\n const ref = useRef<HTMLDivElement>(null);\n const t = page.transform;\n const rotation = page.rotation;\n // All geometry comes from the transform: the DISPLAY footprint (viewWidth/Height,\n // already w↔h-swapped + device-snapped) and the UN-rotated content box\n // (contentWidth/Height). The shell never re-derives `* zoom` / `* dpr` / snapping.\n const outerW = t.viewWidth + frame.left + frame.right;\n const outerH = t.viewHeight + frame.top + frame.bottom;\n // page.screenX/screenY are the device-snapped footprint top-left; the outer box\n // sits one frame further out so the content keeps its scene position.\n const left = page.screenX - frame.left;\n const top = page.screenY - frame.top;\n // Center the (possibly rotated) content box on the display box and rotate about\n // its center — NO translate(), so rotation 0 carries no transform and pixel-snaps\n // like the axis-aligned shadow behind it (no hairline seam).\n const contentLeft = frame.left + (t.viewWidth - t.contentWidth) / 2;\n const contentTop = frame.top + (t.viewHeight - t.contentHeight) / 2;\n // The page-view DEMAND is a PULL: the getter closes over\n // stable references (capability + pon) and reads the stage's live state at\n // call time — visibility is the STAGE's data (`VisiblePage.visibleRect`),\n // not something an adapter re-derives or caches. Absent from the visible\n // set = zero rect (\"want nothing\"), distinct from PageView's undefined\n // getter (\"whole page\").\n const ctx = useMemo(\n () =>\n makePageContext(\n documentId,\n page.pon,\n page.pageIndex,\n frame,\n t,\n () => ref.current!.getBoundingClientRect(),\n () => {\n const live = stage.visiblePages().find((p) => p.pon === page.pon);\n return live\n ? { desiredDeviceWidth: live.transform.deviceWidth, visibleRect: live.visibleRect }\n : {\n desiredDeviceWidth: t.deviceWidth,\n visibleRect: { x: 0, y: 0, width: 0, height: 0 },\n };\n },\n ),\n [documentId, page.pon, page.pageIndex, frame, t, stage],\n );\n return (\n <div style={{ position: 'absolute', left, top, width: outerW, height: outerH }}>\n <PageProvider value={ctx}>\n {/* drop shadow ONLY — axis-aligned at the content box (inset by the frame),\n transparent fill so it can never peek out behind the bitmap, and it\n stays put under rotation. */}\n <div\n style={{\n position: 'absolute',\n left: frame.left,\n top: frame.top,\n width: t.viewWidth,\n height: t.viewHeight,\n boxShadow: '0 6px 18px rgba(0,0,0,.18)',\n }}\n />\n {/* the page: white backing + bitmap as ONE rasterized box, so there is no\n seam between them and nothing white larger than the bitmap to leak.\n The ONLY thing rotation turns; markers/annotations ride it in content\n coordinates. Rotation 0 carries no transform → pixel-snaps cleanly. */}\n <div\n ref={ref}\n style={{\n position: 'absolute',\n left: contentLeft,\n top: contentTop,\n width: t.contentWidth,\n height: t.contentHeight,\n background: '#fff',\n transform: rotation ? `rotate(${rotation}deg)` : undefined,\n // We render our own selection highlights — suppress native text/image\n // selection (and the double-click image grab) on the whole page subtree.\n userSelect: 'none',\n WebkitUserSelect: 'none',\n }}\n >\n {render(ctx)}\n </div>\n {/* box-space chrome — labels, selection border, per-page buttons — fills\n the outer box, NEVER rotates. Bands are plain regions: a label is\n `bottom:0; height: frame.bottom`, a button row `top:0; height: frame.top`. */}\n {chrome?.(ctx)}\n </PageProvider>\n </div>\n );\n}\n\nexport interface StageProps {\n /**\n * PAGE-SPACE content for each visible page (RenderLayer, annotations,\n * markers). Rendered inside the page's content frame, so it ROTATES with the\n * page's display rotation — coordinates are plain PDF points.\n */\n children: (page: PageContextValue) => React.ReactNode;\n /**\n * BOX-SPACE chrome for each visible page (page-number label, selection\n * border, a per-page rotate/delete button). Rendered into the OUTER box\n * (content + reserved `pageFrame`), so it does NOT rotate and the reserved\n * bands are plain regions (`bottom:0; height: page.frame.bottom`). The three\n * coordinate spaces: `children` (page content), `pageChrome` (page box +\n * frame), `overlay` (viewport).\n */\n pageChrome?: (page: PageContextValue) => React.ReactNode;\n /** Viewport-space UI (menus, controls) rendered above the pages. */\n overlay?: React.ReactNode;\n /**\n * Route this Stage's pointer events to the interaction hub (page-resolved via\n * `pageAt`) instead of the built-in drag-to-pan. Pan then becomes the `pan`\n * tool's job and dragging in `pointer` mode selects text (incl. across pages).\n * Pair with `stagePlugin({ interaction: true })`. Default false (built-in pan).\n */\n interaction?: boolean;\n /**\n * Ambient ZOOM gestures on this stage: ctrl/cmd+wheel and trackpad pinch\n * (Safari gesture events included). Default true. Turn OFF for follower\n * lenses with a fixed magnification — a thumbnail rail should scroll under\n * cmd+wheel, not zoom — so a zoom-wheel falls through to ordinary wheel\n * pan, and pinches are still swallowed (they never page-zoom the browser).\n */\n zoomGestures?: boolean;\n /** The stage lens to drive (default: the main StageToken). */\n token?: StageTokenProp;\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport function Stage({\n children,\n pageChrome,\n overlay,\n interaction = false,\n zoomGestures = true,\n token = StageToken,\n className,\n style,\n}: StageProps) {\n const stage = useCapability(token);\n const ix = useOptionalCapability(InteractionToken);\n const useHub = interaction && !!ix;\n // The hub's resolved cursor (text/grab/…), applied to the viewport when driving.\n const hubCursor = useKernelValue(() => ix?.cursor() ?? 'default');\n const ref = useRef<HTMLDivElement>(null);\n const docId = useDocumentId();\n // visiblePages already folds in the camera (each page carries its device-snapped\n // screenX/screenY + transform), so panning re-emits the list — no separate\n // camera subscription needed for positioning.\n const pages = useSelector(token, (c) => c.visiblePages()); // memoized -> stable ref\n // Reserved chrome bands (screen px), uniform across pages — the frame the\n // outer box reserves and `pageChrome` paints into.\n const frame = useSelector(\n token,\n (c) => c.pageFrame(),\n (a, b) => a.top === b.top && a.right === b.right && a.bottom === b.bottom && a.left === b.left,\n );\n\n useEffect(() => {\n const el = ref.current!;\n // Only report the viewport size. Initial placement (home) is the Stage plugin's\n // job — it watches the viewport and homes once it's ready (and a higher-priority\n // initial-view provider can override that). The shell stays dumb.\n const setVp = () => stage.setViewport({ width: el.clientWidth, height: el.clientHeight });\n const ro = new ResizeObserver(setVp);\n ro.observe(el);\n setVp();\n\n // Report the device pixel ratio so page transforms render crisp. dppx changes\n // (zoom, dragging between monitors) fire the media query; re-subscribe each\n // 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\n // Wheel is ambient navigation in BOTH modes: ctrl/meta zooms (classified\n // per input — synthesized pinch, mouse notch, cmd-scroll scrub — by\n // wheelZoomFactor), else scrolls. With zoom gestures off, a zoom-wheel\n // falls through to ordinary pan (a cmd+scroll over a thumbnail rail\n // scrolls the rail).\n const onWheel = (e: WheelEvent) => {\n e.preventDefault();\n const r = el.getBoundingClientRect();\n if (zoomGestures && (e.ctrlKey || e.metaKey)) {\n stage.zoomAround({ x: e.clientX - r.left, y: e.clientY - r.top }, wheelZoomFactor(e));\n } else {\n const dx = e.shiftKey ? e.deltaY : e.deltaX;\n const dy = e.shiftKey ? e.deltaX : e.deltaY;\n stage.panBy(-dx, -dy);\n }\n };\n el.addEventListener('wheel', onWheel, { passive: false });\n\n // Safari never synthesizes ctrl+wheel for trackpad pinch — it fires\n // proprietary gesture events carrying an ABSOLUTE scale; convert to the\n // per-event ratio the camera physics wants. Feature-detected: Chrome and\n // Firefox don't have GestureEvent, so this wiring costs them nothing.\n // preventDefault runs even with zoom gestures off — a pinch over the\n // stage must never zoom the page itself.\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 const g = e as unknown as { scale?: number; clientX: number; clientY: number };\n const scale = g.scale ?? 1;\n if (zoomGestures && scale > 0) {\n const r = el.getBoundingClientRect();\n stage.zoomAround({ x: g.clientX - r.left, y: g.clientY - r.top }, scale / lastScale);\n }\n lastScale = scale;\n };\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 const cleanups: Array<() => void> = [];\n if (useHub && ix) {\n // Forward to the hub: pan/select/etc. become tool-gated handlers. `pageAt`\n // resolves the page per event, so a drag can cross pages (text selection).\n const clicks = createClickCounter();\n const forward = (phase: PointerSample['phase'], e: PointerEvent, clickCount = 1) => {\n const r = el.getBoundingClientRect();\n const vpt = { x: e.clientX - r.left, y: e.clientY - r.top };\n ix.dispatch({\n phase,\n viewport: vpt,\n page: stage.pageAt(vpt) ?? 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, vpt),\n modifiers: { shift: e.shiftKey, alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey },\n clickCount,\n });\n };\n let dragging = false;\n const down = (e: PointerEvent) => {\n if (e.button !== 0) return;\n dragging = true;\n forward('down', e, clicks(Date.now(), e.clientX, e.clientY));\n };\n const hover = (e: PointerEvent) => {\n if (!dragging) forward('move', e); // cursor feedback, no gesture\n };\n const winMove = (e: PointerEvent) => {\n if (dragging) forward('move', e);\n };\n const up = (e: PointerEvent) => {\n if (!dragging) return;\n dragging = false;\n forward('up', e);\n };\n el.addEventListener('pointerdown', down);\n el.addEventListener('pointermove', hover);\n window.addEventListener('pointermove', winMove);\n window.addEventListener('pointerup', up);\n cleanups.push(() => {\n el.removeEventListener('pointerdown', down);\n el.removeEventListener('pointermove', hover);\n window.removeEventListener('pointermove', winMove);\n window.removeEventListener('pointerup', up);\n });\n } else {\n // Built-in drag-to-pan (no interaction hub) — unchanged behaviour.\n let dragging = false;\n let lx = 0;\n let ly = 0;\n const down = (e: PointerEvent) => {\n if (e.button !== 0) return;\n dragging = true;\n lx = e.clientX;\n ly = e.clientY;\n };\n const move = (e: PointerEvent) => {\n if (!dragging) return;\n stage.panBy(e.clientX - lx, e.clientY - ly);\n lx = e.clientX;\n ly = e.clientY;\n };\n const up = () => {\n dragging = false;\n };\n el.addEventListener('pointerdown', down);\n window.addEventListener('pointermove', move);\n window.addEventListener('pointerup', up);\n cleanups.push(() => {\n el.removeEventListener('pointerdown', down);\n window.removeEventListener('pointermove', move);\n window.removeEventListener('pointerup', up);\n });\n }\n\n return () => {\n ro.disconnect();\n mq?.removeEventListener('change', reportDpr);\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 cleanups.forEach((fn) => fn());\n };\n }, [stage, ix, useHub, zoomGestures]);\n\n return (\n <div\n ref={ref}\n className={className}\n style={{\n position: 'relative',\n overflow: 'hidden',\n touchAction: 'none',\n ...(useHub ? { cursor: hubCursor } : null),\n ...style,\n }}\n >\n {pages.map((p) => (\n <PageSurface\n key={p.pageIndex}\n documentId={docId ?? ''}\n page={p}\n frame={frame}\n stage={stage}\n render={children}\n chrome={pageChrome}\n />\n ))}\n {overlay}\n </div>\n );\n}\n\n// ── Facade hooks — thin sugar over the capability + generic binding ───────────\nexport function useStage(token: StageTokenProp = StageToken) {\n return useCapability(token);\n}\nexport function useZoom(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const zoom = useSelector(token, (c) => c.zoomLevel());\n const mode = useSelector(token, (c) => c.zoomMode());\n return {\n zoom,\n /** Active zoom intent: 'automatic' | 'fit-page' | 'fit-width' | 'fit-all' | 'custom'. */\n mode,\n zoomIn: s.zoomIn,\n zoomOut: s.zoomOut,\n fitWidth: s.fitWidth,\n fitPage: s.fitPage,\n fitAll: s.fitAll,\n automatic: s.automatic,\n zoomTo: s.zoomTo,\n };\n}\nexport function usePages(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const currentPage = useSelector(token, (c) => c.currentPage());\n const pageCount = useSelector(token, (c) => c.pageCount());\n return {\n currentPage,\n pageCount,\n goToPage: s.goToPage,\n next: s.next,\n prev: s.prev,\n reveal: s.reveal,\n };\n}\nexport function useLayout(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const flow = useSelector(token, (c) => c.flow());\n const layout = useSelector(token, (c) => c.layout());\n const spread = useSelector(token, (c) => c.spread());\n const sizing = useSelector(token, (c) => c.sizing());\n const bounded = useSelector(token, (c) => c.bounded());\n return {\n flow,\n layout,\n spread,\n sizing,\n bounded,\n setFlow: s.setFlow,\n setLayout: s.setLayout,\n setSpread: s.setSpread,\n setSizing: s.setSizing,\n setBounded: s.setBounded,\n };\n}\n\n/** The document's page list (with PDF labels) + the current item's pages — the\n * data for page thumbnails / worksheet-style page tabs. */\nexport function usePageList(token: StageTokenProp = StageToken) {\n const pages = useSelector(\n token,\n (c) => c.pages(),\n (a, b) =>\n a.length === b.length && a.every((p, i) => p.pon === b[i].pon && p.label === b[i].label),\n );\n const current = useSelector(\n token,\n (c) => c.currentItemPages(),\n (a, b) => a.length === b.length && a.every((x, i) => x === b[i]),\n );\n return { pages, currentItemPages: current };\n}\n\n/**\n * All Stage settings + the batch `update`. This is the seam for \"presets are a\n * customer concern\": keep your own `Partial<StageSettings>` objects and apply them\n * with `update(preset)` (one anchor-preserving change).\n */\nexport function useStageSettings(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n // settingsEqual derives from the plugin's settings registry — a new setting is\n // covered here automatically, without this package spelling out the shape.\n const settings = useSelector(token, (c) => c.settings(), settingsEqual);\n return { settings, update: s.update, reset: s.resetView };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"stage.js","names":[],"sources":["../src/stage.tsx"],"sourcesContent":["/**\n * Stage / RenderLayer + facade hooks.\n *\n * <Stage> virtualizes and positions page surfaces by the camera, and hands each\n * one to YOUR render prop — you bring the layers. (The standalone, Stage-free\n * single-page surface lives in `./page-view` so it never pulls the stage plugin.)\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-stage';\nimport * as React from 'react';\nimport { useLayoutEffect, useMemo, useRef } from 'react';\nimport { StageToken, createScrollHandler, settingsEqual } from '@embedpdf/plugin-stage';\nimport type { StageCapability, VisiblePage } from '@embedpdf/plugin-stage';\nimport type { CapabilityToken } from '@embedpdf/core';\n\n/** Which stage lens to bind to. Defaults to the main StageToken — pass a custom\n * token to drive an additional lens (e.g. a wrapped thumbnail sidebar). */\nexport type StageTokenProp = CapabilityToken<StageCapability>;\nimport type { PageFrame } from '@embedpdf/core-geometry';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport { createStageSurface } from '@embedpdf/web';\nimport { ProjectorProvider, type ProjectorBinding, type ViewProjector } from './anchored';\nimport {\n makePageContext,\n PageProvider,\n useCapability,\n useDocumentId,\n useKernelValue,\n useOptionalCapability,\n useSelector,\n} from './runtime';\nimport type { PageContextValue } from './runtime';\n\nfunction PageSurface({\n documentId,\n page,\n frame,\n stage,\n render,\n chrome,\n}: {\n documentId: string;\n page: VisiblePage;\n /** Reserved chrome bands around the page (screen px); the layout reserved the\n * matching space, so the outer box tiles into it. */\n frame: PageFrame;\n /** The stage capability — the demand getter reads visibility LIVE off it. */\n stage: StageCapability;\n render: (page: PageContextValue) => React.ReactNode;\n chrome?: (page: PageContextValue) => React.ReactNode;\n}) {\n const ref = useRef<HTMLDivElement>(null);\n const t = page.transform;\n const rotation = page.rotation;\n // All geometry comes from the transform: the DISPLAY footprint (viewWidth/Height,\n // already w↔h-swapped + device-snapped) and the UN-rotated content box\n // (contentWidth/Height). The shell never re-derives `* zoom` / `* dpr` / snapping.\n const outerW = t.viewWidth + frame.left + frame.right;\n const outerH = t.viewHeight + frame.top + frame.bottom;\n // page.screenX/screenY are the device-snapped footprint top-left; the outer box\n // sits one frame further out so the content keeps its scene position.\n const left = page.screenX - frame.left;\n const top = page.screenY - frame.top;\n // Center the (possibly rotated) content box on the display box and rotate about\n // its center — NO translate(), so rotation 0 carries no transform and pixel-snaps\n // like the axis-aligned shadow behind it (no hairline seam).\n const contentLeft = frame.left + (t.viewWidth - t.contentWidth) / 2;\n const contentTop = frame.top + (t.viewHeight - t.contentHeight) / 2;\n // The page-view DEMAND is a PULL: the getter closes over\n // stable references (capability + pon) and reads the stage's live state at\n // call time — visibility is the STAGE's data (`VisiblePage.visibleRect`),\n // not something an adapter re-derives or caches. Absent from the visible\n // set = zero rect (\"want nothing\"), distinct from PageView's undefined\n // getter (\"whole page\").\n const ctx = useMemo(\n () =>\n makePageContext(\n documentId,\n // the hosting lens — per-view raster planning keys tile state by it,\n // so a thumbnail rail and the main view never fight over one plan\n stage.lensId(),\n page.pon,\n page.pageIndex,\n frame,\n t,\n () => ref.current!.getBoundingClientRect(),\n () => {\n const live = stage.visiblePages().find((p) => p.pon === page.pon);\n return live\n ? { desiredDeviceWidth: live.transform.deviceWidth, visibleRect: live.visibleRect }\n : {\n desiredDeviceWidth: t.deviceWidth,\n visibleRect: { x: 0, y: 0, width: 0, height: 0 },\n };\n },\n ),\n [documentId, page.pon, page.pageIndex, frame, t, stage],\n );\n return (\n <div style={{ position: 'absolute', left, top, width: outerW, height: outerH }}>\n <PageProvider value={ctx}>\n {/* drop shadow ONLY — axis-aligned at the content box (inset by the frame),\n transparent fill so it can never peek out behind the bitmap, and it\n stays put under rotation. */}\n <div\n style={{\n position: 'absolute',\n left: frame.left,\n top: frame.top,\n width: t.viewWidth,\n height: t.viewHeight,\n // themeable: override via the CSS variable (app stylesheet), no props\n boxShadow: 'var(--epdf-page-shadow, 0 6px 18px rgba(0,0,0,.18))',\n }}\n />\n {/* the page: white backing + bitmap as ONE rasterized box, so there is no\n seam between them and nothing white larger than the bitmap to leak.\n The ONLY thing rotation turns; markers/annotations ride it in content\n coordinates. Rotation 0 carries no transform → pixel-snaps cleanly. */}\n <div\n ref={ref}\n style={{\n position: 'absolute',\n left: contentLeft,\n top: contentTop,\n width: t.contentWidth,\n height: t.contentHeight,\n background: '#fff',\n transform: rotation ? `rotate(${rotation}deg)` : undefined,\n // We render our own selection highlights — suppress native text/image\n // selection (and the double-click image grab) on the whole page subtree.\n userSelect: 'none',\n WebkitUserSelect: 'none',\n }}\n >\n {render(ctx)}\n </div>\n {/* box-space chrome — labels, selection border, per-page buttons — fills\n the outer box, NEVER rotates. Bands are plain regions: a label is\n `bottom:0; height: frame.bottom`, a button row `top:0; height: frame.top`. */}\n {chrome?.(ctx)}\n </PageProvider>\n </div>\n );\n}\n\nexport interface StageProps {\n /**\n * PAGE-SPACE content for each visible page (RenderLayer, annotations,\n * markers). Rendered inside the page's content frame, so it ROTATES with the\n * page's display rotation — coordinates are plain PDF points.\n */\n children: (page: PageContextValue) => React.ReactNode;\n /**\n * BOX-SPACE chrome for each visible page (page-number label, selection\n * border, a per-page rotate/delete button). Rendered into the OUTER box\n * (content + reserved `pageFrame`), so it does NOT rotate and the reserved\n * bands are plain regions (`bottom:0; height: page.frame.bottom`). The three\n * coordinate spaces: `children` (page content), `pageChrome` (page box +\n * frame), `overlay` (viewport).\n */\n pageChrome?: (page: PageContextValue) => React.ReactNode;\n /** Viewport-space UI (menus, controls) rendered above the pages. */\n overlay?: React.ReactNode;\n /**\n * Route this Stage's pointer events to the interaction hub (page-resolved via\n * `pageAt`) — AND register this lens's tool-gated pan-scroll handler with it\n * (lens-scoped, so multiple stages on one document never pan each other).\n * Pan is then the `pan` tool's job and dragging in `pointer` mode selects\n * text (incl. across pages).\n *\n * Default TRUE: registering `interactionPlugin()` is the one opt-in — tools\n * just work; without the hub this is inert and the stage falls back to\n * built-in drag-to-pan, so a hub-less setup costs nothing. Set `false` on\n * SECONDARY lenses (a thumbnail rail) that should stay click-to-navigate\n * instead of feeding the document's tools.\n */\n interaction?: boolean;\n /**\n * With {@link interaction}: let drags over page GAPS pan regardless of the\n * active tool (and show a grab cursor there) — the gutter always pans; there\n * is nothing to draw/select outside a page. Default true.\n */\n panFallback?: boolean;\n /**\n * Ambient ZOOM gestures on this stage: ctrl/cmd+wheel and trackpad pinch\n * (Safari gesture events included). Default true. Turn OFF for follower\n * lenses with a fixed magnification — a thumbnail rail should scroll under\n * cmd+wheel, not zoom — so a zoom-wheel falls through to ordinary wheel\n * pan, and pinches are still swallowed (they never page-zoom the browser).\n */\n zoomGestures?: boolean;\n /** The stage lens to drive (default: the main StageToken). */\n token?: StageTokenProp;\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport function Stage({\n children,\n pageChrome,\n overlay,\n interaction = true,\n panFallback = true,\n zoomGestures = true,\n token = StageToken,\n className,\n style,\n}: StageProps) {\n const stage = useCapability(token);\n const ix = useOptionalCapability(InteractionToken);\n const useHub = interaction && !!ix;\n // The hub's resolved cursor (text/grab/…), applied to the viewport when driving.\n const hubCursor = useKernelValue(() => ix?.cursor() ?? 'default');\n const ref = useRef<HTMLDivElement>(null);\n const docId = useDocumentId();\n // visiblePages already folds in the camera (each page carries its device-snapped\n // screenX/screenY + transform), so panning re-emits the list — no separate\n // camera subscription needed for positioning.\n const pages = useSelector(token, (c) => c.visiblePages()); // memoized -> stable ref\n // Reserved chrome bands (screen px), uniform across pages — the frame the\n // outer box reserves and `pageChrome` paints into.\n const frame = useSelector(\n token,\n (c) => c.pageFrame(),\n (a, b) => a.top === b.top && a.right === b.right && a.bottom === b.bottom && a.left === b.left,\n );\n\n // The Stage's ViewProjector: anchored UI (menus, popovers) positions through\n // the CAMERA — pure state, no DOM reads, no portal, and NO subscription:\n // `pages` (visiblePages) is the binding's revision, so a camera change\n // re-renders the pages AND every anchored consumer in the SAME React\n // commit — surface and overlay can never paint a frame apart.\n const projector = useMemo<ViewProjector>(\n () => ({\n space: 'overlay',\n toScreen: (pon, rect) => stage.pageRectToScreen(pon, rect),\n toScreenPoint: (pon, at) => {\n const r = stage.pageRectToScreen(pon, { x: at.x, y: at.y, width: 0, height: 0 });\n return r ? { x: r.x, y: r.y } : null;\n },\n viewEnv: (pon) => {\n const t = stage.pageRect(pon)?.transform;\n return t ? { scale: t.viewScale, rotation: t.rotation, zoom: t.zoom } : null;\n },\n }),\n [stage],\n );\n const projectorBinding = useMemo<ProjectorBinding>(\n () => ({ projector, revision: pages }),\n [projector, pages],\n );\n\n useLayoutEffect(() => {\n const el = ref.current!;\n // The WHOLE browser binding — viewport/DPR reporting, sample normalization,\n // gesture controller — is the shared @embedpdf/web surface, so every\n // framework adapter has one feel. This component keeps only React glue.\n const detachSurface = createStageSurface(el, stage, {\n hub: useHub ? ix : null,\n source: stage.lensId(),\n zoomGestures,\n });\n // Interaction opt-in lives WITH the sample source: the same knob that\n // forwards this lens's samples also registers its pan-scroll handler,\n // lens-scoped — two stages on one document can never pan each other.\n const offScroll =\n useHub && ix\n ? ix.registerHandler(createScrollHandler(stage, ix, { panFallback }), {\n source: stage.lensId(),\n })\n : null;\n\n return () => {\n offScroll?.();\n detachSurface();\n };\n }, [stage, ix, useHub, zoomGestures, panFallback]);\n\n return (\n <div\n ref={ref}\n className={className}\n style={{\n position: 'relative',\n overflow: 'hidden',\n touchAction: 'none',\n ...(useHub ? { cursor: hubCursor } : null),\n ...style,\n }}\n >\n {pages.map((p) => (\n <PageSurface\n key={p.pon} // durable page identity — survives move/delete (matches Angular's `track p.pon`)\n documentId={docId ?? ''}\n page={p}\n frame={frame}\n stage={stage}\n render={children}\n chrome={pageChrome}\n />\n ))}\n {/* Anchored UI mounts in the overlay: absolute coords here are the\n projector's overlay space (the stage container). */}\n <ProjectorProvider value={projectorBinding}>{overlay}</ProjectorProvider>\n </div>\n );\n}\n\n// ── Facade hooks — thin sugar over the capability + generic binding ───────────\nexport function useStage(token: StageTokenProp = StageToken) {\n return useCapability(token);\n}\nexport function useZoom(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const zoom = useSelector(token, (c) => c.zoomLevel());\n const mode = useSelector(token, (c) => c.zoomMode());\n return {\n zoom,\n /** Active zoom intent: 'automatic' | 'fit-page' | 'fit-width' | 'fit-all' | 'custom'. */\n mode,\n zoomIn: s.zoomIn,\n zoomOut: s.zoomOut,\n fitWidth: s.fitWidth,\n fitPage: s.fitPage,\n fitAll: s.fitAll,\n automatic: s.automatic,\n zoomTo: s.zoomTo,\n };\n}\nexport function usePages(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const currentPage = useSelector(token, (c) => c.currentPage());\n const pageCount = useSelector(token, (c) => c.pageCount());\n return {\n currentPage,\n pageCount,\n goToPage: s.goToPage,\n next: s.next,\n prev: s.prev,\n reveal: s.reveal,\n };\n}\nexport function useLayout(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n const flow = useSelector(token, (c) => c.flow());\n const layout = useSelector(token, (c) => c.layout());\n const spread = useSelector(token, (c) => c.spread());\n const sizing = useSelector(token, (c) => c.sizing());\n const bounded = useSelector(token, (c) => c.bounded());\n return {\n flow,\n layout,\n spread,\n sizing,\n bounded,\n setFlow: s.setFlow,\n setLayout: s.setLayout,\n setSpread: s.setSpread,\n setSizing: s.setSizing,\n setBounded: s.setBounded,\n };\n}\n\n/** The document's page list (with PDF labels) + the current item's pages — the\n * data for page thumbnails / worksheet-style page tabs. */\nexport function usePageList(token: StageTokenProp = StageToken) {\n const pages = useSelector(\n token,\n (c) => c.pages(),\n (a, b) =>\n a.length === b.length && a.every((p, i) => p.pon === b[i].pon && p.label === b[i].label),\n );\n const current = useSelector(\n token,\n (c) => c.currentItemPages(),\n (a, b) => a.length === b.length && a.every((x, i) => x === b[i]),\n );\n return { pages, currentItemPages: current };\n}\n\n/**\n * All Stage settings + the batch `update`. This is the seam for \"presets are a\n * customer concern\": keep your own `Partial<StageSettings>` objects and apply them\n * with `update(preset)` (one anchor-preserving change).\n */\nexport function useStageSettings(token: StageTokenProp = StageToken) {\n const s = useCapability(token);\n // settingsEqual derives from the plugin's settings registry — a new setting is\n // covered here automatically, without this package spelling out the shape.\n const settings = useSelector(token, (c) => c.settings(), settingsEqual);\n return { settings, update: s.update, reset: s.resetView };\n}\n"],"mappings":"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"}
package/dist/stamp.js CHANGED
@@ -1,3 +1,4 @@
1
+ import "./rolldown-runtime-BBjsoOtd.js";
1
2
  import { shallowArray, useCapability, useDocumentId, useSelector } from "./runtime.js";
2
3
  import { useEffect, useState } from "react";
3
4
  import { StampToken } from "@embedpdf/plugin-stamp";
package/dist/stamp.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"stamp.js","names":[],"sources":["../src/stamp.tsx"],"sourcesContent":["/**\n * The React view of @embedpdf/plugin-stamp.\n *\n * Thin by design: the plugin owns the store (libraries/assets) and the binary\n * sidecar (bytes + cached previews); this file only exposes hooks and owns the\n * DOM-side object-URL lifetime for gallery thumbnails. Placement UI needs no\n * component here — arming rides the annotation plugin, whose `<AnnotationLayer>`\n * already renders the hover ghost.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-stamp';\nimport { useEffect, useState } from 'react';\nimport { StampToken, type StampAsset, type StampLibrary } from '@embedpdf/plugin-stamp';\nimport { shallowArray, useCapability, useDocumentId, useSelector } from './runtime';\n\n/** The stamp capability (workspace-scoped: one library set for every document). */\nexport function useStamp() {\n return useCapability(StampToken);\n}\n\nexport function useStampLibraries(): StampLibrary[] {\n return useSelector(StampToken, (c) => c.libraries(), shallowArray);\n}\n\n/** Assets of one library, or every asset when `libraryId` is omitted. */\nexport function useStampAssets(libraryId?: string): StampAsset[] {\n return useSelector(StampToken, (c) => c.assets(libraryId), shallowArray);\n}\n\n/**\n * Object URL for an asset's cached preview (gallery thumbnails). The plugin\n * holds the preview BYTES; the URL — a DOM resource — is created here and\n * revoked on unmount/asset change, mirroring `<AnnotationLayer>`'s rule that\n * object-URL lifetime belongs to the framework layer.\n */\nexport function useStampAssetPreviewUrl(assetId: string | null): string | null {\n const stamp = useCapability(StampToken);\n const [url, setUrl] = useState<string | null>(null);\n useEffect(() => {\n const preview = assetId ? stamp.assetPreview(assetId) : null;\n if (!preview) {\n setUrl(null);\n return;\n }\n // Copy into an EXACT ArrayBuffer (the engine idiom) before Blob-wrapping.\n const body = new ArrayBuffer(preview.bytes.byteLength);\n new Uint8Array(body).set(preview.bytes);\n const obj = URL.createObjectURL(new Blob([body], { type: preview.mimeType }));\n setUrl(obj);\n return () => {\n URL.revokeObjectURL(obj);\n setUrl(null);\n };\n }, [stamp, assetId]);\n return url;\n}\n\n/**\n * Arm/disarm assets against the nearest `<DocumentScope>` (or the active\n * document) — the one-liner a gallery item's onClick needs.\n */\nexport function useArmStampAsset(): {\n armAsset: (assetId: string, opts?: { targetWidth?: number }) => Promise<void>;\n disarm: () => void;\n} {\n const stamp = useCapability(StampToken);\n const documentId = useDocumentId();\n return {\n armAsset: (assetId, opts) => {\n if (!documentId) return Promise.reject(new Error('[stamp] no document in scope'));\n return stamp.armAsset(documentId, assetId, opts);\n },\n disarm: () => {\n if (documentId) stamp.disarm(documentId);\n },\n };\n}\n"],"mappings":";;;;;;AAiBA,SAAgB,WAAW;CACzB,OAAO,cAAc,UAAU;AACjC;AAEA,SAAgB,oBAAoC;CAClD,OAAO,YAAY,aAAa,MAAM,EAAE,UAAU,GAAG,YAAY;AACnE;;AAGA,SAAgB,eAAe,WAAkC;CAC/D,OAAO,YAAY,aAAa,MAAM,EAAE,OAAO,SAAS,GAAG,YAAY;AACzE;;;;;;;AAQA,SAAgB,wBAAwB,SAAuC;CAC7E,MAAM,QAAQ,cAAc,UAAU;CACtC,MAAM,CAAC,KAAK,UAAU,SAAwB,IAAI;CAClD,gBAAgB;EACd,MAAM,UAAU,UAAU,MAAM,aAAa,OAAO,IAAI;EACxD,IAAI,CAAC,SAAS;GACZ,OAAO,IAAI;GACX;EACF;EAEA,MAAM,OAAO,IAAI,YAAY,QAAQ,MAAM,UAAU;EACrD,IAAI,WAAW,IAAI,CAAC,CAAC,IAAI,QAAQ,KAAK;EACtC,MAAM,MAAM,IAAI,gBAAgB,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,QAAQ,SAAS,CAAC,CAAC;EAC5E,OAAO,GAAG;EACV,aAAa;GACX,IAAI,gBAAgB,GAAG;GACvB,OAAO,IAAI;EACb;CACF,GAAG,CAAC,OAAO,OAAO,CAAC;CACnB,OAAO;AACT;;;;;AAMA,SAAgB,mBAGd;CACA,MAAM,QAAQ,cAAc,UAAU;CACtC,MAAM,aAAa,cAAc;CACjC,OAAO;EACL,WAAW,SAAS,SAAS;GAC3B,IAAI,CAAC,YAAY,OAAO,QAAQ,uBAAO,IAAI,MAAM,8BAA8B,CAAC;GAChF,OAAO,MAAM,SAAS,YAAY,SAAS,IAAI;EACjD;EACA,cAAc;GACZ,IAAI,YAAY,MAAM,OAAO,UAAU;EACzC;CACF;AACF"}
1
+ {"version":3,"file":"stamp.js","names":[],"sources":["../src/stamp.tsx"],"sourcesContent":["/**\n * The React view of @embedpdf/plugin-stamp.\n *\n * Thin by design: the plugin owns the store (libraries/assets) and the binary\n * sidecar (bytes + cached previews); this file only exposes hooks and owns the\n * DOM-side object-URL lifetime for gallery thumbnails. Placement UI needs no\n * component here — arming rides the annotation plugin, whose `<AnnotationLayer>`\n * already renders the hover ghost.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-stamp';\nimport { useEffect, useState } from 'react';\nimport { StampToken, type StampAsset, type StampLibrary } from '@embedpdf/plugin-stamp';\nimport { shallowArray, useCapability, useDocumentId, useSelector } from './runtime';\n\n/** The stamp capability (workspace-scoped: one library set for every document). */\nexport function useStamp() {\n return useCapability(StampToken);\n}\n\nexport function useStampLibraries(): StampLibrary[] {\n return useSelector(StampToken, (c) => c.libraries(), shallowArray);\n}\n\n/** Assets of one library, or every asset when `libraryId` is omitted. */\nexport function useStampAssets(libraryId?: string): StampAsset[] {\n return useSelector(StampToken, (c) => c.assets(libraryId), shallowArray);\n}\n\n/**\n * Object URL for an asset's cached preview (gallery thumbnails). The plugin\n * holds the preview BYTES; the URL — a DOM resource — is created here and\n * revoked on unmount/asset change, mirroring `<AnnotationLayer>`'s rule that\n * object-URL lifetime belongs to the framework layer.\n */\nexport function useStampAssetPreviewUrl(assetId: string | null): string | null {\n const stamp = useCapability(StampToken);\n const [url, setUrl] = useState<string | null>(null);\n useEffect(() => {\n const preview = assetId ? stamp.assetPreview(assetId) : null;\n if (!preview) {\n setUrl(null);\n return;\n }\n // Copy into an EXACT ArrayBuffer (the engine idiom) before Blob-wrapping.\n const body = new ArrayBuffer(preview.bytes.byteLength);\n new Uint8Array(body).set(preview.bytes);\n const obj = URL.createObjectURL(new Blob([body], { type: preview.mimeType }));\n setUrl(obj);\n return () => {\n URL.revokeObjectURL(obj);\n setUrl(null);\n };\n }, [stamp, assetId]);\n return url;\n}\n\n/**\n * Arm/disarm assets against the nearest `<DocumentScope>` (or the active\n * document) — the one-liner a gallery item's onClick needs.\n */\nexport function useArmStampAsset(): {\n armAsset: (assetId: string, opts?: { targetWidth?: number }) => Promise<void>;\n disarm: () => void;\n} {\n const stamp = useCapability(StampToken);\n const documentId = useDocumentId();\n return {\n armAsset: (assetId, opts) => {\n if (!documentId) return Promise.reject(new Error('[stamp] no document in scope'));\n return stamp.armAsset(documentId, assetId, opts);\n },\n disarm: () => {\n if (documentId) stamp.disarm(documentId);\n },\n };\n}\n"],"mappings":";;;;;;;AAiBA,SAAgB,WAAW;CACzB,OAAO,cAAc,UAAU;AACjC;AAEA,SAAgB,oBAAoC;CAClD,OAAO,YAAY,aAAa,MAAM,EAAE,UAAU,GAAG,YAAY;AACnE;;AAGA,SAAgB,eAAe,WAAkC;CAC/D,OAAO,YAAY,aAAa,MAAM,EAAE,OAAO,SAAS,GAAG,YAAY;AACzE;;;;;;;AAQA,SAAgB,wBAAwB,SAAuC;CAC7E,MAAM,QAAQ,cAAc,UAAU;CACtC,MAAM,CAAC,KAAK,UAAU,SAAwB,IAAI;CAClD,gBAAgB;EACd,MAAM,UAAU,UAAU,MAAM,aAAa,OAAO,IAAI;EACxD,IAAI,CAAC,SAAS;GACZ,OAAO,IAAI;GACX;EACF;EAEA,MAAM,OAAO,IAAI,YAAY,QAAQ,MAAM,UAAU;EACrD,IAAI,WAAW,IAAI,CAAC,CAAC,IAAI,QAAQ,KAAK;EACtC,MAAM,MAAM,IAAI,gBAAgB,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,QAAQ,SAAS,CAAC,CAAC;EAC5E,OAAO,GAAG;EACV,aAAa;GACX,IAAI,gBAAgB,GAAG;GACvB,OAAO,IAAI;EACb;CACF,GAAG,CAAC,OAAO,OAAO,CAAC;CACnB,OAAO;AACT;;;;;AAMA,SAAgB,mBAGd;CACA,MAAM,QAAQ,cAAc,UAAU;CACtC,MAAM,aAAa,cAAc;CACjC,OAAO;EACL,WAAW,SAAS,SAAS;GAC3B,IAAI,CAAC,YAAY,OAAO,QAAQ,uBAAO,IAAI,MAAM,8BAA8B,CAAC;GAChF,OAAO,MAAM,SAAS,YAAY,SAAS,IAAI;EACjD;EACA,cAAc;GACZ,IAAI,YAAY,MAAM,OAAO,UAAU;EACzC;CACF;AACF"}
package/dist/toolbar.js CHANGED
@@ -1,3 +1,4 @@
1
+ import "./rolldown-runtime-BBjsoOtd.js";
1
2
  import { useCapability, useDocumentId, useKernel, useKernelValue } from "./runtime.js";
2
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
4
  import * as React from "react";