@embedpdf/react 3.0.0-next.4 → 3.0.0-next.6

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 (59) hide show
  1. package/dist/{AnnotationListSnapshot-_Z600IzD-DYoq1Iu1.d.cts → AnnotationListSnapshot-CHjkcIaL-CMHGL8-D.d.cts} +2 -2
  2. package/dist/{AnnotationListSnapshot-_Z600IzD-DYoq1Iu1.d.ts → AnnotationListSnapshot-CHjkcIaL-CMHGL8-D.d.ts} +2 -2
  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 +689 -16
  16. package/dist/annotation.cjs.map +1 -0
  17. package/dist/annotation.d.cts +119 -2
  18. package/dist/annotation.d.ts +119 -2
  19. package/dist/annotation.js +678 -3
  20. package/dist/annotation.js.map +1 -0
  21. package/dist/form.cjs +1 -1
  22. package/dist/form.d.cts +2 -2
  23. package/dist/form.d.ts +2 -2
  24. package/dist/form.js +1 -1
  25. package/dist/index.cjs +14 -4
  26. package/dist/index.d.cts +6 -5
  27. package/dist/index.d.ts +6 -5
  28. package/dist/index.js +5 -4
  29. package/dist/interaction.cjs +1 -1
  30. package/dist/interaction.js +1 -1
  31. package/dist/page-view.cjs +58 -36
  32. package/dist/page-view.cjs.map +1 -1
  33. package/dist/page-view.js +54 -34
  34. package/dist/page-view.js.map +1 -1
  35. package/dist/render.cjs +64 -48
  36. package/dist/render.cjs.map +1 -1
  37. package/dist/render.d.cts +6 -19
  38. package/dist/render.d.ts +6 -19
  39. package/dist/render.js +67 -50
  40. package/dist/render.js.map +1 -1
  41. package/dist/search.d.cts +1 -1
  42. package/dist/search.d.ts +1 -1
  43. package/dist/selection.cjs +59 -4
  44. package/dist/selection.cjs.map +1 -1
  45. package/dist/selection.d.cts +35 -2
  46. package/dist/selection.d.ts +35 -2
  47. package/dist/selection.js +52 -5
  48. package/dist/selection.js.map +1 -1
  49. package/dist/stage.cjs +33 -1
  50. package/dist/stage.cjs.map +1 -1
  51. package/dist/stage.js +33 -1
  52. package/dist/stage.js.map +1 -1
  53. package/package.json +27 -23
  54. package/dist/annotation-BTlcOjcK.js +0 -829
  55. package/dist/annotation-BTlcOjcK.js.map +0 -1
  56. package/dist/annotation-BvNPtcxN.d.cts +0 -173
  57. package/dist/annotation-CPbPvbKa.d.ts +0 -173
  58. package/dist/annotation-DVwYpWyP.cjs +0 -913
  59. package/dist/annotation-DVwYpWyP.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","names":[],"sources":["../src/render.tsx"],"sourcesContent":["/**\n * RenderLayer + TileLayer — the React views of @embedpdf/plugin-render.\n *\n * RenderLayer paints the BASE rung to an <img> from the engine's ENCODED\n * image() (identical for local & cloud). TileLayer paints the tile paint-plan\n * ABOVE it when the deployment lattice caps the base below the demand — mount\n * it per lens (the main stage yes, a thumbnail rail no); where the demand is\n * small it engages nothing. Both are dumb painters: every decision (policy\n * conformance, want sets, retention, release) is plugin-render's.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-render';\nimport * as React from 'react';\nimport { useEffect, useRef } from 'react';\nimport { RenderToken } from '@embedpdf/plugin-render';\nimport type { PageViewDemand, TilePaintSource } from '@embedpdf/plugin-render';\nimport { useCapability, usePage, useSelector } from './runtime';\n\nexport interface RenderLayerProps {\n /**\n * Bake annotations into the page bitmap (default true). Pass false when an\n * <AnnotationLayer> owns annotation rendering, so they aren't drawn twice.\n */\n annotations?: boolean;\n}\n\nexport function RenderLayer({ annotations = true }: RenderLayerProps = {}) {\n const page = usePage();\n const render = useCapability(RenderToken);\n const ref = useRef<HTMLImageElement>(null);\n // ONE dependency: the raster's canonical identity — conformed viewport +\n // annotations flag + epoch. Inside a lattice\n // rung, zoom changes don't move it: no refetch, no DOM churn — the stage's\n // CSS transform does the scaling. It changes exactly at rung crossings and\n // on CONFIRMED mutations (epoch bumps at commit, never mid-gesture). Under\n // `continuous` it embeds the exact scale — v2 behavior byte-for-byte. The\n // policy behind it is a document fact the kernel materialized before\n // publish, so the key is always computable.\n const sourceKey = useSelector(RenderToken, (c) =>\n c.renderSourceKey(page.pon, {\n scale: page.transform.renderScale,\n includeAnnotations: annotations,\n }),\n );\n useEffect(() => {\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n (async () => {\n try {\n // The capability conforms this to the policy (lattice → ladder width,\n // continuous → this exact scale) and collapses same-key asks in its\n // raster store. A stale-closure scale is harmless by construction:\n // any scale in this rung produces this key's canonical request.\n const image = await render.renderPage(page.pon, {\n scale: page.transform.renderScale,\n includeAnnotations: annotations,\n signal: controller.signal,\n });\n const obj = await image.objectUrl(controller.signal);\n if (controller.signal.aborted) {\n obj.revoke();\n return;\n }\n revoke = obj.revoke;\n if (ref.current) ref.current.src = obj.url;\n } catch {\n /* aborted (camera moved / unmounted) or render failed */\n }\n })();\n return () => {\n controller.abort();\n revoke?.();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- sourceKey IS the\n // render identity; scale/annotations/epoch are folded into it upstream.\n }, [render, page.pon, sourceKey]);\n return (\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n }}\n />\n );\n}\n\nexport interface TileLayerProps {\n /**\n * Bake annotations into tile bitmaps (default true). Pass false when an\n * <AnnotationLayer> owns annotation rendering — the composition that keeps\n * tile artifacts annotation-version-free (CDN-stable across edits).\n */\n annotations?: boolean;\n}\n\n/**\n * TileLayer — the sharp plane above the base rung.\n *\n * Mounting it is the per-lens OPT-IN; whether it spends anything is demand\n * arithmetic (a thumbnail rail's demand never engages). The paint list is the\n * plugin's retention-safe plan: keyed <img>s inside ONE container that carries\n * the page scale as a single transform (one rounding context — no tile seams),\n * with `onLoad` reporting the decode boundary back so retained coarser\n * generations release only when their replacement is truly on screen.\n */\nexport function TileLayer({ annotations = true }: TileLayerProps = {}) {\n const page = usePage();\n const render = useCapability(RenderToken);\n // Demand: the host's live camera view (Stage) or whole-page (PageView).\n const demand: PageViewDemand = page.getViewDemand?.() ?? {\n desiredDeviceWidth: page.transform.deviceWidth,\n };\n const plan = useSelector(RenderToken, (c) =>\n c.tilePlan(page.pon, demand, { includeAnnotations: annotations }),\n );\n // Lens unmounted: stop in-flight tile fetches; resolved bytes stay cached.\n useEffect(() => () => render.releaseTiles(page.pon), [render, page.pon]);\n if (plan.paint.length === 0) return null;\n const t = page.transform;\n const scale = t.viewScale;\n return (\n <div\n style={{\n position: 'absolute',\n left: 0,\n top: 0,\n width: t.contentWidth / scale,\n height: t.contentHeight / scale,\n transform: `scale(${scale})`,\n transformOrigin: '0 0',\n pointerEvents: 'none',\n }}\n >\n {plan.paint.map((source) => (\n <TileImg\n key={source.key}\n source={source}\n onPainted={() => render.tilePainted(page.pon, source.key)}\n />\n ))}\n </div>\n );\n}\n\n/** One tile: object-URL lifecycle + the painted (decode-boundary) report. */\nfunction TileImg({ source, onPainted }: { source: TilePaintSource; onPainted: () => void }) {\n const ref = useRef<HTMLImageElement>(null);\n useEffect(() => {\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n (async () => {\n try {\n const obj = await source.handle.objectUrl(controller.signal);\n if (controller.signal.aborted) {\n obj.revoke();\n return;\n }\n revoke = obj.revoke;\n if (ref.current) ref.current.src = obj.url;\n } catch {\n /* aborted (plan moved on) */\n }\n })();\n return () => {\n controller.abort();\n revoke?.();\n };\n }, [source.handle]);\n return (\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n onLoad={onPainted}\n style={{\n position: 'absolute',\n left: source.rect.x,\n top: source.rect.y,\n width: source.rect.width,\n height: source.rect.height,\n zIndex: source.z,\n }}\n />\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"render.js","names":[],"sources":["../src/render.tsx"],"sourcesContent":["/**\n * RenderLayer — the React view of @embedpdf/plugin-render. The page raster\n * is ONE concern with two planes:\n *\n * - the BASE plane: a whole-page <img> at the plugin's resolved render\n * points — the exact settled demand capped at the pixel budget on a\n * continuous (local) engine, the advertised ladder on a lattice (cloud)\n * deployment. Always present; the instant backdrop.\n * - the TILE plane: the plugin's retention-safe paint plan above it,\n * engaging by demand arithmetic when the view wants more pixels than\n * the base may spend. A thumbnail-sized demand engages nothing — the\n * arithmetic is the configuration.\n *\n * The layer is a DUMB painter, deliberately thin: plain <img>s bound through\n * the shared browser adapter, with painted/unpainted reports around their\n * visible lifetime. Every decision — strategy ∧ policy conformance, want\n * sets, level settling, retention, release — is plugin-render's; anything\n * mid-gesture simply CSS-scales until the plugin hands down new pixels.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-render';\nimport * as React from 'react';\nimport { useEffect, useLayoutEffect, useRef } from 'react';\nimport { RenderToken } from '@embedpdf/plugin-render';\nimport type { PageViewDemand, TilePaintSource } from '@embedpdf/plugin-render';\nimport { bindPaintedImage } from '@embedpdf/web';\nimport { useCapability, usePage, useSelector } from './runtime';\n\nexport interface RenderLayerProps {\n /**\n * Bake annotations into the page bitmap (default true). Pass false when an\n * <AnnotationLayer> owns annotation rendering, so they aren't drawn twice.\n */\n annotations?: boolean;\n /**\n * Mount the tile plane (default true). Whether it SPENDS anything is\n * demand arithmetic — leave it on; pass false only for a lens that must\n * never tile even under deep zoom.\n */\n tiles?: boolean;\n}\n\nlet warnedTileSize = false;\n\nexport function RenderLayer({ annotations = true, tiles = true }: RenderLayerProps = {}) {\n const page = usePage();\n const render = useCapability(RenderToken);\n const settings = render.paintSettings();\n const ref = useRef<HTMLImageElement>(null);\n\n // ONE dependency: the raster's canonical identity — conformed width +\n // annotations flag + epoch. Under a lattice it moves only at rung\n // crossings; under exact mode it tracks the demand and is CONSTANT above\n // the budget — so the deep-zoom backdrop never refetches, and the sub-\n // budget range refetches per settled demand exactly like v2 did.\n const sourceKey = useSelector(RenderToken, (c) =>\n c.renderSourceKey(page.pon, {\n scale: page.transform.renderScale,\n includeAnnotations: annotations,\n }),\n );\n\n useEffect(() => {\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n (async () => {\n try {\n // The capability conforms this to the resolved render points and\n // collapses same-key asks in its raster store. A stale-closure scale\n // is harmless by construction: any scale mapping to this key\n // produces this key's canonical request.\n const image = await render.renderPage(page.pon, {\n scale: page.transform.renderScale,\n includeAnnotations: annotations,\n signal: controller.signal,\n });\n const obj = await image.objectUrl(controller.signal);\n if (controller.signal.aborted) {\n obj.revoke();\n return;\n }\n revoke = obj.revoke;\n // Imperative src on a STABLE element: the browser keeps the old\n // bitmap until the new one decodes, and nothing ever re-requests the\n // old URL — so revoke-on-cleanup is safe here by construction.\n if (ref.current) ref.current.src = obj.url;\n } catch {\n /* aborted (camera moved / unmounted) or render failed */\n }\n })();\n return () => {\n controller.abort();\n revoke?.();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- sourceKey IS the\n // render identity; scale/annotations/epoch are folded into it upstream.\n }, [render, page.pon, sourceKey]);\n\n return (\n <>\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n }}\n />\n {tiles && settings.tiles ? (\n <TilePlane annotations={annotations} fadeMs={settings.fadeMs} />\n ) : null}\n </>\n );\n}\n\n/**\n * The sharp plane above the base — the plugin's retention-safe paint plan\n * as keyed <img>s. Each tile reports painted after its first presentation\n * opportunity (and its inverse on unmount), so retained coarser generations\n * release only when their replacement is truly compositable.\n *\n * Tiles are placed in VIEW (CSS px) space directly — never in page points\n * under a scaled container. Blink quantizes layout lengths to 1/64 CSS px\n * BEFORE transforms apply, so a pt-space rect under a ×25 zoom transform\n * turns that harmless 1/64 into ±0.4px+ of per-tile misplacement — visible\n * seams and per-zoom-step letter shifts that grow with depth. In view\n * space the same quantization is a fixed ~1/64 CSS px at every zoom.\n * (v2 placed tiles in screen px for exactly this reason.)\n */\nfunction TilePlane({ annotations, fadeMs }: { annotations: boolean; fadeMs: number }) {\n const page = usePage();\n const render = useCapability(RenderToken);\n // Demand: the host's live camera view (Stage) or whole-page (PageView).\n // Level settling lives in the plugin (its settle gate), pan stays live.\n const demand: PageViewDemand = page.getViewDemand?.() ?? {\n desiredDeviceWidth: page.transform.deviceWidth,\n };\n const plan = useSelector(RenderToken, (c) =>\n c.tilePlan(page.pon, demand, { includeAnnotations: annotations }),\n );\n // Lens unmounted: stop in-flight tile fetches; resolved bytes stay cached.\n useEffect(() => () => render.releaseTiles(page.pon), [render, page.pon]);\n if (plan.paint.length === 0) return null;\n const t = page.transform;\n const s = t.viewScale;\n return (\n <div\n style={{\n position: 'absolute',\n left: 0,\n top: 0,\n width: t.contentWidth,\n height: t.contentHeight,\n pointerEvents: 'none',\n // STACKING CONTRACT: tile <img>s carry zIndex ranks (coarse under\n // fine) that exist only while generations are mixed — mid-zoom. This\n // container MUST be a stacking context so those ranks stay internal;\n // without it they join the Stage's context and paint above every\n // z-auto sibling (annotations, page chrome, menus) exactly while\n // zooming. The old scaled container created one implicitly via its\n // transform; `isolation` is the explicit, no-side-effect way.\n isolation: 'isolate',\n }}\n >\n {fadeMs > 0 ? (\n <style>{`@keyframes epdf-tile-in { from { opacity: 0 } to { opacity: 1 } }`}</style>\n ) : null}\n {plan.paint.map((source) => (\n <TileImg\n key={source.key}\n source={source}\n view={{\n x: source.rect.x * s,\n y: source.rect.y * s,\n width: source.rect.width * s,\n height: source.rect.height * s,\n }}\n fadeMs={fadeMs}\n onPainted={() => render.tilePainted(page.pon, source.key)}\n onUnpainted={() => render.tileUnpainted(page.pon, source.key)}\n />\n ))}\n </div>\n );\n}\n\n/** One tile: geometry plus the shared browser image binding. The binding\n * hides the incomplete image, reports painted after its first presentation\n * opportunity, and inverts that report when the element leaves the DOM. */\nfunction TileImg({\n source,\n view,\n fadeMs,\n onPainted,\n onUnpainted,\n}: {\n source: TilePaintSource;\n /** Placement rect in VIEW (CSS px) space. */\n view: { x: number; y: number; width: number; height: number };\n fadeMs: number;\n onPainted: () => void;\n onUnpainted: () => void;\n}) {\n const ref = useRef<HTMLImageElement>(null);\n useLayoutEffect(() => {\n if (!ref.current) return;\n return bindPaintedImage(ref.current, source.handle, { onPainted, onUnpainted });\n // eslint-disable-next-line react-hooks/exhaustive-deps -- the handle is\n // the bitmap's identity; the report callbacks carry stable values.\n }, [source.handle]);\n return (\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n onLoad={(e) => {\n // A raster whose intrinsic size disagrees with its rect would be\n // silently stretched into place — the stale-handle bug class.\n const w = e.currentTarget.naturalWidth;\n if (w > 0 && !warnedTileSize) {\n const expected = Math.round(source.rect.width * source.scale);\n if (Math.abs(w - expected) > 1) {\n warnedTileSize = true;\n console.warn(\n `[render] tile bitmap ${w}px wide does not match its rect ` +\n `(expected ~${expected}px) — stale raster identity? key=${source.key}`,\n );\n }\n }\n }}\n style={{\n position: 'absolute',\n left: view.x,\n top: view.y,\n width: view.width,\n height: view.height,\n zIndex: source.z,\n ...(fadeMs > 0 ? { animation: `epdf-tile-in ${fadeMs}ms ease-out` } : null),\n }}\n />\n );\n}\n"],"mappings":"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"}
package/dist/search.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as SearchQuery } from "./AnnotationListSnapshot-_Z600IzD-DYoq1Iu1.cjs";
1
+ import { r as SearchQuery } from "./AnnotationListSnapshot-CHjkcIaL-CMHGL8-D.cjs";
2
2
  import * as React from "react";
3
3
  export * from "@embedpdf/plugin-search";
4
4
  //#region src/search.d.ts
package/dist/search.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as SearchQuery } from "./AnnotationListSnapshot-_Z600IzD-DYoq1Iu1.js";
1
+ import { r as SearchQuery } from "./AnnotationListSnapshot-CHjkcIaL-CMHGL8-D.js";
2
2
  import * as React from "react";
3
3
  export * from "@embedpdf/plugin-search";
4
4
  //#region src/search.d.ts
@@ -1,16 +1,19 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-DDXJien1.cjs");
3
3
  const require_runtime = require("./runtime.cjs");
4
+ const require_anchored = require("./anchored.cjs");
4
5
  let react_jsx_runtime = require("react/jsx-runtime");
5
6
  let react = require("react");
6
7
  react = require_rolldown_runtime.__toESM(react, 1);
8
+ let _embedpdf_web = require("@embedpdf/web");
7
9
  let _embedpdf_plugin_selection = require("@embedpdf/plugin-selection");
10
+ let _embedpdf_plugin_selection_internal = require("@embedpdf/plugin-selection/internal");
8
11
  //#region src/selection.tsx
9
12
  function SelectionLayer({ color = "rgba(33, 150, 243, 0.35)" }) {
10
13
  const page = require_runtime.usePage();
11
- const selection = require_runtime.useCapability(_embedpdf_plugin_selection.SelectionToken);
12
- const segments = require_runtime.useSelector(_embedpdf_plugin_selection.SelectionToken, (c) => c.segmentsForPage(page.pon), require_runtime.shallowArray);
13
- const visible = require_runtime.useSelector(_embedpdf_plugin_selection.SelectionToken, (c) => c.highlightVisible());
14
+ const selection = require_runtime.useCapability(_embedpdf_plugin_selection_internal.SelectionToken);
15
+ const segments = require_runtime.useSelector(_embedpdf_plugin_selection_internal.SelectionToken, (c) => c.segmentsForPage(page.pon), require_runtime.shallowArray);
16
+ const visible = require_runtime.useSelector(_embedpdf_plugin_selection_internal.SelectionToken, (c) => c.highlightVisible());
14
17
  (0, react.useEffect)(() => {
15
18
  selection.ensurePage(page.pon);
16
19
  }, [selection, page.pon]);
@@ -37,12 +40,64 @@ function SelectionLayer({ color = "rgba(33, 150, 243, 0.35)" }) {
37
40
  })
38
41
  });
39
42
  }
40
- /** The selection capability (clear(), hasSelection(), …) for app chrome. */
43
+ /** The PUBLIC selection capability (select(), readText(), canCopy(), …) for
44
+ * app chrome — toolbars, context menus, automation. */
41
45
  function useSelection() {
42
46
  return require_runtime.useCapability(_embedpdf_plugin_selection.SelectionToken);
43
47
  }
48
+ /** Structural equality for the selection's menu anchor — keeps the menu from
49
+ * re-rendering on unrelated dispatches (the capability returns a fresh
50
+ * object each call). */
51
+ const sameMenuAnchor = (a, b) => {
52
+ if (a === b) return true;
53
+ if (!a || !b) return false;
54
+ return a.pon === b.pon && a.bounds.x === b.bounds.x && a.bounds.y === b.bounds.y && a.bounds.width === b.bounds.width && a.bounds.height === b.bounds.height;
55
+ };
56
+ /**
57
+ * Floats over the current TEXT selection (one anchor regardless of
58
+ * cross-page selection; it rides the gesture's end page) — and only once the
59
+ * selection SETTLES: hidden while `isSelecting()` (mid-drag), it appears at
60
+ * pointer-up; programmatic selections show immediately (born settled). Works
61
+ * under `<Stage>` (mount in the overlay slot) and `<PageView>` alike — the
62
+ * surface provides the projection. Compose the contents from hooks:
63
+ * `useSelection()` for copy/clear, `useAnnotation()` for
64
+ * `markupFromSelection('highlight')`, `copySelection` for the clipboard.
65
+ * For live-follow UI during the drag, compose `<Anchored>` with
66
+ * `menuAnchor()` yourself — the primitive carries no policy.
67
+ */
68
+ function SelectionMenu({ children, gap = 8, placement = "top" }) {
69
+ const selecting = require_runtime.useSelector(_embedpdf_plugin_selection.SelectionToken, (c) => c.isSelecting());
70
+ const anchor = require_runtime.useSelector(_embedpdf_plugin_selection.SelectionToken, (c) => c.menuAnchor(), sameMenuAnchor);
71
+ if (selecting || !anchor) return null;
72
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_anchored.Anchored, {
73
+ anchor,
74
+ placement,
75
+ gap,
76
+ children
77
+ });
78
+ }
79
+ /**
80
+ * Mount ONCE per viewer to wire clipboard copy: prefetches the selected text
81
+ * when the selection settles, answers the native `copy` event synchronously,
82
+ * and falls back to the async Clipboard API for ctrl/cmd+C when the page has
83
+ * no DOM selection. Renders nothing. For a toolbar Copy button, call
84
+ * `copySelection(useSelection())` from its click handler instead.
85
+ */
86
+ function SelectionClipboard({ prefetch } = {}) {
87
+ const selection = require_runtime.useCapability(_embedpdf_plugin_selection.SelectionToken);
88
+ (0, react.useEffect)(() => (0, _embedpdf_web.wireSelectionClipboard)(selection, prefetch === void 0 ? {} : { prefetch }), [selection, prefetch]);
89
+ return null;
90
+ }
44
91
  //#endregion
92
+ exports.SelectionClipboard = SelectionClipboard;
45
93
  exports.SelectionLayer = SelectionLayer;
94
+ exports.SelectionMenu = SelectionMenu;
95
+ Object.defineProperty(exports, "copySelection", {
96
+ enumerable: true,
97
+ get: function() {
98
+ return _embedpdf_web.copySelection;
99
+ }
100
+ });
46
101
  exports.useSelection = useSelection;
47
102
  Object.keys(_embedpdf_plugin_selection).forEach(function(k) {
48
103
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
@@ -1 +1 @@
1
- {"version":3,"file":"selection.cjs","names":["usePage","useCapability","SelectionToken","useSelector","shallowArray"],"sources":["../src/selection.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-selection.\n *\n * <SelectionLayer> is a dumb renderer: it warms the page's geometry on mount,\n * reads the content-space highlight rects from the capability, and paints them —\n * mapping each rect through PageContext.pageToContent (the same path markers use).\n * Zero pointer handling here; that's the PagePointerSource + the hub.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-selection';\nimport * as React from 'react';\nimport { useEffect } from 'react';\nimport { SelectionToken } from '@embedpdf/plugin-selection';\nimport { shallowArray, useCapability, usePage, useSelector } from './runtime';\n\nexport interface SelectionLayerProps {\n /** Highlight colour (default: translucent blue). */\n color?: string;\n}\n\nexport function SelectionLayer({ color = 'rgba(33, 150, 243, 0.35)' }: SelectionLayerProps) {\n const page = usePage();\n const selection = useCapability(SelectionToken);\n const segments = useSelector(SelectionToken, (c) => c.segmentsForPage(page.pon), shallowArray);\n // A consumer (e.g. a markup tool drawing its own preview) can take over the\n // selection visual; when it does, we render nothing so the two never overlap.\n const visible = useSelector(SelectionToken, (c) => c.highlightVisible());\n\n // Warm this page's text geometry as soon as it's on screen, so the first\n // pointer-down can hit-test without waiting on the engine round-trip.\n useEffect(() => {\n selection.ensurePage(page.pon);\n }, [selection, page.pon]);\n\n if (!visible) return null;\n\n return (\n <svg\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n overflow: 'visible',\n pointerEvents: 'none',\n }}\n >\n {segments.map((s, i) => {\n // content space → un-rotated content view px (rides the page's CSS\n // rotation). An affine map, so mapping the four corners is exact —\n // upright segments render pixel-identical to the old div-per-rect.\n const ring = [s.quad.upperStart, s.quad.upperEnd, s.quad.lowerEnd, s.quad.lowerStart].map(\n (p) => page.transform.pageToContent(p),\n );\n return (\n <polygon key={i} points={ring.map((p) => `${p.x},${p.y}`).join(' ')} fill={color} />\n );\n })}\n </svg>\n );\n}\n\n/** The selection capability (clear(), hasSelection(), …) for app chrome. */\nexport function useSelection() {\n return useCapability(SelectionToken);\n}\n"],"mappings":";;;;;;;;AAqBA,SAAgB,eAAe,EAAE,QAAQ,8BAAmD;CAC1F,MAAM,OAAOA,gBAAAA,QAAQ;CACrB,MAAM,YAAYC,gBAAAA,cAAcC,2BAAAA,cAAc;CAC9C,MAAM,WAAWC,gBAAAA,YAAYD,2BAAAA,iBAAiB,MAAM,EAAE,gBAAgB,KAAK,GAAG,GAAGE,gBAAAA,YAAY;CAG7F,MAAM,UAAUD,gBAAAA,YAAYD,2BAAAA,iBAAiB,MAAM,EAAE,iBAAiB,CAAC;CAIvE,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,UAAU,WAAW,KAAK,GAAG;CAC/B,GAAG,CAAC,WAAW,KAAK,GAAG,CAAC;CAExB,IAAI,CAAC,SAAS,OAAO;CAErB,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EACE,OAAO;GACL,UAAU;GACV,OAAO;GACP,OAAO;GACP,QAAQ;GACR,UAAU;GACV,eAAe;EACjB;EAEC,UAAA,SAAS,KAAK,GAAG,MAAM;GAOtB,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,WAAD;IAAiB,QAJN;KAAC,EAAE,KAAK;KAAY,EAAE,KAAK;KAAU,EAAE,KAAK;KAAU,EAAE,KAAK;IAAU,CAAC,CAAC,KACnF,MAAM,KAAK,UAAU,cAAc,CAAC,CAGT,CAAC,CAAC,KAAK,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG;IAAG,MAAM;GAAQ,GAArE,CAAqE;EAEvF,CAAC;CACE,CAAA;AAET;;AAGA,SAAgB,eAAe;CAC7B,OAAOD,gBAAAA,cAAcC,2BAAAA,cAAc;AACrC"}
1
+ {"version":3,"file":"selection.cjs","names":["usePage","useCapability","SelectionHostToken","useSelector","shallowArray","SelectionToken","Anchored"],"sources":["../src/selection.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-selection.\n *\n * <SelectionLayer> is a dumb renderer: it warms the page's geometry on mount,\n * reads the content-space highlight rects from the capability, and paints them —\n * mapping each rect through PageContext.pageToContent (the same path markers use).\n * Zero pointer handling here; that's the PagePointerSource + the hub.\n *\n * The layer resolves the HOST lens (`/internal`: geometry warming, the\n * highlight handshake) — the adapter is exactly what that entry exists for.\n * `useSelection()` hands app code the PUBLIC lens only.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-selection';\n// The clipboard side effect lives in @embedpdf/web (the plugin is DOM-free);\n// re-exported here so app code has one import for the whole feature.\nexport { copySelection } from '@embedpdf/web';\nimport * as React from 'react';\nimport { useEffect } from 'react';\nimport { SelectionToken, type SelectionMenuAnchor } from '@embedpdf/plugin-selection';\nimport { SelectionToken as SelectionHostToken } from '@embedpdf/plugin-selection/internal';\nimport { wireSelectionClipboard, type SelectionClipboardOptions } from '@embedpdf/web';\nimport { Anchored, type AnchoredPlacement } from './anchored';\nimport { shallowArray, useCapability, usePage, useSelector } from './runtime';\n\nexport interface SelectionLayerProps {\n /** Highlight colour (default: translucent blue). */\n color?: string;\n}\n\nexport function SelectionLayer({ color = 'rgba(33, 150, 243, 0.35)' }: SelectionLayerProps) {\n const page = usePage();\n const selection = useCapability(SelectionHostToken);\n const segments = useSelector(\n SelectionHostToken,\n (c) => c.segmentsForPage(page.pon),\n shallowArray,\n );\n // A consumer (e.g. a markup tool drawing its own preview) can take over the\n // selection visual; when it does, we render nothing so the two never overlap.\n const visible = useSelector(SelectionHostToken, (c) => c.highlightVisible());\n\n // Warm this page's text geometry as soon as it's on screen, so the first\n // pointer-down can hit-test without waiting on the engine round-trip.\n // (A no-op without doc.text.select — nothing warms, nothing renders.)\n useEffect(() => {\n selection.ensurePage(page.pon);\n }, [selection, page.pon]);\n\n if (!visible) return null;\n\n return (\n <svg\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n overflow: 'visible',\n pointerEvents: 'none',\n }}\n >\n {segments.map((s, i) => {\n // content space → un-rotated content view px (rides the page's CSS\n // rotation). An affine map, so mapping the four corners is exact —\n // upright segments render pixel-identical to the old div-per-rect.\n const ring = [s.quad.upperStart, s.quad.upperEnd, s.quad.lowerEnd, s.quad.lowerStart].map(\n (p) => page.transform.pageToContent(p),\n );\n return (\n <polygon key={i} points={ring.map((p) => `${p.x},${p.y}`).join(' ')} fill={color} />\n );\n })}\n </svg>\n );\n}\n\n/** The PUBLIC selection capability (select(), readText(), canCopy(), …) for\n * app chrome — toolbars, context menus, automation. */\nexport function useSelection() {\n return useCapability(SelectionToken);\n}\n\n/** Structural equality for the selection's menu anchor — keeps the menu from\n * re-rendering on unrelated dispatches (the capability returns a fresh\n * object each call). */\nconst sameMenuAnchor = (\n a: SelectionMenuAnchor | null,\n b: SelectionMenuAnchor | null,\n): boolean => {\n if (a === b) return true;\n if (!a || !b) return false;\n return (\n a.pon === b.pon &&\n a.bounds.x === b.bounds.x &&\n a.bounds.y === b.bounds.y &&\n a.bounds.width === b.bounds.width &&\n a.bounds.height === b.bounds.height\n );\n};\n\nexport interface SelectionMenuProps {\n children: React.ReactNode;\n /** Gap in screen px between the selection and the menu (default 8). */\n gap?: number;\n /** Where to place the menu relative to the selection. Default 'top'. */\n placement?: AnchoredPlacement;\n}\n\n/**\n * Floats over the current TEXT selection (one anchor regardless of\n * cross-page selection; it rides the gesture's end page) — and only once the\n * selection SETTLES: hidden while `isSelecting()` (mid-drag), it appears at\n * pointer-up; programmatic selections show immediately (born settled). Works\n * under `<Stage>` (mount in the overlay slot) and `<PageView>` alike — the\n * surface provides the projection. Compose the contents from hooks:\n * `useSelection()` for copy/clear, `useAnnotation()` for\n * `markupFromSelection('highlight')`, `copySelection` for the clipboard.\n * For live-follow UI during the drag, compose `<Anchored>` with\n * `menuAnchor()` yourself — the primitive carries no policy.\n */\nexport function SelectionMenu({ children, gap = 8, placement = 'top' }: SelectionMenuProps) {\n const selecting = useSelector(SelectionToken, (c) => c.isSelecting());\n const anchor = useSelector(SelectionToken, (c) => c.menuAnchor(), sameMenuAnchor);\n if (selecting || !anchor) return null;\n return (\n <Anchored anchor={anchor} placement={placement} gap={gap}>\n {children}\n </Anchored>\n );\n}\n\nexport type SelectionClipboardProps = Pick<SelectionClipboardOptions, 'prefetch'>;\n\n/**\n * Mount ONCE per viewer to wire clipboard copy: prefetches the selected text\n * when the selection settles, answers the native `copy` event synchronously,\n * and falls back to the async Clipboard API for ctrl/cmd+C when the page has\n * no DOM selection. Renders nothing. For a toolbar Copy button, call\n * `copySelection(useSelection())` from its click handler instead.\n */\nexport function SelectionClipboard({ prefetch }: SelectionClipboardProps = {}) {\n const selection = useCapability(SelectionToken);\n useEffect(\n () => wireSelectionClipboard(selection, prefetch === undefined ? {} : { prefetch }),\n [selection, prefetch],\n );\n return null;\n}\n"],"mappings":";;;;;;;;;;;AA+BA,SAAgB,eAAe,EAAE,QAAQ,8BAAmD;CAC1F,MAAM,OAAOA,gBAAAA,QAAQ;CACrB,MAAM,YAAYC,gBAAAA,cAAcC,oCAAAA,cAAkB;CAClD,MAAM,WAAWC,gBAAAA,YACfD,oCAAAA,iBACC,MAAM,EAAE,gBAAgB,KAAK,GAAG,GACjCE,gBAAAA,YACF;CAGA,MAAM,UAAUD,gBAAAA,YAAYD,oCAAAA,iBAAqB,MAAM,EAAE,iBAAiB,CAAC;CAK3E,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,UAAU,WAAW,KAAK,GAAG;CAC/B,GAAG,CAAC,WAAW,KAAK,GAAG,CAAC;CAExB,IAAI,CAAC,SAAS,OAAO;CAErB,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EACE,OAAO;GACL,UAAU;GACV,OAAO;GACP,OAAO;GACP,QAAQ;GACR,UAAU;GACV,eAAe;EACjB;EAEC,UAAA,SAAS,KAAK,GAAG,MAAM;GAOtB,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,WAAD;IAAiB,QAJN;KAAC,EAAE,KAAK;KAAY,EAAE,KAAK;KAAU,EAAE,KAAK;KAAU,EAAE,KAAK;IAAU,CAAC,CAAC,KACnF,MAAM,KAAK,UAAU,cAAc,CAAC,CAGT,CAAC,CAAC,KAAK,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG;IAAG,MAAM;GAAQ,GAArE,CAAqE;EAEvF,CAAC;CACE,CAAA;AAET;;;AAIA,SAAgB,eAAe;CAC7B,OAAOD,gBAAAA,cAAcI,2BAAAA,cAAc;AACrC;;;;AAKA,MAAM,kBACJ,GACA,MACY;CACZ,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO;CACrB,OACE,EAAE,QAAQ,EAAE,OACZ,EAAE,OAAO,MAAM,EAAE,OAAO,KACxB,EAAE,OAAO,MAAM,EAAE,OAAO,KACxB,EAAE,OAAO,UAAU,EAAE,OAAO,SAC5B,EAAE,OAAO,WAAW,EAAE,OAAO;AAEjC;;;;;;;;;;;;;AAsBA,SAAgB,cAAc,EAAE,UAAU,MAAM,GAAG,YAAY,SAA6B;CAC1F,MAAM,YAAYF,gBAAAA,YAAYE,2BAAAA,iBAAiB,MAAM,EAAE,YAAY,CAAC;CACpE,MAAM,SAASF,gBAAAA,YAAYE,2BAAAA,iBAAiB,MAAM,EAAE,WAAW,GAAG,cAAc;CAChF,IAAI,aAAa,CAAC,QAAQ,OAAO;CACjC,OACE,iBAAA,GAAA,kBAAA,IAAA,CAACC,iBAAAA,UAAD;EAAkB;EAAmB;EAAgB;EAClD;CACO,CAAA;AAEd;;;;;;;;AAWA,SAAgB,mBAAmB,EAAE,aAAsC,CAAC,GAAG;CAC7E,MAAM,YAAYL,gBAAAA,cAAcI,2BAAAA,cAAc;CAC9C,CAAA,GAAA,MAAA,UAAA,QAAA,GAAA,cAAA,uBAAA,CAC+B,WAAW,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,CAAC,GAClF,CAAC,WAAW,QAAQ,CACtB;CACA,OAAO;AACT"}
@@ -1,3 +1,6 @@
1
+ import { AnchoredPlacement as AnchoredPlacement$1 } from "./anchored.cjs";
2
+ import * as React from "react";
3
+ import { SelectionClipboardOptions, copySelection } from "@embedpdf/web";
1
4
  export * from "@embedpdf/plugin-selection";
2
5
  //#region src/selection.d.ts
3
6
  interface SelectionLayerProps {
@@ -5,8 +8,38 @@ interface SelectionLayerProps {
5
8
  color?: string;
6
9
  }
7
10
  declare function SelectionLayer({ color }: SelectionLayerProps): import("react/jsx-runtime").JSX.Element | null;
8
- /** The selection capability (clear(), hasSelection(), …) for app chrome. */
11
+ /** The PUBLIC selection capability (select(), readText(), canCopy(), …) for
12
+ * app chrome — toolbars, context menus, automation. */
9
13
  declare function useSelection(): import("@embedpdf/plugin-selection").SelectionCapability;
14
+ interface SelectionMenuProps {
15
+ children: React.ReactNode;
16
+ /** Gap in screen px between the selection and the menu (default 8). */
17
+ gap?: number;
18
+ /** Where to place the menu relative to the selection. Default 'top'. */
19
+ placement?: AnchoredPlacement$1;
20
+ }
21
+ /**
22
+ * Floats over the current TEXT selection (one anchor regardless of
23
+ * cross-page selection; it rides the gesture's end page) — and only once the
24
+ * selection SETTLES: hidden while `isSelecting()` (mid-drag), it appears at
25
+ * pointer-up; programmatic selections show immediately (born settled). Works
26
+ * under `<Stage>` (mount in the overlay slot) and `<PageView>` alike — the
27
+ * surface provides the projection. Compose the contents from hooks:
28
+ * `useSelection()` for copy/clear, `useAnnotation()` for
29
+ * `markupFromSelection('highlight')`, `copySelection` for the clipboard.
30
+ * For live-follow UI during the drag, compose `<Anchored>` with
31
+ * `menuAnchor()` yourself — the primitive carries no policy.
32
+ */
33
+ declare function SelectionMenu({ children, gap, placement }: SelectionMenuProps): import("react/jsx-runtime").JSX.Element | null;
34
+ type SelectionClipboardProps = Pick<SelectionClipboardOptions, 'prefetch'>;
35
+ /**
36
+ * Mount ONCE per viewer to wire clipboard copy: prefetches the selected text
37
+ * when the selection settles, answers the native `copy` event synchronously,
38
+ * and falls back to the async Clipboard API for ctrl/cmd+C when the page has
39
+ * no DOM selection. Renders nothing. For a toolbar Copy button, call
40
+ * `copySelection(useSelection())` from its click handler instead.
41
+ */
42
+ declare function SelectionClipboard({ prefetch }?: SelectionClipboardProps): null;
10
43
  //#endregion
11
- export { SelectionLayer, SelectionLayerProps, useSelection };
44
+ export { SelectionClipboard, SelectionClipboardProps, SelectionLayer, SelectionLayerProps, SelectionMenu, SelectionMenuProps, copySelection, useSelection };
12
45
  //# sourceMappingURL=selection.d.cts.map
@@ -1,3 +1,6 @@
1
+ import { AnchoredPlacement as AnchoredPlacement$1 } from "./anchored.js";
2
+ import * as React from "react";
3
+ import { SelectionClipboardOptions, copySelection } from "@embedpdf/web";
1
4
  export * from "@embedpdf/plugin-selection";
2
5
  //#region src/selection.d.ts
3
6
  interface SelectionLayerProps {
@@ -5,8 +8,38 @@ interface SelectionLayerProps {
5
8
  color?: string;
6
9
  }
7
10
  declare function SelectionLayer({ color }: SelectionLayerProps): import("react/jsx-runtime").JSX.Element | null;
8
- /** The selection capability (clear(), hasSelection(), …) for app chrome. */
11
+ /** The PUBLIC selection capability (select(), readText(), canCopy(), …) for
12
+ * app chrome — toolbars, context menus, automation. */
9
13
  declare function useSelection(): import("@embedpdf/plugin-selection").SelectionCapability;
14
+ interface SelectionMenuProps {
15
+ children: React.ReactNode;
16
+ /** Gap in screen px between the selection and the menu (default 8). */
17
+ gap?: number;
18
+ /** Where to place the menu relative to the selection. Default 'top'. */
19
+ placement?: AnchoredPlacement$1;
20
+ }
21
+ /**
22
+ * Floats over the current TEXT selection (one anchor regardless of
23
+ * cross-page selection; it rides the gesture's end page) — and only once the
24
+ * selection SETTLES: hidden while `isSelecting()` (mid-drag), it appears at
25
+ * pointer-up; programmatic selections show immediately (born settled). Works
26
+ * under `<Stage>` (mount in the overlay slot) and `<PageView>` alike — the
27
+ * surface provides the projection. Compose the contents from hooks:
28
+ * `useSelection()` for copy/clear, `useAnnotation()` for
29
+ * `markupFromSelection('highlight')`, `copySelection` for the clipboard.
30
+ * For live-follow UI during the drag, compose `<Anchored>` with
31
+ * `menuAnchor()` yourself — the primitive carries no policy.
32
+ */
33
+ declare function SelectionMenu({ children, gap, placement }: SelectionMenuProps): import("react/jsx-runtime").JSX.Element | null;
34
+ type SelectionClipboardProps = Pick<SelectionClipboardOptions, 'prefetch'>;
35
+ /**
36
+ * Mount ONCE per viewer to wire clipboard copy: prefetches the selected text
37
+ * when the selection settles, answers the native `copy` event synchronously,
38
+ * and falls back to the async Clipboard API for ctrl/cmd+C when the page has
39
+ * no DOM selection. Renders nothing. For a toolbar Copy button, call
40
+ * `copySelection(useSelection())` from its click handler instead.
41
+ */
42
+ declare function SelectionClipboard({ prefetch }?: SelectionClipboardProps): null;
10
43
  //#endregion
11
- export { SelectionLayer, SelectionLayerProps, useSelection };
44
+ export { SelectionClipboard, SelectionClipboardProps, SelectionLayer, SelectionLayerProps, SelectionMenu, SelectionMenuProps, copySelection, useSelection };
12
45
  //# sourceMappingURL=selection.d.ts.map
package/dist/selection.js CHANGED
@@ -1,14 +1,17 @@
1
1
  import { shallowArray, useCapability, usePage, useSelector } from "./runtime.js";
2
+ import { Anchored } from "./anchored.js";
2
3
  import { jsx } from "react/jsx-runtime";
3
4
  import { useEffect } from "react";
5
+ import { copySelection, wireSelectionClipboard } from "@embedpdf/web";
4
6
  import { SelectionToken } from "@embedpdf/plugin-selection";
7
+ import { SelectionToken as SelectionToken$1 } from "@embedpdf/plugin-selection/internal";
5
8
  export * from "@embedpdf/plugin-selection";
6
9
  //#region src/selection.tsx
7
10
  function SelectionLayer({ color = "rgba(33, 150, 243, 0.35)" }) {
8
11
  const page = usePage();
9
- const selection = useCapability(SelectionToken);
10
- const segments = useSelector(SelectionToken, (c) => c.segmentsForPage(page.pon), shallowArray);
11
- const visible = useSelector(SelectionToken, (c) => c.highlightVisible());
12
+ const selection = useCapability(SelectionToken$1);
13
+ const segments = useSelector(SelectionToken$1, (c) => c.segmentsForPage(page.pon), shallowArray);
14
+ const visible = useSelector(SelectionToken$1, (c) => c.highlightVisible());
12
15
  useEffect(() => {
13
16
  selection.ensurePage(page.pon);
14
17
  }, [selection, page.pon]);
@@ -35,11 +38,55 @@ function SelectionLayer({ color = "rgba(33, 150, 243, 0.35)" }) {
35
38
  })
36
39
  });
37
40
  }
38
- /** The selection capability (clear(), hasSelection(), …) for app chrome. */
41
+ /** The PUBLIC selection capability (select(), readText(), canCopy(), …) for
42
+ * app chrome — toolbars, context menus, automation. */
39
43
  function useSelection() {
40
44
  return useCapability(SelectionToken);
41
45
  }
46
+ /** Structural equality for the selection's menu anchor — keeps the menu from
47
+ * re-rendering on unrelated dispatches (the capability returns a fresh
48
+ * object each call). */
49
+ const sameMenuAnchor = (a, b) => {
50
+ if (a === b) return true;
51
+ if (!a || !b) return false;
52
+ return a.pon === b.pon && a.bounds.x === b.bounds.x && a.bounds.y === b.bounds.y && a.bounds.width === b.bounds.width && a.bounds.height === b.bounds.height;
53
+ };
54
+ /**
55
+ * Floats over the current TEXT selection (one anchor regardless of
56
+ * cross-page selection; it rides the gesture's end page) — and only once the
57
+ * selection SETTLES: hidden while `isSelecting()` (mid-drag), it appears at
58
+ * pointer-up; programmatic selections show immediately (born settled). Works
59
+ * under `<Stage>` (mount in the overlay slot) and `<PageView>` alike — the
60
+ * surface provides the projection. Compose the contents from hooks:
61
+ * `useSelection()` for copy/clear, `useAnnotation()` for
62
+ * `markupFromSelection('highlight')`, `copySelection` for the clipboard.
63
+ * For live-follow UI during the drag, compose `<Anchored>` with
64
+ * `menuAnchor()` yourself — the primitive carries no policy.
65
+ */
66
+ function SelectionMenu({ children, gap = 8, placement = "top" }) {
67
+ const selecting = useSelector(SelectionToken, (c) => c.isSelecting());
68
+ const anchor = useSelector(SelectionToken, (c) => c.menuAnchor(), sameMenuAnchor);
69
+ if (selecting || !anchor) return null;
70
+ return /* @__PURE__ */ jsx(Anchored, {
71
+ anchor,
72
+ placement,
73
+ gap,
74
+ children
75
+ });
76
+ }
77
+ /**
78
+ * Mount ONCE per viewer to wire clipboard copy: prefetches the selected text
79
+ * when the selection settles, answers the native `copy` event synchronously,
80
+ * and falls back to the async Clipboard API for ctrl/cmd+C when the page has
81
+ * no DOM selection. Renders nothing. For a toolbar Copy button, call
82
+ * `copySelection(useSelection())` from its click handler instead.
83
+ */
84
+ function SelectionClipboard({ prefetch } = {}) {
85
+ const selection = useCapability(SelectionToken);
86
+ useEffect(() => wireSelectionClipboard(selection, prefetch === void 0 ? {} : { prefetch }), [selection, prefetch]);
87
+ return null;
88
+ }
42
89
  //#endregion
43
- export { SelectionLayer, useSelection };
90
+ export { SelectionClipboard, SelectionLayer, SelectionMenu, copySelection, useSelection };
44
91
 
45
92
  //# sourceMappingURL=selection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"selection.js","names":[],"sources":["../src/selection.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-selection.\n *\n * <SelectionLayer> is a dumb renderer: it warms the page's geometry on mount,\n * reads the content-space highlight rects from the capability, and paints them —\n * mapping each rect through PageContext.pageToContent (the same path markers use).\n * Zero pointer handling here; that's the PagePointerSource + the hub.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-selection';\nimport * as React from 'react';\nimport { useEffect } from 'react';\nimport { SelectionToken } from '@embedpdf/plugin-selection';\nimport { shallowArray, useCapability, usePage, useSelector } from './runtime';\n\nexport interface SelectionLayerProps {\n /** Highlight colour (default: translucent blue). */\n color?: string;\n}\n\nexport function SelectionLayer({ color = 'rgba(33, 150, 243, 0.35)' }: SelectionLayerProps) {\n const page = usePage();\n const selection = useCapability(SelectionToken);\n const segments = useSelector(SelectionToken, (c) => c.segmentsForPage(page.pon), shallowArray);\n // A consumer (e.g. a markup tool drawing its own preview) can take over the\n // selection visual; when it does, we render nothing so the two never overlap.\n const visible = useSelector(SelectionToken, (c) => c.highlightVisible());\n\n // Warm this page's text geometry as soon as it's on screen, so the first\n // pointer-down can hit-test without waiting on the engine round-trip.\n useEffect(() => {\n selection.ensurePage(page.pon);\n }, [selection, page.pon]);\n\n if (!visible) return null;\n\n return (\n <svg\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n overflow: 'visible',\n pointerEvents: 'none',\n }}\n >\n {segments.map((s, i) => {\n // content space → un-rotated content view px (rides the page's CSS\n // rotation). An affine map, so mapping the four corners is exact —\n // upright segments render pixel-identical to the old div-per-rect.\n const ring = [s.quad.upperStart, s.quad.upperEnd, s.quad.lowerEnd, s.quad.lowerStart].map(\n (p) => page.transform.pageToContent(p),\n );\n return (\n <polygon key={i} points={ring.map((p) => `${p.x},${p.y}`).join(' ')} fill={color} />\n );\n })}\n </svg>\n );\n}\n\n/** The selection capability (clear(), hasSelection(), …) for app chrome. */\nexport function useSelection() {\n return useCapability(SelectionToken);\n}\n"],"mappings":";;;;;;AAqBA,SAAgB,eAAe,EAAE,QAAQ,8BAAmD;CAC1F,MAAM,OAAO,QAAQ;CACrB,MAAM,YAAY,cAAc,cAAc;CAC9C,MAAM,WAAW,YAAY,iBAAiB,MAAM,EAAE,gBAAgB,KAAK,GAAG,GAAG,YAAY;CAG7F,MAAM,UAAU,YAAY,iBAAiB,MAAM,EAAE,iBAAiB,CAAC;CAIvE,gBAAgB;EACd,UAAU,WAAW,KAAK,GAAG;CAC/B,GAAG,CAAC,WAAW,KAAK,GAAG,CAAC;CAExB,IAAI,CAAC,SAAS,OAAO;CAErB,OACE,oBAAC,OAAD;EACE,OAAO;GACL,UAAU;GACV,OAAO;GACP,OAAO;GACP,QAAQ;GACR,UAAU;GACV,eAAe;EACjB;EAEC,UAAA,SAAS,KAAK,GAAG,MAAM;GAOtB,OACE,oBAAC,WAAD;IAAiB,QAJN;KAAC,EAAE,KAAK;KAAY,EAAE,KAAK;KAAU,EAAE,KAAK;KAAU,EAAE,KAAK;IAAU,CAAC,CAAC,KACnF,MAAM,KAAK,UAAU,cAAc,CAAC,CAGT,CAAC,CAAC,KAAK,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG;IAAG,MAAM;GAAQ,GAArE,CAAqE;EAEvF,CAAC;CACE,CAAA;AAET;;AAGA,SAAgB,eAAe;CAC7B,OAAO,cAAc,cAAc;AACrC"}
1
+ {"version":3,"file":"selection.js","names":["SelectionHostToken"],"sources":["../src/selection.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-selection.\n *\n * <SelectionLayer> is a dumb renderer: it warms the page's geometry on mount,\n * reads the content-space highlight rects from the capability, and paints them —\n * mapping each rect through PageContext.pageToContent (the same path markers use).\n * Zero pointer handling here; that's the PagePointerSource + the hub.\n *\n * The layer resolves the HOST lens (`/internal`: geometry warming, the\n * highlight handshake) — the adapter is exactly what that entry exists for.\n * `useSelection()` hands app code the PUBLIC lens only.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-selection';\n// The clipboard side effect lives in @embedpdf/web (the plugin is DOM-free);\n// re-exported here so app code has one import for the whole feature.\nexport { copySelection } from '@embedpdf/web';\nimport * as React from 'react';\nimport { useEffect } from 'react';\nimport { SelectionToken, type SelectionMenuAnchor } from '@embedpdf/plugin-selection';\nimport { SelectionToken as SelectionHostToken } from '@embedpdf/plugin-selection/internal';\nimport { wireSelectionClipboard, type SelectionClipboardOptions } from '@embedpdf/web';\nimport { Anchored, type AnchoredPlacement } from './anchored';\nimport { shallowArray, useCapability, usePage, useSelector } from './runtime';\n\nexport interface SelectionLayerProps {\n /** Highlight colour (default: translucent blue). */\n color?: string;\n}\n\nexport function SelectionLayer({ color = 'rgba(33, 150, 243, 0.35)' }: SelectionLayerProps) {\n const page = usePage();\n const selection = useCapability(SelectionHostToken);\n const segments = useSelector(\n SelectionHostToken,\n (c) => c.segmentsForPage(page.pon),\n shallowArray,\n );\n // A consumer (e.g. a markup tool drawing its own preview) can take over the\n // selection visual; when it does, we render nothing so the two never overlap.\n const visible = useSelector(SelectionHostToken, (c) => c.highlightVisible());\n\n // Warm this page's text geometry as soon as it's on screen, so the first\n // pointer-down can hit-test without waiting on the engine round-trip.\n // (A no-op without doc.text.select — nothing warms, nothing renders.)\n useEffect(() => {\n selection.ensurePage(page.pon);\n }, [selection, page.pon]);\n\n if (!visible) return null;\n\n return (\n <svg\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n overflow: 'visible',\n pointerEvents: 'none',\n }}\n >\n {segments.map((s, i) => {\n // content space → un-rotated content view px (rides the page's CSS\n // rotation). An affine map, so mapping the four corners is exact —\n // upright segments render pixel-identical to the old div-per-rect.\n const ring = [s.quad.upperStart, s.quad.upperEnd, s.quad.lowerEnd, s.quad.lowerStart].map(\n (p) => page.transform.pageToContent(p),\n );\n return (\n <polygon key={i} points={ring.map((p) => `${p.x},${p.y}`).join(' ')} fill={color} />\n );\n })}\n </svg>\n );\n}\n\n/** The PUBLIC selection capability (select(), readText(), canCopy(), …) for\n * app chrome — toolbars, context menus, automation. */\nexport function useSelection() {\n return useCapability(SelectionToken);\n}\n\n/** Structural equality for the selection's menu anchor — keeps the menu from\n * re-rendering on unrelated dispatches (the capability returns a fresh\n * object each call). */\nconst sameMenuAnchor = (\n a: SelectionMenuAnchor | null,\n b: SelectionMenuAnchor | null,\n): boolean => {\n if (a === b) return true;\n if (!a || !b) return false;\n return (\n a.pon === b.pon &&\n a.bounds.x === b.bounds.x &&\n a.bounds.y === b.bounds.y &&\n a.bounds.width === b.bounds.width &&\n a.bounds.height === b.bounds.height\n );\n};\n\nexport interface SelectionMenuProps {\n children: React.ReactNode;\n /** Gap in screen px between the selection and the menu (default 8). */\n gap?: number;\n /** Where to place the menu relative to the selection. Default 'top'. */\n placement?: AnchoredPlacement;\n}\n\n/**\n * Floats over the current TEXT selection (one anchor regardless of\n * cross-page selection; it rides the gesture's end page) — and only once the\n * selection SETTLES: hidden while `isSelecting()` (mid-drag), it appears at\n * pointer-up; programmatic selections show immediately (born settled). Works\n * under `<Stage>` (mount in the overlay slot) and `<PageView>` alike — the\n * surface provides the projection. Compose the contents from hooks:\n * `useSelection()` for copy/clear, `useAnnotation()` for\n * `markupFromSelection('highlight')`, `copySelection` for the clipboard.\n * For live-follow UI during the drag, compose `<Anchored>` with\n * `menuAnchor()` yourself — the primitive carries no policy.\n */\nexport function SelectionMenu({ children, gap = 8, placement = 'top' }: SelectionMenuProps) {\n const selecting = useSelector(SelectionToken, (c) => c.isSelecting());\n const anchor = useSelector(SelectionToken, (c) => c.menuAnchor(), sameMenuAnchor);\n if (selecting || !anchor) return null;\n return (\n <Anchored anchor={anchor} placement={placement} gap={gap}>\n {children}\n </Anchored>\n );\n}\n\nexport type SelectionClipboardProps = Pick<SelectionClipboardOptions, 'prefetch'>;\n\n/**\n * Mount ONCE per viewer to wire clipboard copy: prefetches the selected text\n * when the selection settles, answers the native `copy` event synchronously,\n * and falls back to the async Clipboard API for ctrl/cmd+C when the page has\n * no DOM selection. Renders nothing. For a toolbar Copy button, call\n * `copySelection(useSelection())` from its click handler instead.\n */\nexport function SelectionClipboard({ prefetch }: SelectionClipboardProps = {}) {\n const selection = useCapability(SelectionToken);\n useEffect(\n () => wireSelectionClipboard(selection, prefetch === undefined ? {} : { prefetch }),\n [selection, prefetch],\n );\n return null;\n}\n"],"mappings":";;;;;;;;;AA+BA,SAAgB,eAAe,EAAE,QAAQ,8BAAmD;CAC1F,MAAM,OAAO,QAAQ;CACrB,MAAM,YAAY,cAAcA,gBAAkB;CAClD,MAAM,WAAW,YACfA,mBACC,MAAM,EAAE,gBAAgB,KAAK,GAAG,GACjC,YACF;CAGA,MAAM,UAAU,YAAYA,mBAAqB,MAAM,EAAE,iBAAiB,CAAC;CAK3E,gBAAgB;EACd,UAAU,WAAW,KAAK,GAAG;CAC/B,GAAG,CAAC,WAAW,KAAK,GAAG,CAAC;CAExB,IAAI,CAAC,SAAS,OAAO;CAErB,OACE,oBAAC,OAAD;EACE,OAAO;GACL,UAAU;GACV,OAAO;GACP,OAAO;GACP,QAAQ;GACR,UAAU;GACV,eAAe;EACjB;EAEC,UAAA,SAAS,KAAK,GAAG,MAAM;GAOtB,OACE,oBAAC,WAAD;IAAiB,QAJN;KAAC,EAAE,KAAK;KAAY,EAAE,KAAK;KAAU,EAAE,KAAK;KAAU,EAAE,KAAK;IAAU,CAAC,CAAC,KACnF,MAAM,KAAK,UAAU,cAAc,CAAC,CAGT,CAAC,CAAC,KAAK,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG;IAAG,MAAM;GAAQ,GAArE,CAAqE;EAEvF,CAAC;CACE,CAAA;AAET;;;AAIA,SAAgB,eAAe;CAC7B,OAAO,cAAc,cAAc;AACrC;;;;AAKA,MAAM,kBACJ,GACA,MACY;CACZ,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO;CACrB,OACE,EAAE,QAAQ,EAAE,OACZ,EAAE,OAAO,MAAM,EAAE,OAAO,KACxB,EAAE,OAAO,MAAM,EAAE,OAAO,KACxB,EAAE,OAAO,UAAU,EAAE,OAAO,SAC5B,EAAE,OAAO,WAAW,EAAE,OAAO;AAEjC;;;;;;;;;;;;;AAsBA,SAAgB,cAAc,EAAE,UAAU,MAAM,GAAG,YAAY,SAA6B;CAC1F,MAAM,YAAY,YAAY,iBAAiB,MAAM,EAAE,YAAY,CAAC;CACpE,MAAM,SAAS,YAAY,iBAAiB,MAAM,EAAE,WAAW,GAAG,cAAc;CAChF,IAAI,aAAa,CAAC,QAAQ,OAAO;CACjC,OACE,oBAAC,UAAD;EAAkB;EAAmB;EAAgB;EAClD;CACO,CAAA;AAEd;;;;;;;;AAWA,SAAgB,mBAAmB,EAAE,aAAsC,CAAC,GAAG;CAC7E,MAAM,YAAY,cAAc,cAAc;CAC9C,gBACQ,uBAAuB,WAAW,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,CAAC,GAClF,CAAC,WAAW,QAAQ,CACtB;CACA,OAAO;AACT"}
package/dist/stage.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-DDXJien1.cjs");
3
3
  const require_runtime = require("./runtime.cjs");
4
+ const require_anchored = require("./anchored.cjs");
4
5
  const require_interaction = require("./interaction.cjs");
5
6
  let react_jsx_runtime = require("react/jsx-runtime");
6
7
  let react = require("react");
@@ -88,6 +89,34 @@ function Stage({ children, pageChrome, overlay, interaction = false, zoomGesture
88
89
  const docId = require_runtime.useDocumentId();
89
90
  const pages = require_runtime.useSelector(token, (c) => c.visiblePages());
90
91
  const frame = require_runtime.useSelector(token, (c) => c.pageFrame(), (a, b) => a.top === b.top && a.right === b.right && a.bottom === b.bottom && a.left === b.left);
92
+ const projector = (0, react.useMemo)(() => ({
93
+ space: "overlay",
94
+ toScreen: (pon, rect) => stage.pageRectToScreen(pon, rect),
95
+ toScreenPoint: (pon, at) => {
96
+ const r = stage.pageRectToScreen(pon, {
97
+ x: at.x,
98
+ y: at.y,
99
+ width: 0,
100
+ height: 0
101
+ });
102
+ return r ? {
103
+ x: r.x,
104
+ y: r.y
105
+ } : null;
106
+ },
107
+ viewEnv: (pon) => {
108
+ const t = stage.pageRect(pon)?.transform;
109
+ return t ? {
110
+ scale: t.viewScale,
111
+ rotation: t.rotation,
112
+ zoom: t.zoom
113
+ } : null;
114
+ }
115
+ }), [stage]);
116
+ const projectorBinding = (0, react.useMemo)(() => ({
117
+ projector,
118
+ revision: pages
119
+ }), [projector, pages]);
91
120
  (0, react.useEffect)(() => {
92
121
  const el = ref.current;
93
122
  const setVp = () => stage.setViewport({
@@ -255,7 +284,10 @@ function Stage({ children, pageChrome, overlay, interaction = false, zoomGesture
255
284
  stage,
256
285
  render: children,
257
286
  chrome: pageChrome
258
- }, p.pageIndex)), overlay]
287
+ }, p.pageIndex)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_anchored.ProjectorProvider, {
288
+ value: projectorBinding,
289
+ children: overlay
290
+ })]
259
291
  });
260
292
  }
261
293
  function useStage(token = _embedpdf_plugin_stage.StageToken) {
@@ -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","createClickCounter","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 { 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 { ProjectorProvider, type ProjectorBinding, type ViewProjector } from './anchored';\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 // 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 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 {/* 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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"}