@embedpdf/react 3.0.0-next.3 → 3.0.0-next.5

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 (57) hide show
  1. package/dist/{AnnotationListSnapshot-W0RCsyDE-dWna4TTJ.d.cts → AnnotationListSnapshot-CHjkcIaL-CMHGL8-D.d.cts} +19 -2
  2. package/dist/{AnnotationListSnapshot-W0RCsyDE-dWna4TTJ.d.ts → AnnotationListSnapshot-CHjkcIaL-CMHGL8-D.d.ts} +19 -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 -3
  26. package/dist/index.d.cts +5 -4
  27. package/dist/index.d.ts +5 -4
  28. package/dist/index.js +4 -3
  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/search.cjs +36 -19
  36. package/dist/search.cjs.map +1 -1
  37. package/dist/search.d.cts +1 -1
  38. package/dist/search.d.ts +1 -1
  39. package/dist/search.js +36 -19
  40. package/dist/search.js.map +1 -1
  41. package/dist/selection.cjs +73 -22
  42. package/dist/selection.cjs.map +1 -1
  43. package/dist/selection.d.cts +35 -2
  44. package/dist/selection.d.ts +35 -2
  45. package/dist/selection.js +66 -23
  46. package/dist/selection.js.map +1 -1
  47. package/dist/stage.cjs +33 -1
  48. package/dist/stage.cjs.map +1 -1
  49. package/dist/stage.js +33 -1
  50. package/dist/stage.js.map +1 -1
  51. package/package.json +27 -23
  52. package/dist/annotation-B6jwfTCh.d.cts +0 -173
  53. package/dist/annotation-Ct6dFtzJ.cjs +0 -913
  54. package/dist/annotation-Ct6dFtzJ.cjs.map +0 -1
  55. package/dist/annotation-DjGWJljY.js +0 -829
  56. package/dist/annotation-DjGWJljY.js.map +0 -1
  57. package/dist/annotation-nUMefyjw.d.ts +0 -173
package/dist/page-view.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { DocumentScope, PageProvider, makePageContext, useActiveDocumentId, useKernel } from "./runtime.js";
2
+ import { ProjectorProvider } from "./anchored.js";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  import { useMemo, useRef } from "react";
5
+ import { observeClientGeometry } from "@embedpdf/web";
4
6
  import { NO_FRAME, pageTransform } from "@embedpdf/core-geometry";
5
7
  //#region src/page-view.tsx
6
8
  /** A single page surface with NO Stage — same layers + rotation + chrome frame,
@@ -42,6 +44,21 @@ function PageView({ page, documentId, width = 240, pageFrame = NO_FRAME, childre
42
44
  pageFrame,
43
45
  transform
44
46
  ]);
47
+ const projector = useMemo(() => ({
48
+ space: "client",
49
+ toScreen: (p, rect) => p === ctx.pon && ref.current ? ctx.toClientRect(rect) : null,
50
+ toScreenPoint: (p, at) => p === ctx.pon && ref.current ? ctx.toClientPoint(at) : null,
51
+ viewEnv: (p) => p === ctx.pon ? {
52
+ scale: ctx.transform.viewScale,
53
+ rotation: ctx.transform.rotation,
54
+ zoom: ctx.transform.zoom
55
+ } : null
56
+ }), [ctx]);
57
+ const projectorBinding = useMemo(() => ({
58
+ projector,
59
+ revision: ctx,
60
+ subscribe: observeClientGeometry
61
+ }), [projector, ctx]);
45
62
  if (!docId || !meta || !base) return null;
46
63
  const t = transform;
47
64
  const outerW = t.viewWidth + pageFrame.left + pageFrame.right;
@@ -50,41 +67,44 @@ function PageView({ page, documentId, width = 240, pageFrame = NO_FRAME, childre
50
67
  const contentTop = pageFrame.top + (t.viewHeight - t.contentHeight) / 2;
51
68
  return /* @__PURE__ */ jsx(DocumentScope, {
52
69
  id: docId,
53
- children: /* @__PURE__ */ jsx("div", {
54
- style: {
55
- position: "relative",
56
- width: outerW,
57
- height: outerH,
58
- ...style
59
- },
60
- children: /* @__PURE__ */ jsxs(PageProvider, {
61
- value: ctx,
62
- children: [
63
- /* @__PURE__ */ jsx("div", { style: {
64
- position: "absolute",
65
- left: pageFrame.left,
66
- top: pageFrame.top,
67
- width: t.viewWidth,
68
- height: t.viewHeight,
69
- boxShadow: "0 6px 18px rgba(0,0,0,.18)"
70
- } }),
71
- /* @__PURE__ */ jsx("div", {
72
- ref,
73
- style: {
70
+ children: /* @__PURE__ */ jsx(ProjectorProvider, {
71
+ value: projectorBinding,
72
+ children: /* @__PURE__ */ jsx("div", {
73
+ style: {
74
+ position: "relative",
75
+ width: outerW,
76
+ height: outerH,
77
+ ...style
78
+ },
79
+ children: /* @__PURE__ */ jsxs(PageProvider, {
80
+ value: ctx,
81
+ children: [
82
+ /* @__PURE__ */ jsx("div", { style: {
74
83
  position: "absolute",
75
- left: contentLeft,
76
- top: contentTop,
77
- width: t.contentWidth,
78
- height: t.contentHeight,
79
- background: "#fff",
80
- transform: rotation ? `rotate(${rotation}deg)` : void 0,
81
- userSelect: "none",
82
- WebkitUserSelect: "none"
83
- },
84
- children
85
- }),
86
- pageChrome
87
- ]
84
+ left: pageFrame.left,
85
+ top: pageFrame.top,
86
+ width: t.viewWidth,
87
+ height: t.viewHeight,
88
+ boxShadow: "0 6px 18px rgba(0,0,0,.18)"
89
+ } }),
90
+ /* @__PURE__ */ jsx("div", {
91
+ ref,
92
+ style: {
93
+ position: "absolute",
94
+ left: contentLeft,
95
+ top: contentTop,
96
+ width: t.contentWidth,
97
+ height: t.contentHeight,
98
+ background: "#fff",
99
+ transform: rotation ? `rotate(${rotation}deg)` : void 0,
100
+ userSelect: "none",
101
+ WebkitUserSelect: "none"
102
+ },
103
+ children
104
+ }),
105
+ pageChrome
106
+ ]
107
+ })
88
108
  })
89
109
  })
90
110
  });
@@ -1 +1 @@
1
- {"version":3,"file":"page-view.js","names":[],"sources":["../src/page-view.tsx"],"sourcesContent":["/**\n * <PageView> — a single page surface with NO Stage.\n *\n * Same layers + rotation + chrome frame as a `<Stage>` page, but no\n * camera/scroll/zoom and, crucially, NO dependency on `@embedpdf/plugin-stage`.\n * It builds its own `PageTransform` from a target content width and shares the\n * exact `PageContext` seam, so every layer (RenderLayer, AnnotationLayer,\n * PageAnnotationMenu, …) works here identically.\n */\nimport * as React from 'react';\nimport { useMemo, useRef } from 'react';\nimport { NO_FRAME, pageTransform, type PageFrame } from '@embedpdf/core-geometry';\nimport {\n DocumentScope,\n makePageContext,\n PageProvider,\n useActiveDocumentId,\n useKernel,\n} from './runtime';\n\nexport interface PageViewProps {\n page: number;\n /** Which document to show. Defaults to the active document. */\n documentId?: string;\n /** Target width for the page CONTENT; the display box is the rotated footprint. */\n width?: number;\n /** Reserved chrome bands around the page (screen px) — same model as `<Stage>`. */\n pageFrame?: PageFrame;\n /** Page-space content (rotates with the page). */\n children: React.ReactNode;\n /** Box-space chrome (label, border, …) — never rotated. Mirrors `<Stage pageChrome>`. */\n pageChrome?: React.ReactNode;\n style?: React.CSSProperties;\n}\n\n/** A single page surface with NO Stage — same layers + rotation + chrome frame,\n * no camera/scroll/zoom. */\nexport function PageView({\n page,\n documentId,\n width = 240,\n pageFrame = NO_FRAME,\n children,\n pageChrome,\n style,\n}: PageViewProps) {\n const kernel = useKernel();\n const active = useActiveDocumentId();\n const ref = useRef<HTMLDivElement>(null);\n const docId = documentId ?? active;\n const meta = docId ? kernel.getState().core.documents[docId] : undefined;\n const base = meta?.pages[page];\n const pon = base?.pageObjectNumber ?? page + 1;\n const rotation = base?.rotation ?? 0;\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n // Standalone (no Stage/camera): build the page's transform from the target\n // content `width` directly. `scale` is view px per point = width / pageWidthPts.\n const transform = useMemo(\n () =>\n pageTransform({\n pageSize: base\n ? { width: base.size.width, height: base.size.height }\n : { width: 1, height: 1 },\n rotation,\n scale: base ? width / base.size.width : 1,\n // Physical 100% on the web: 1pt = 96/72 CSS px, times the page's\n // /UserUnit — so `transform.zoom` is meaningful even without a Stage\n // (a thumbnail-sized PageView reads as zoomed out, as it should).\n baseScale: (96 / 72) * (base?.userUnit ?? 1),\n dpr,\n }),\n [base?.size.width, base?.size.height, base?.userUnit, rotation, width, dpr],\n );\n const ctx = useMemo(\n () =>\n makePageContext(docId ?? '', pon, page, pageFrame, transform, () =>\n ref.current!.getBoundingClientRect(),\n ),\n [docId, pon, page, pageFrame, transform],\n );\n if (!docId || !meta || !base) return null;\n const t = transform;\n const outerW = t.viewWidth + pageFrame.left + pageFrame.right;\n const outerH = t.viewHeight + pageFrame.top + pageFrame.bottom;\n const contentLeft = pageFrame.left + (t.viewWidth - t.contentWidth) / 2;\n const contentTop = pageFrame.top + (t.viewHeight - t.contentHeight) / 2;\n return (\n <DocumentScope id={docId}>\n <div style={{ position: 'relative', width: outerW, height: outerH, ...style }}>\n <PageProvider value={ctx}>\n {/* drop shadow ONLY — transparent, axis-aligned, can't leak behind the bitmap */}\n <div\n style={{\n position: 'absolute',\n left: pageFrame.left,\n top: pageFrame.top,\n width: t.viewWidth,\n height: t.viewHeight,\n boxShadow: '0 6px 18px rgba(0,0,0,.18)',\n }}\n />\n {/* white backing + content as ONE box; rotation 0 carries no transform */}\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 userSelect: 'none',\n WebkitUserSelect: 'none',\n }}\n >\n {children}\n </div>\n {pageChrome}\n </PageProvider>\n </div>\n </DocumentScope>\n );\n}\n"],"mappings":";;;;;;;AAqCA,SAAgB,SAAS,EACvB,MACA,YACA,QAAQ,KACR,YAAY,UACZ,UACA,YACA,SACgB;CAChB,MAAM,SAAS,UAAU;CACzB,MAAM,SAAS,oBAAoB;CACnC,MAAM,MAAM,OAAuB,IAAI;CACvC,MAAM,QAAQ,cAAc;CAC5B,MAAM,OAAO,QAAQ,OAAO,SAAS,CAAC,CAAC,KAAK,UAAU,SAAS,KAAA;CAC/D,MAAM,OAAO,MAAM,MAAM;CACzB,MAAM,MAAM,MAAM,oBAAoB,OAAO;CAC7C,MAAM,WAAW,MAAM,YAAY;CACnC,MAAM,MAAM,OAAO,WAAW,cAAc,OAAO,oBAAoB,IAAI;CAG3E,MAAM,YAAY,cAEd,cAAc;EACZ,UAAU,OACN;GAAE,OAAO,KAAK,KAAK;GAAO,QAAQ,KAAK,KAAK;EAAO,IACnD;GAAE,OAAO;GAAG,QAAQ;EAAE;EAC1B;EACA,OAAO,OAAO,QAAQ,KAAK,KAAK,QAAQ;EAIxC,WAAY,KAAK,MAAO,MAAM,YAAY;EAC1C;CACF,CAAC,GACH;EAAC,MAAM,KAAK;EAAO,MAAM,KAAK;EAAQ,MAAM;EAAU;EAAU;EAAO;CAAG,CAC5E;CACA,MAAM,MAAM,cAER,gBAAgB,SAAS,IAAI,KAAK,MAAM,WAAW,iBACjD,IAAI,QAAS,sBAAsB,CACrC,GACF;EAAC;EAAO;EAAK;EAAM;EAAW;CAAS,CACzC;CACA,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,OAAO;CACrC,MAAM,IAAI;CACV,MAAM,SAAS,EAAE,YAAY,UAAU,OAAO,UAAU;CACxD,MAAM,SAAS,EAAE,aAAa,UAAU,MAAM,UAAU;CACxD,MAAM,cAAc,UAAU,QAAQ,EAAE,YAAY,EAAE,gBAAgB;CACtE,MAAM,aAAa,UAAU,OAAO,EAAE,aAAa,EAAE,iBAAiB;CACtE,OACE,oBAAC,eAAD;EAAe,IAAI;EACjB,UAAA,oBAAC,OAAD;GAAK,OAAO;IAAE,UAAU;IAAY,OAAO;IAAQ,QAAQ;IAAQ,GAAG;GAAM;GAC1E,UAAA,qBAAC,cAAD;IAAc,OAAO;IAArB,UAAA;KAEE,oBAAC,OAAD,EACE,OAAO;MACL,UAAU;MACV,MAAM,UAAU;MAChB,KAAK,UAAU;MACf,OAAO,EAAE;MACT,QAAQ,EAAE;MACV,WAAW;KACb,EACD,CAAA;KAED,oBAAC,OAAD;MACO;MACL,OAAO;OACL,UAAU;OACV,MAAM;OACN,KAAK;OACL,OAAO,EAAE;OACT,QAAQ,EAAE;OACV,YAAY;OACZ,WAAW,WAAW,UAAU,SAAS,QAAQ,KAAA;OACjD,YAAY;OACZ,kBAAkB;MACpB;MAEC;KACE,CAAA;KACJ;IACW;;EACX,CAAA;CACQ,CAAA;AAEnB"}
1
+ {"version":3,"file":"page-view.js","names":[],"sources":["../src/page-view.tsx"],"sourcesContent":["/**\n * <PageView> — a single page surface with NO Stage.\n *\n * Same layers + rotation + chrome frame as a `<Stage>` page, but no\n * camera/scroll/zoom and, crucially, NO dependency on `@embedpdf/plugin-stage`.\n * It builds its own `PageTransform` from a target content width and shares the\n * exact `PageContext` seam, so every layer (RenderLayer, AnnotationLayer, …)\n * works here identically — and it provides the measured `ViewProjector`, so\n * anchored UI (`<AnnotationMenu>`, `<SelectionMenu>`) works here too, portalled\n * and clipping-immune.\n */\nimport * as React from 'react';\nimport { useMemo, useRef } from 'react';\nimport { NO_FRAME, pageTransform, type PageFrame } from '@embedpdf/core-geometry';\nimport { observeClientGeometry } from '@embedpdf/web';\nimport { ProjectorProvider, type ProjectorBinding, type ViewProjector } from './anchored';\nimport {\n DocumentScope,\n makePageContext,\n PageProvider,\n useActiveDocumentId,\n useKernel,\n} from './runtime';\n\nexport interface PageViewProps {\n page: number;\n /** Which document to show. Defaults to the active document. */\n documentId?: string;\n /** Target width for the page CONTENT; the display box is the rotated footprint. */\n width?: number;\n /** Reserved chrome bands around the page (screen px) — same model as `<Stage>`. */\n pageFrame?: PageFrame;\n /** Page-space content (rotates with the page). */\n children: React.ReactNode;\n /** Box-space chrome (label, border, …) — never rotated. Mirrors `<Stage pageChrome>`. */\n pageChrome?: React.ReactNode;\n style?: React.CSSProperties;\n}\n\n/** A single page surface with NO Stage — same layers + rotation + chrome frame,\n * no camera/scroll/zoom. */\nexport function PageView({\n page,\n documentId,\n width = 240,\n pageFrame = NO_FRAME,\n children,\n pageChrome,\n style,\n}: PageViewProps) {\n const kernel = useKernel();\n const active = useActiveDocumentId();\n const ref = useRef<HTMLDivElement>(null);\n const docId = documentId ?? active;\n const meta = docId ? kernel.getState().core.documents[docId] : undefined;\n const base = meta?.pages[page];\n const pon = base?.pageObjectNumber ?? page + 1;\n const rotation = base?.rotation ?? 0;\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n // Standalone (no Stage/camera): build the page's transform from the target\n // content `width` directly. `scale` is view px per point = width / pageWidthPts.\n const transform = useMemo(\n () =>\n pageTransform({\n pageSize: base\n ? { width: base.size.width, height: base.size.height }\n : { width: 1, height: 1 },\n rotation,\n scale: base ? width / base.size.width : 1,\n // Physical 100% on the web: 1pt = 96/72 CSS px, times the page's\n // /UserUnit — so `transform.zoom` is meaningful even without a Stage\n // (a thumbnail-sized PageView reads as zoomed out, as it should).\n baseScale: (96 / 72) * (base?.userUnit ?? 1),\n dpr,\n }),\n [base?.size.width, base?.size.height, base?.userUnit, rotation, width, dpr],\n );\n const ctx = useMemo(\n () =>\n makePageContext(docId ?? '', pon, page, pageFrame, transform, () =>\n ref.current!.getBoundingClientRect(),\n ),\n [docId, pon, page, pageFrame, transform],\n );\n // The PageView's ViewProjector: no camera, so anchored UI positions by\n // MEASURING the DOM (client space → portal + position:fixed, immune to\n // ancestor overflow clipping). `toScreen` answers null until the page\n // element has committed — <Anchored> forces one post-commit pass to pick\n // it up. The binding's revision covers state-driven changes (a new\n // transform/page); `observeClientGeometry` covers the genuinely\n // browser-driven ones (document scroll, window resize) that no state\n // change announces.\n const projector = useMemo<ViewProjector>(\n () => ({\n space: 'client',\n toScreen: (p, rect) => (p === ctx.pon && ref.current ? ctx.toClientRect(rect) : null),\n toScreenPoint: (p, at) => (p === ctx.pon && ref.current ? ctx.toClientPoint(at) : null),\n viewEnv: (p) =>\n p === ctx.pon\n ? {\n scale: ctx.transform.viewScale,\n rotation: ctx.transform.rotation,\n zoom: ctx.transform.zoom,\n }\n : null,\n }),\n [ctx],\n );\n const projectorBinding = useMemo<ProjectorBinding>(\n () => ({ projector, revision: ctx, subscribe: observeClientGeometry }),\n [projector, ctx],\n );\n if (!docId || !meta || !base) return null;\n const t = transform;\n const outerW = t.viewWidth + pageFrame.left + pageFrame.right;\n const outerH = t.viewHeight + pageFrame.top + pageFrame.bottom;\n const contentLeft = pageFrame.left + (t.viewWidth - t.contentWidth) / 2;\n const contentTop = pageFrame.top + (t.viewHeight - t.contentHeight) / 2;\n return (\n <DocumentScope id={docId}>\n <ProjectorProvider value={projectorBinding}>\n <div style={{ position: 'relative', width: outerW, height: outerH, ...style }}>\n <PageProvider value={ctx}>\n {/* drop shadow ONLY — transparent, axis-aligned, can't leak behind the bitmap */}\n <div\n style={{\n position: 'absolute',\n left: pageFrame.left,\n top: pageFrame.top,\n width: t.viewWidth,\n height: t.viewHeight,\n boxShadow: '0 6px 18px rgba(0,0,0,.18)',\n }}\n />\n {/* white backing + content as ONE box; rotation 0 carries no transform */}\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 userSelect: 'none',\n WebkitUserSelect: 'none',\n }}\n >\n {children}\n </div>\n {pageChrome}\n </PageProvider>\n </div>\n </ProjectorProvider>\n </DocumentScope>\n );\n}\n"],"mappings":";;;;;;;;;AAyCA,SAAgB,SAAS,EACvB,MACA,YACA,QAAQ,KACR,YAAY,UACZ,UACA,YACA,SACgB;CAChB,MAAM,SAAS,UAAU;CACzB,MAAM,SAAS,oBAAoB;CACnC,MAAM,MAAM,OAAuB,IAAI;CACvC,MAAM,QAAQ,cAAc;CAC5B,MAAM,OAAO,QAAQ,OAAO,SAAS,CAAC,CAAC,KAAK,UAAU,SAAS,KAAA;CAC/D,MAAM,OAAO,MAAM,MAAM;CACzB,MAAM,MAAM,MAAM,oBAAoB,OAAO;CAC7C,MAAM,WAAW,MAAM,YAAY;CACnC,MAAM,MAAM,OAAO,WAAW,cAAc,OAAO,oBAAoB,IAAI;CAG3E,MAAM,YAAY,cAEd,cAAc;EACZ,UAAU,OACN;GAAE,OAAO,KAAK,KAAK;GAAO,QAAQ,KAAK,KAAK;EAAO,IACnD;GAAE,OAAO;GAAG,QAAQ;EAAE;EAC1B;EACA,OAAO,OAAO,QAAQ,KAAK,KAAK,QAAQ;EAIxC,WAAY,KAAK,MAAO,MAAM,YAAY;EAC1C;CACF,CAAC,GACH;EAAC,MAAM,KAAK;EAAO,MAAM,KAAK;EAAQ,MAAM;EAAU;EAAU;EAAO;CAAG,CAC5E;CACA,MAAM,MAAM,cAER,gBAAgB,SAAS,IAAI,KAAK,MAAM,WAAW,iBACjD,IAAI,QAAS,sBAAsB,CACrC,GACF;EAAC;EAAO;EAAK;EAAM;EAAW;CAAS,CACzC;CASA,MAAM,YAAY,eACT;EACL,OAAO;EACP,WAAW,GAAG,SAAU,MAAM,IAAI,OAAO,IAAI,UAAU,IAAI,aAAa,IAAI,IAAI;EAChF,gBAAgB,GAAG,OAAQ,MAAM,IAAI,OAAO,IAAI,UAAU,IAAI,cAAc,EAAE,IAAI;EAClF,UAAU,MACR,MAAM,IAAI,MACN;GACE,OAAO,IAAI,UAAU;GACrB,UAAU,IAAI,UAAU;GACxB,MAAM,IAAI,UAAU;EACtB,IACA;CACR,IACA,CAAC,GAAG,CACN;CACA,MAAM,mBAAmB,eAChB;EAAE;EAAW,UAAU;EAAK,WAAW;CAAsB,IACpE,CAAC,WAAW,GAAG,CACjB;CACA,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,OAAO;CACrC,MAAM,IAAI;CACV,MAAM,SAAS,EAAE,YAAY,UAAU,OAAO,UAAU;CACxD,MAAM,SAAS,EAAE,aAAa,UAAU,MAAM,UAAU;CACxD,MAAM,cAAc,UAAU,QAAQ,EAAE,YAAY,EAAE,gBAAgB;CACtE,MAAM,aAAa,UAAU,OAAO,EAAE,aAAa,EAAE,iBAAiB;CACtE,OACE,oBAAC,eAAD;EAAe,IAAI;EACjB,UAAA,oBAAC,mBAAD;GAAmB,OAAO;GAC1B,UAAA,oBAAC,OAAD;IAAK,OAAO;KAAE,UAAU;KAAY,OAAO;KAAQ,QAAQ;KAAQ,GAAG;IAAM;IAC1E,UAAA,qBAAC,cAAD;KAAc,OAAO;KAArB,UAAA;MAEE,oBAAC,OAAD,EACE,OAAO;OACL,UAAU;OACV,MAAM,UAAU;OAChB,KAAK,UAAU;OACf,OAAO,EAAE;OACT,QAAQ,EAAE;OACV,WAAW;MACb,EACD,CAAA;MAED,oBAAC,OAAD;OACO;OACL,OAAO;QACL,UAAU;QACV,MAAM;QACN,KAAK;QACL,OAAO,EAAE;QACT,QAAQ,EAAE;QACV,YAAY;QACZ,WAAW,WAAW,UAAU,SAAS,QAAQ,KAAA;QACjD,YAAY;QACZ,kBAAkB;OACpB;OAEC;MACE,CAAA;MACJ;KACW;;GACX,CAAA;EACc,CAAA;CACN,CAAA;AAEnB"}
package/dist/search.cjs CHANGED
@@ -17,25 +17,42 @@ function SearchLayer({ color = "#ffd500", activeColor = "#ff9632", blendMode = "
17
17
  inset: 0,
18
18
  pointerEvents: "none"
19
19
  },
20
- children: hits.map((hit) => hit.rects.map((r, i) => {
21
- const tl = page.transform.pageToContent({
22
- x: r.x,
23
- y: r.y
24
- });
25
- const br = page.transform.pageToContent({
26
- x: r.x + r.width,
27
- y: r.y + r.height
28
- });
29
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
30
- position: "absolute",
31
- left: tl.x,
32
- top: tl.y,
33
- width: br.x - tl.x,
34
- height: br.y - tl.y,
35
- background: hit === active ? activeColor : color,
36
- mixBlendMode: blendMode,
37
- borderRadius: 2
38
- } }, `${hit.charStart}:${i}`);
20
+ children: hits.map((hit) => hit.segments.map(({ quad: q }, i) => {
21
+ const fill = hit === active ? activeColor : color;
22
+ if (q.upperStart.y === q.upperEnd.y && q.lowerStart.y === q.lowerEnd.y && q.upperStart.x === q.lowerStart.x) {
23
+ const tl = page.transform.pageToContent(q.upperStart);
24
+ const br = page.transform.pageToContent(q.lowerEnd);
25
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
26
+ position: "absolute",
27
+ left: tl.x,
28
+ top: tl.y,
29
+ width: br.x - tl.x,
30
+ height: br.y - tl.y,
31
+ background: fill,
32
+ mixBlendMode: blendMode,
33
+ borderRadius: 2
34
+ } }, `${hit.charStart}:${i}`);
35
+ }
36
+ const ring = [
37
+ q.upperStart,
38
+ q.upperEnd,
39
+ q.lowerEnd,
40
+ q.lowerStart
41
+ ].map((p) => page.transform.pageToContent(p));
42
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
43
+ style: {
44
+ position: "absolute",
45
+ inset: 0,
46
+ width: "100%",
47
+ height: "100%",
48
+ overflow: "visible",
49
+ mixBlendMode: blendMode
50
+ },
51
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polygon", {
52
+ points: ring.map((p) => `${p.x},${p.y}`).join(" "),
53
+ fill
54
+ })
55
+ }, `${hit.charStart}:${i}`);
39
56
  }))
40
57
  });
41
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"search.cjs","names":["usePage","useSelector","SearchToken","shallowArray","useCapability"],"sources":["../src/search.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-search.\n *\n * <SearchLayer> is a dumb renderer, the SelectionLayer's twin: it reads\n * the page's content-space hit rects from the capability and paints them\n * through PageContext.pageToContent. The active hit gets its own colour.\n * No pointer handling, no engine calls — search is driven from app chrome\n * via useSearch().\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-search';\nimport * as React from 'react';\nimport { SearchToken } from '@embedpdf/plugin-search';\nimport type { SearchHit } from '@embedpdf/plugin-search';\nimport { shallowArray, useCapability, usePage, useSelector } from './runtime';\n\nexport interface SearchLayerProps {\n /** Highlight colour for hits — SOLID (default: highlighter yellow). */\n color?: string;\n /** Highlight colour for the ACTIVE hit — SOLID (default: orange). */\n activeColor?: string;\n /**\n * How the highlight composites with the page. `'multiply'` (default) is\n * the real-highlighter look: text stays crisp black through the colour,\n * only the paper tints. Pass `'normal'` (with translucent colours) for\n * dark/scanned documents where multiply-on-dark would vanish.\n */\n blendMode?: React.CSSProperties['mixBlendMode'];\n}\n\nexport function SearchLayer({\n color = '#ffd500',\n activeColor = '#ff9632',\n blendMode = 'multiply',\n}: SearchLayerProps) {\n const page = usePage();\n const hits = useSelector(SearchToken, (c) => c.hitsForPage(page.pon), shallowArray);\n const active = useSelector(SearchToken, (c) => c.activeHit());\n\n if (hits.length === 0) return null;\n\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {hits.map((hit: SearchHit) =>\n hit.rects.map((r, i) => {\n const tl = page.transform.pageToContent({ x: r.x, y: r.y });\n const br = page.transform.pageToContent({ x: r.x + r.width, y: r.y + r.height });\n return (\n <div\n key={`${hit.charStart}:${i}`}\n style={{\n position: 'absolute',\n left: tl.x,\n top: tl.y,\n width: br.x - tl.x,\n height: br.y - tl.y,\n background: hit === active ? activeColor : color,\n mixBlendMode: blendMode,\n borderRadius: 2,\n }}\n />\n );\n }),\n )}\n </div>\n );\n}\n\n/** The search capability (search/clear/next/prev/…) for app chrome. */\nexport function useSearch() {\n return useCapability(SearchToken);\n}\n\n/** Reactive search read-model for chrome: the query, status, counts, progress. */\nexport function useSearchState() {\n const query = useSelector(SearchToken, (c) => c.query());\n const status = useSelector(SearchToken, (c) => c.status());\n const hitCount = useSelector(SearchToken, (c) => c.hitCount());\n const activeIndex = useSelector(SearchToken, (c) => c.activeIndex());\n const progress = useSelector(\n SearchToken,\n (c) => c.progress(),\n (a, b) => a.scanned === b.scanned && a.total === b.total,\n );\n const error = useSelector(SearchToken, (c) => c.errorMessage());\n return { query, status, hitCount, activeIndex, progress, error };\n}\n"],"mappings":";;;;;;;;AA+BA,SAAgB,YAAY,EAC1B,QAAQ,WACR,cAAc,WACd,YAAY,cACO;CACnB,MAAM,OAAOA,gBAAAA,QAAQ;CACrB,MAAM,OAAOC,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,YAAY,KAAK,GAAG,GAAGC,gBAAAA,YAAY;CAClF,MAAM,SAASF,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,UAAU,CAAC;CAE5D,IAAI,KAAK,WAAW,GAAG,OAAO;CAE9B,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG,eAAe;EAAO;EACjE,UAAA,KAAK,KAAK,QACT,IAAI,MAAM,KAAK,GAAG,MAAM;GACtB,MAAM,KAAK,KAAK,UAAU,cAAc;IAAE,GAAG,EAAE;IAAG,GAAG,EAAE;GAAE,CAAC;GAC1D,MAAM,KAAK,KAAK,UAAU,cAAc;IAAE,GAAG,EAAE,IAAI,EAAE;IAAO,GAAG,EAAE,IAAI,EAAE;GAAO,CAAC;GAC/E,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD,EAEE,OAAO;IACL,UAAU;IACV,MAAM,GAAG;IACT,KAAK,GAAG;IACR,OAAO,GAAG,IAAI,GAAG;IACjB,QAAQ,GAAG,IAAI,GAAG;IAClB,YAAY,QAAQ,SAAS,cAAc;IAC3C,cAAc;IACd,cAAc;GAChB,EACD,GAXM,GAAG,IAAI,UAAU,GAAG,GAW1B;EAEL,CAAC,CACH;CACG,CAAA;AAET;;AAGA,SAAgB,YAAY;CAC1B,OAAOE,gBAAAA,cAAcF,wBAAAA,WAAW;AAClC;;AAGA,SAAgB,iBAAiB;CAW/B,OAAO;EAAE,OAVKD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,MAAM,CAUzC;EAAG,QATDD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,OAAO,CASnC;EAAG,UARPD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,SAAS,CAQ7B;EAAG,aAPdD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,YAAY,CAOtB;EAAG,UAN9BD,gBAAAA,YACfC,wBAAAA,cACC,MAAM,EAAE,SAAS,IACjB,GAAG,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,KAGC;EAAG,OAD3CD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,aAAa,CACA;CAAE;AACjE"}
1
+ {"version":3,"file":"search.cjs","names":["usePage","useSelector","SearchToken","shallowArray","useCapability"],"sources":["../src/search.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-search.\n *\n * <SearchLayer> is a dumb renderer, the SelectionLayer's twin: it reads\n * the page's content-space hit rects from the capability and paints them\n * through PageContext.pageToContent. The active hit gets its own colour.\n * No pointer handling, no engine calls — search is driven from app chrome\n * via useSearch().\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-search';\nimport * as React from 'react';\nimport { SearchToken } from '@embedpdf/plugin-search';\nimport type { SearchHit } from '@embedpdf/plugin-search';\nimport { shallowArray, useCapability, usePage, useSelector } from './runtime';\n\nexport interface SearchLayerProps {\n /** Highlight colour for hits — SOLID (default: highlighter yellow). */\n color?: string;\n /** Highlight colour for the ACTIVE hit — SOLID (default: orange). */\n activeColor?: string;\n /**\n * How the highlight composites with the page. `'multiply'` (default) is\n * the real-highlighter look: text stays crisp black through the colour,\n * only the paper tints. Pass `'normal'` (with translucent colours) for\n * dark/scanned documents where multiply-on-dark would vanish.\n */\n blendMode?: React.CSSProperties['mixBlendMode'];\n}\n\nexport function SearchLayer({\n color = '#ffd500',\n activeColor = '#ff9632',\n blendMode = 'multiply',\n}: SearchLayerProps) {\n const page = usePage();\n const hits = useSelector(SearchToken, (c) => c.hitsForPage(page.pon), shallowArray);\n const active = useSelector(SearchToken, (c) => c.activeHit());\n\n if (hits.length === 0) return null;\n\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {hits.map((hit: SearchHit) =>\n hit.segments.map(({ quad: q }, i) => {\n const fill = hit === active ? activeColor : color;\n // Upright hits keep the classic rounded div (pixel-identical to the\n // pre-orientation layer); rotated hits draw their true oriented cell.\n const upright =\n q.upperStart.y === q.upperEnd.y &&\n q.lowerStart.y === q.lowerEnd.y &&\n q.upperStart.x === q.lowerStart.x;\n if (upright) {\n const tl = page.transform.pageToContent(q.upperStart);\n const br = page.transform.pageToContent(q.lowerEnd);\n return (\n <div\n key={`${hit.charStart}:${i}`}\n style={{\n position: 'absolute',\n left: tl.x,\n top: tl.y,\n width: br.x - tl.x,\n height: br.y - tl.y,\n background: fill,\n mixBlendMode: blendMode,\n borderRadius: 2,\n }}\n />\n );\n }\n const ring = [q.upperStart, q.upperEnd, q.lowerEnd, q.lowerStart].map((p) =>\n page.transform.pageToContent(p),\n );\n return (\n <svg\n key={`${hit.charStart}:${i}`}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n overflow: 'visible',\n mixBlendMode: blendMode,\n }}\n >\n <polygon points={ring.map((p) => `${p.x},${p.y}`).join(' ')} fill={fill} />\n </svg>\n );\n }),\n )}\n </div>\n );\n}\n\n/** The search capability (search/clear/next/prev/…) for app chrome. */\nexport function useSearch() {\n return useCapability(SearchToken);\n}\n\n/** Reactive search read-model for chrome: the query, status, counts, progress. */\nexport function useSearchState() {\n const query = useSelector(SearchToken, (c) => c.query());\n const status = useSelector(SearchToken, (c) => c.status());\n const hitCount = useSelector(SearchToken, (c) => c.hitCount());\n const activeIndex = useSelector(SearchToken, (c) => c.activeIndex());\n const progress = useSelector(\n SearchToken,\n (c) => c.progress(),\n (a, b) => a.scanned === b.scanned && a.total === b.total,\n );\n const error = useSelector(SearchToken, (c) => c.errorMessage());\n return { query, status, hitCount, activeIndex, progress, error };\n}\n"],"mappings":";;;;;;;;AA+BA,SAAgB,YAAY,EAC1B,QAAQ,WACR,cAAc,WACd,YAAY,cACO;CACnB,MAAM,OAAOA,gBAAAA,QAAQ;CACrB,MAAM,OAAOC,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,YAAY,KAAK,GAAG,GAAGC,gBAAAA,YAAY;CAClF,MAAM,SAASF,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,UAAU,CAAC;CAE5D,IAAI,KAAK,WAAW,GAAG,OAAO;CAE9B,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG,eAAe;EAAO;EACjE,UAAA,KAAK,KAAK,QACT,IAAI,SAAS,KAAK,EAAE,MAAM,KAAK,MAAM;GACnC,MAAM,OAAO,QAAQ,SAAS,cAAc;GAO5C,IAHE,EAAE,WAAW,MAAM,EAAE,SAAS,KAC9B,EAAE,WAAW,MAAM,EAAE,SAAS,KAC9B,EAAE,WAAW,MAAM,EAAE,WAAW,GACrB;IACX,MAAM,KAAK,KAAK,UAAU,cAAc,EAAE,UAAU;IACpD,MAAM,KAAK,KAAK,UAAU,cAAc,EAAE,QAAQ;IAClD,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD,EAEE,OAAO;KACL,UAAU;KACV,MAAM,GAAG;KACT,KAAK,GAAG;KACR,OAAO,GAAG,IAAI,GAAG;KACjB,QAAQ,GAAG,IAAI,GAAG;KAClB,YAAY;KACZ,cAAc;KACd,cAAc;IAChB,EACD,GAXM,GAAG,IAAI,UAAU,GAAG,GAW1B;GAEL;GACA,MAAM,OAAO;IAAC,EAAE;IAAY,EAAE;IAAU,EAAE;IAAU,EAAE;GAAU,CAAC,CAAC,KAAK,MACrE,KAAK,UAAU,cAAc,CAAC,CAChC;GACA,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;IAEE,OAAO;KACL,UAAU;KACV,OAAO;KACP,OAAO;KACP,QAAQ;KACR,UAAU;KACV,cAAc;IAChB;IAEA,UAAA,iBAAA,GAAA,kBAAA,IAAA,CAAC,WAAD;KAAS,QAAQ,KAAK,KAAK,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG;KAAS;IAAO,CAAA;GACvE,GAXE,GAAG,IAAI,UAAU,GAAG,GAWtB;EAET,CAAC,CACH;CACG,CAAA;AAET;;AAGA,SAAgB,YAAY;CAC1B,OAAOE,gBAAAA,cAAcF,wBAAAA,WAAW;AAClC;;AAGA,SAAgB,iBAAiB;CAW/B,OAAO;EAAE,OAVKD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,MAAM,CAUzC;EAAG,QATDD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,OAAO,CASnC;EAAG,UARPD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,SAAS,CAQ7B;EAAG,aAPdD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,YAAY,CAOtB;EAAG,UAN9BD,gBAAAA,YACfC,wBAAAA,cACC,MAAM,EAAE,SAAS,IACjB,GAAG,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,KAGC;EAAG,OAD3CD,gBAAAA,YAAYC,wBAAAA,cAAc,MAAM,EAAE,aAAa,CACA;CAAE;AACjE"}
package/dist/search.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as SearchQuery } from "./AnnotationListSnapshot-W0RCsyDE-dWna4TTJ.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-W0RCsyDE-dWna4TTJ.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
package/dist/search.js CHANGED
@@ -15,25 +15,42 @@ function SearchLayer({ color = "#ffd500", activeColor = "#ff9632", blendMode = "
15
15
  inset: 0,
16
16
  pointerEvents: "none"
17
17
  },
18
- children: hits.map((hit) => hit.rects.map((r, i) => {
19
- const tl = page.transform.pageToContent({
20
- x: r.x,
21
- y: r.y
22
- });
23
- const br = page.transform.pageToContent({
24
- x: r.x + r.width,
25
- y: r.y + r.height
26
- });
27
- return /* @__PURE__ */ jsx("div", { style: {
28
- position: "absolute",
29
- left: tl.x,
30
- top: tl.y,
31
- width: br.x - tl.x,
32
- height: br.y - tl.y,
33
- background: hit === active ? activeColor : color,
34
- mixBlendMode: blendMode,
35
- borderRadius: 2
36
- } }, `${hit.charStart}:${i}`);
18
+ children: hits.map((hit) => hit.segments.map(({ quad: q }, i) => {
19
+ const fill = hit === active ? activeColor : color;
20
+ if (q.upperStart.y === q.upperEnd.y && q.lowerStart.y === q.lowerEnd.y && q.upperStart.x === q.lowerStart.x) {
21
+ const tl = page.transform.pageToContent(q.upperStart);
22
+ const br = page.transform.pageToContent(q.lowerEnd);
23
+ return /* @__PURE__ */ jsx("div", { style: {
24
+ position: "absolute",
25
+ left: tl.x,
26
+ top: tl.y,
27
+ width: br.x - tl.x,
28
+ height: br.y - tl.y,
29
+ background: fill,
30
+ mixBlendMode: blendMode,
31
+ borderRadius: 2
32
+ } }, `${hit.charStart}:${i}`);
33
+ }
34
+ const ring = [
35
+ q.upperStart,
36
+ q.upperEnd,
37
+ q.lowerEnd,
38
+ q.lowerStart
39
+ ].map((p) => page.transform.pageToContent(p));
40
+ return /* @__PURE__ */ jsx("svg", {
41
+ style: {
42
+ position: "absolute",
43
+ inset: 0,
44
+ width: "100%",
45
+ height: "100%",
46
+ overflow: "visible",
47
+ mixBlendMode: blendMode
48
+ },
49
+ children: /* @__PURE__ */ jsx("polygon", {
50
+ points: ring.map((p) => `${p.x},${p.y}`).join(" "),
51
+ fill
52
+ })
53
+ }, `${hit.charStart}:${i}`);
37
54
  }))
38
55
  });
39
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","names":[],"sources":["../src/search.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-search.\n *\n * <SearchLayer> is a dumb renderer, the SelectionLayer's twin: it reads\n * the page's content-space hit rects from the capability and paints them\n * through PageContext.pageToContent. The active hit gets its own colour.\n * No pointer handling, no engine calls — search is driven from app chrome\n * via useSearch().\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-search';\nimport * as React from 'react';\nimport { SearchToken } from '@embedpdf/plugin-search';\nimport type { SearchHit } from '@embedpdf/plugin-search';\nimport { shallowArray, useCapability, usePage, useSelector } from './runtime';\n\nexport interface SearchLayerProps {\n /** Highlight colour for hits — SOLID (default: highlighter yellow). */\n color?: string;\n /** Highlight colour for the ACTIVE hit — SOLID (default: orange). */\n activeColor?: string;\n /**\n * How the highlight composites with the page. `'multiply'` (default) is\n * the real-highlighter look: text stays crisp black through the colour,\n * only the paper tints. Pass `'normal'` (with translucent colours) for\n * dark/scanned documents where multiply-on-dark would vanish.\n */\n blendMode?: React.CSSProperties['mixBlendMode'];\n}\n\nexport function SearchLayer({\n color = '#ffd500',\n activeColor = '#ff9632',\n blendMode = 'multiply',\n}: SearchLayerProps) {\n const page = usePage();\n const hits = useSelector(SearchToken, (c) => c.hitsForPage(page.pon), shallowArray);\n const active = useSelector(SearchToken, (c) => c.activeHit());\n\n if (hits.length === 0) return null;\n\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {hits.map((hit: SearchHit) =>\n hit.rects.map((r, i) => {\n const tl = page.transform.pageToContent({ x: r.x, y: r.y });\n const br = page.transform.pageToContent({ x: r.x + r.width, y: r.y + r.height });\n return (\n <div\n key={`${hit.charStart}:${i}`}\n style={{\n position: 'absolute',\n left: tl.x,\n top: tl.y,\n width: br.x - tl.x,\n height: br.y - tl.y,\n background: hit === active ? activeColor : color,\n mixBlendMode: blendMode,\n borderRadius: 2,\n }}\n />\n );\n }),\n )}\n </div>\n );\n}\n\n/** The search capability (search/clear/next/prev/…) for app chrome. */\nexport function useSearch() {\n return useCapability(SearchToken);\n}\n\n/** Reactive search read-model for chrome: the query, status, counts, progress. */\nexport function useSearchState() {\n const query = useSelector(SearchToken, (c) => c.query());\n const status = useSelector(SearchToken, (c) => c.status());\n const hitCount = useSelector(SearchToken, (c) => c.hitCount());\n const activeIndex = useSelector(SearchToken, (c) => c.activeIndex());\n const progress = useSelector(\n SearchToken,\n (c) => c.progress(),\n (a, b) => a.scanned === b.scanned && a.total === b.total,\n );\n const error = useSelector(SearchToken, (c) => c.errorMessage());\n return { query, status, hitCount, activeIndex, progress, error };\n}\n"],"mappings":";;;;;;AA+BA,SAAgB,YAAY,EAC1B,QAAQ,WACR,cAAc,WACd,YAAY,cACO;CACnB,MAAM,OAAO,QAAQ;CACrB,MAAM,OAAO,YAAY,cAAc,MAAM,EAAE,YAAY,KAAK,GAAG,GAAG,YAAY;CAClF,MAAM,SAAS,YAAY,cAAc,MAAM,EAAE,UAAU,CAAC;CAE5D,IAAI,KAAK,WAAW,GAAG,OAAO;CAE9B,OACE,oBAAC,OAAD;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG,eAAe;EAAO;EACjE,UAAA,KAAK,KAAK,QACT,IAAI,MAAM,KAAK,GAAG,MAAM;GACtB,MAAM,KAAK,KAAK,UAAU,cAAc;IAAE,GAAG,EAAE;IAAG,GAAG,EAAE;GAAE,CAAC;GAC1D,MAAM,KAAK,KAAK,UAAU,cAAc;IAAE,GAAG,EAAE,IAAI,EAAE;IAAO,GAAG,EAAE,IAAI,EAAE;GAAO,CAAC;GAC/E,OACE,oBAAC,OAAD,EAEE,OAAO;IACL,UAAU;IACV,MAAM,GAAG;IACT,KAAK,GAAG;IACR,OAAO,GAAG,IAAI,GAAG;IACjB,QAAQ,GAAG,IAAI,GAAG;IAClB,YAAY,QAAQ,SAAS,cAAc;IAC3C,cAAc;IACd,cAAc;GAChB,EACD,GAXM,GAAG,IAAI,UAAU,GAAG,GAW1B;EAEL,CAAC,CACH;CACG,CAAA;AAET;;AAGA,SAAgB,YAAY;CAC1B,OAAO,cAAc,WAAW;AAClC;;AAGA,SAAgB,iBAAiB;CAW/B,OAAO;EAAE,OAVK,YAAY,cAAc,MAAM,EAAE,MAAM,CAUzC;EAAG,QATD,YAAY,cAAc,MAAM,EAAE,OAAO,CASnC;EAAG,UARP,YAAY,cAAc,MAAM,EAAE,SAAS,CAQ7B;EAAG,aAPd,YAAY,cAAc,MAAM,EAAE,YAAY,CAOtB;EAAG,UAN9B,YACf,cACC,MAAM,EAAE,SAAS,IACjB,GAAG,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,KAGC;EAAG,OAD3C,YAAY,cAAc,MAAM,EAAE,aAAa,CACA;CAAE;AACjE"}
1
+ {"version":3,"file":"search.js","names":[],"sources":["../src/search.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-search.\n *\n * <SearchLayer> is a dumb renderer, the SelectionLayer's twin: it reads\n * the page's content-space hit rects from the capability and paints them\n * through PageContext.pageToContent. The active hit gets its own colour.\n * No pointer handling, no engine calls — search is driven from app chrome\n * via useSearch().\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-search';\nimport * as React from 'react';\nimport { SearchToken } from '@embedpdf/plugin-search';\nimport type { SearchHit } from '@embedpdf/plugin-search';\nimport { shallowArray, useCapability, usePage, useSelector } from './runtime';\n\nexport interface SearchLayerProps {\n /** Highlight colour for hits — SOLID (default: highlighter yellow). */\n color?: string;\n /** Highlight colour for the ACTIVE hit — SOLID (default: orange). */\n activeColor?: string;\n /**\n * How the highlight composites with the page. `'multiply'` (default) is\n * the real-highlighter look: text stays crisp black through the colour,\n * only the paper tints. Pass `'normal'` (with translucent colours) for\n * dark/scanned documents where multiply-on-dark would vanish.\n */\n blendMode?: React.CSSProperties['mixBlendMode'];\n}\n\nexport function SearchLayer({\n color = '#ffd500',\n activeColor = '#ff9632',\n blendMode = 'multiply',\n}: SearchLayerProps) {\n const page = usePage();\n const hits = useSelector(SearchToken, (c) => c.hitsForPage(page.pon), shallowArray);\n const active = useSelector(SearchToken, (c) => c.activeHit());\n\n if (hits.length === 0) return null;\n\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {hits.map((hit: SearchHit) =>\n hit.segments.map(({ quad: q }, i) => {\n const fill = hit === active ? activeColor : color;\n // Upright hits keep the classic rounded div (pixel-identical to the\n // pre-orientation layer); rotated hits draw their true oriented cell.\n const upright =\n q.upperStart.y === q.upperEnd.y &&\n q.lowerStart.y === q.lowerEnd.y &&\n q.upperStart.x === q.lowerStart.x;\n if (upright) {\n const tl = page.transform.pageToContent(q.upperStart);\n const br = page.transform.pageToContent(q.lowerEnd);\n return (\n <div\n key={`${hit.charStart}:${i}`}\n style={{\n position: 'absolute',\n left: tl.x,\n top: tl.y,\n width: br.x - tl.x,\n height: br.y - tl.y,\n background: fill,\n mixBlendMode: blendMode,\n borderRadius: 2,\n }}\n />\n );\n }\n const ring = [q.upperStart, q.upperEnd, q.lowerEnd, q.lowerStart].map((p) =>\n page.transform.pageToContent(p),\n );\n return (\n <svg\n key={`${hit.charStart}:${i}`}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n overflow: 'visible',\n mixBlendMode: blendMode,\n }}\n >\n <polygon points={ring.map((p) => `${p.x},${p.y}`).join(' ')} fill={fill} />\n </svg>\n );\n }),\n )}\n </div>\n );\n}\n\n/** The search capability (search/clear/next/prev/…) for app chrome. */\nexport function useSearch() {\n return useCapability(SearchToken);\n}\n\n/** Reactive search read-model for chrome: the query, status, counts, progress. */\nexport function useSearchState() {\n const query = useSelector(SearchToken, (c) => c.query());\n const status = useSelector(SearchToken, (c) => c.status());\n const hitCount = useSelector(SearchToken, (c) => c.hitCount());\n const activeIndex = useSelector(SearchToken, (c) => c.activeIndex());\n const progress = useSelector(\n SearchToken,\n (c) => c.progress(),\n (a, b) => a.scanned === b.scanned && a.total === b.total,\n );\n const error = useSelector(SearchToken, (c) => c.errorMessage());\n return { query, status, hitCount, activeIndex, progress, error };\n}\n"],"mappings":";;;;;;AA+BA,SAAgB,YAAY,EAC1B,QAAQ,WACR,cAAc,WACd,YAAY,cACO;CACnB,MAAM,OAAO,QAAQ;CACrB,MAAM,OAAO,YAAY,cAAc,MAAM,EAAE,YAAY,KAAK,GAAG,GAAG,YAAY;CAClF,MAAM,SAAS,YAAY,cAAc,MAAM,EAAE,UAAU,CAAC;CAE5D,IAAI,KAAK,WAAW,GAAG,OAAO;CAE9B,OACE,oBAAC,OAAD;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG,eAAe;EAAO;EACjE,UAAA,KAAK,KAAK,QACT,IAAI,SAAS,KAAK,EAAE,MAAM,KAAK,MAAM;GACnC,MAAM,OAAO,QAAQ,SAAS,cAAc;GAO5C,IAHE,EAAE,WAAW,MAAM,EAAE,SAAS,KAC9B,EAAE,WAAW,MAAM,EAAE,SAAS,KAC9B,EAAE,WAAW,MAAM,EAAE,WAAW,GACrB;IACX,MAAM,KAAK,KAAK,UAAU,cAAc,EAAE,UAAU;IACpD,MAAM,KAAK,KAAK,UAAU,cAAc,EAAE,QAAQ;IAClD,OACE,oBAAC,OAAD,EAEE,OAAO;KACL,UAAU;KACV,MAAM,GAAG;KACT,KAAK,GAAG;KACR,OAAO,GAAG,IAAI,GAAG;KACjB,QAAQ,GAAG,IAAI,GAAG;KAClB,YAAY;KACZ,cAAc;KACd,cAAc;IAChB,EACD,GAXM,GAAG,IAAI,UAAU,GAAG,GAW1B;GAEL;GACA,MAAM,OAAO;IAAC,EAAE;IAAY,EAAE;IAAU,EAAE;IAAU,EAAE;GAAU,CAAC,CAAC,KAAK,MACrE,KAAK,UAAU,cAAc,CAAC,CAChC;GACA,OACE,oBAAC,OAAD;IAEE,OAAO;KACL,UAAU;KACV,OAAO;KACP,OAAO;KACP,QAAQ;KACR,UAAU;KACV,cAAc;IAChB;IAEA,UAAA,oBAAC,WAAD;KAAS,QAAQ,KAAK,KAAK,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG;KAAS;IAAO,CAAA;GACvE,GAXE,GAAG,IAAI,UAAU,GAAG,GAWtB;EAET,CAAC,CACH;CACG,CAAA;AAET;;AAGA,SAAgB,YAAY;CAC1B,OAAO,cAAc,WAAW;AAClC;;AAGA,SAAgB,iBAAiB;CAW/B,OAAO;EAAE,OAVK,YAAY,cAAc,MAAM,EAAE,MAAM,CAUzC;EAAG,QATD,YAAY,cAAc,MAAM,EAAE,OAAO,CASnC;EAAG,UARP,YAAY,cAAc,MAAM,EAAE,SAAS,CAQ7B;EAAG,aAPd,YAAY,cAAc,MAAM,EAAE,YAAY,CAOtB;EAAG,UAN9B,YACf,cACC,MAAM,EAAE,SAAS,IACjB,GAAG,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,KAGC;EAAG,OAD3C,YAAY,cAAc,MAAM,EAAE,aAAa,CACA;CAAE;AACjE"}
@@ -1,52 +1,103 @@
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 rects = require_runtime.useSelector(_embedpdf_plugin_selection.SelectionToken, (c) => c.rectsForPage(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]);
17
20
  if (!visible) return null;
18
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
21
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
19
22
  style: {
20
23
  position: "absolute",
21
24
  inset: 0,
25
+ width: "100%",
26
+ height: "100%",
27
+ overflow: "visible",
22
28
  pointerEvents: "none"
23
29
  },
24
- children: rects.map((r, i) => {
25
- const tl = page.transform.pageToContent({
26
- x: r.x,
27
- y: r.y
28
- });
29
- const br = page.transform.pageToContent({
30
- x: r.x + r.width,
31
- y: r.y + r.height
32
- });
33
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
34
- position: "absolute",
35
- left: tl.x,
36
- top: tl.y,
37
- width: br.x - tl.x,
38
- height: br.y - tl.y,
39
- background: color
40
- } }, i);
30
+ children: segments.map((s, i) => {
31
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polygon", {
32
+ points: [
33
+ s.quad.upperStart,
34
+ s.quad.upperEnd,
35
+ s.quad.lowerEnd,
36
+ s.quad.lowerStart
37
+ ].map((p) => page.transform.pageToContent(p)).map((p) => `${p.x},${p.y}`).join(" "),
38
+ fill: color
39
+ }, i);
41
40
  })
42
41
  });
43
42
  }
44
- /** The selection capability (clear(), hasSelection(), …) for app chrome. */
43
+ /** The PUBLIC selection capability (select(), readText(), canCopy(), …) for
44
+ * app chrome — toolbars, context menus, automation. */
45
45
  function useSelection() {
46
46
  return require_runtime.useCapability(_embedpdf_plugin_selection.SelectionToken);
47
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
+ }
48
91
  //#endregion
92
+ exports.SelectionClipboard = SelectionClipboard;
49
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
+ });
50
101
  exports.useSelection = useSelection;
51
102
  Object.keys(_embedpdf_plugin_selection).forEach(function(k) {
52
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 rects = useSelector(SelectionToken, (c) => c.rectsForPage(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 <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {rects.map((r, i) => {\n // content space → un-rotated content view px (rides the page's CSS rotation)\n const tl = page.transform.pageToContent({ x: r.x, y: r.y });\n const br = page.transform.pageToContent({ x: r.x + r.width, y: r.y + r.height });\n return (\n <div\n key={i}\n style={{\n position: 'absolute',\n left: tl.x,\n top: tl.y,\n width: br.x - tl.x,\n height: br.y - tl.y,\n background: color,\n }}\n />\n );\n })}\n </div>\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,QAAQC,gBAAAA,YAAYD,2BAAAA,iBAAiB,MAAM,EAAE,aAAa,KAAK,GAAG,GAAGE,gBAAAA,YAAY;CAGvF,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;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG,eAAe;EAAO;EACjE,UAAA,MAAM,KAAK,GAAG,MAAM;GAEnB,MAAM,KAAK,KAAK,UAAU,cAAc;IAAE,GAAG,EAAE;IAAG,GAAG,EAAE;GAAE,CAAC;GAC1D,MAAM,KAAK,KAAK,UAAU,cAAc;IAAE,GAAG,EAAE,IAAI,EAAE;IAAO,GAAG,EAAE,IAAI,EAAE;GAAO,CAAC;GAC/E,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD,EAEE,OAAO;IACL,UAAU;IACV,MAAM,GAAG;IACT,KAAK,GAAG;IACR,OAAO,GAAG,IAAI,GAAG;IACjB,QAAQ,GAAG,IAAI,GAAG;IAClB,YAAY;GACd,EACD,GATM,CASN;EAEL,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