@embedpdf/react 3.0.0-next.14 → 3.0.0-next.15

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 (232) hide show
  1. package/dist/AnnotationList-CkijRHet-HEFNUIZe.d.cts +877 -0
  2. package/dist/AnnotationList-CkijRHet-HEFNUIZe.d.ts +877 -0
  3. package/dist/actions.cjs +25 -5
  4. package/dist/actions.cjs.map +1 -1
  5. package/dist/actions.d.cts +18 -5
  6. package/dist/actions.d.ts +18 -5
  7. package/dist/actions.js +22 -6
  8. package/dist/actions.js.map +1 -1
  9. package/dist/anchored-DeX95xSd.d.ts +85 -0
  10. package/dist/anchored-DzN3JywS.d.cts +85 -0
  11. package/dist/anchored.cjs +71 -28
  12. package/dist/anchored.cjs.map +1 -1
  13. package/dist/anchored.d.cts +2 -45
  14. package/dist/anchored.d.ts +2 -45
  15. package/dist/anchored.js +56 -19
  16. package/dist/anchored.js.map +1 -1
  17. package/dist/annotation-B48UT2ln.d.ts +302 -0
  18. package/dist/annotation-C1ttM8Wj.js +708 -0
  19. package/dist/annotation-C1ttM8Wj.js.map +1 -0
  20. package/dist/annotation-C9dVwo9b.d.cts +302 -0
  21. package/dist/annotation-Dex5EBda.cjs +830 -0
  22. package/dist/annotation-Dex5EBda.cjs.map +1 -0
  23. package/dist/annotation.cjs +20 -784
  24. package/dist/annotation.d.cts +2 -2
  25. package/dist/annotation.d.ts +2 -2
  26. package/dist/annotation.js +2 -769
  27. package/dist/commands.cjs +68 -28
  28. package/dist/commands.cjs.map +1 -1
  29. package/dist/commands.d.cts +37 -10
  30. package/dist/commands.d.ts +37 -10
  31. package/dist/commands.js +68 -31
  32. package/dist/commands.js.map +1 -1
  33. package/dist/form-55izw0jS.js +511 -0
  34. package/dist/form-55izw0jS.js.map +1 -0
  35. package/dist/form-BNqQfGGH.cjs +548 -0
  36. package/dist/form-BNqQfGGH.cjs.map +1 -0
  37. package/dist/form.cjs +5 -5
  38. package/dist/form.d.cts +41 -2
  39. package/dist/form.d.ts +41 -2
  40. package/dist/form.js +2 -3
  41. package/dist/history.cjs +38 -0
  42. package/dist/history.cjs.map +1 -0
  43. package/dist/history.d.cts +27 -0
  44. package/dist/history.d.ts +27 -0
  45. package/dist/history.js +27 -0
  46. package/dist/history.js.map +1 -0
  47. package/dist/i18n.cjs +26 -30
  48. package/dist/i18n.cjs.map +1 -1
  49. package/dist/i18n.d.cts +24 -21
  50. package/dist/i18n.d.ts +24 -21
  51. package/dist/i18n.js +24 -31
  52. package/dist/i18n.js.map +1 -1
  53. package/dist/index.cjs +200 -57
  54. package/dist/index.d.cts +31 -32
  55. package/dist/index.d.ts +31 -32
  56. package/dist/index.js +32 -30
  57. package/dist/inert-CmBEbKCn.cjs +19 -0
  58. package/dist/inert-CmBEbKCn.cjs.map +1 -0
  59. package/dist/inert-D8fkennb.js +14 -0
  60. package/dist/inert-D8fkennb.js.map +1 -0
  61. package/dist/interaction.cjs +48 -102
  62. package/dist/interaction.cjs.map +1 -1
  63. package/dist/interaction.d.cts +23 -32
  64. package/dist/interaction.d.ts +23 -32
  65. package/dist/interaction.js +33 -102
  66. package/dist/interaction.js.map +1 -1
  67. package/dist/link.cjs +65 -104
  68. package/dist/link.cjs.map +1 -1
  69. package/dist/link.d.cts +21 -16
  70. package/dist/link.d.ts +22 -17
  71. package/dist/link.js +67 -107
  72. package/dist/link.js.map +1 -1
  73. package/dist/measurement.cjs +36 -11
  74. package/dist/measurement.cjs.map +1 -1
  75. package/dist/measurement.d.cts +33 -7
  76. package/dist/measurement.d.ts +33 -7
  77. package/dist/measurement.js +35 -14
  78. package/dist/measurement.js.map +1 -1
  79. package/dist/metadata.cjs +20 -7
  80. package/dist/metadata.cjs.map +1 -1
  81. package/dist/metadata.d.cts +22 -7
  82. package/dist/metadata.d.ts +22 -7
  83. package/dist/metadata.js +20 -10
  84. package/dist/metadata.js.map +1 -1
  85. package/dist/page-edit.cjs +7 -3
  86. package/dist/page-edit.cjs.map +1 -1
  87. package/dist/page-edit.d.cts +6 -2
  88. package/dist/page-edit.d.ts +6 -2
  89. package/dist/page-edit.js +7 -4
  90. package/dist/page-edit.js.map +1 -1
  91. package/dist/page-layers-Bkaaz8Zf.js +26 -0
  92. package/dist/page-layers-Bkaaz8Zf.js.map +1 -0
  93. package/dist/page-layers-BloZDoOV.cjs +37 -0
  94. package/dist/page-layers-BloZDoOV.cjs.map +1 -0
  95. package/dist/page-view.cjs +89 -82
  96. package/dist/page-view.cjs.map +1 -1
  97. package/dist/page-view.d.cts +14 -7
  98. package/dist/page-view.d.ts +18 -11
  99. package/dist/page-view.js +86 -81
  100. package/dist/page-view.js.map +1 -1
  101. package/dist/redaction.cjs +30 -15
  102. package/dist/redaction.cjs.map +1 -1
  103. package/dist/redaction.d.cts +27 -9
  104. package/dist/redaction.d.ts +27 -9
  105. package/dist/redaction.js +29 -18
  106. package/dist/redaction.js.map +1 -1
  107. package/dist/render.cjs +71 -46
  108. package/dist/render.cjs.map +1 -1
  109. package/dist/render.d.cts +17 -5
  110. package/dist/render.d.ts +17 -5
  111. package/dist/render.js +70 -49
  112. package/dist/render.js.map +1 -1
  113. package/dist/runtime-B6xcPsEb.js +398 -0
  114. package/dist/runtime-B6xcPsEb.js.map +1 -0
  115. package/dist/runtime-DBEBlpQV.d.cts +237 -0
  116. package/dist/runtime-V3MVMb91.d.ts +237 -0
  117. package/dist/runtime-fzpGv78v.cjs +555 -0
  118. package/dist/runtime-fzpGv78v.cjs.map +1 -0
  119. package/dist/runtime.cjs +45 -324
  120. package/dist/runtime.d.cts +2 -2
  121. package/dist/runtime.d.ts +2 -2
  122. package/dist/runtime.js +2 -306
  123. package/dist/search.cjs +81 -50
  124. package/dist/search.cjs.map +1 -1
  125. package/dist/search.d.cts +37 -24
  126. package/dist/search.d.ts +37 -24
  127. package/dist/search.js +81 -53
  128. package/dist/search.js.map +1 -1
  129. package/dist/selection.cjs +105 -108
  130. package/dist/selection.cjs.map +1 -1
  131. package/dist/selection.d.cts +54 -26
  132. package/dist/selection.d.ts +56 -28
  133. package/dist/selection.js +103 -110
  134. package/dist/selection.js.map +1 -1
  135. package/dist/shell.cjs +36 -21
  136. package/dist/shell.cjs.map +1 -1
  137. package/dist/shell.d.cts +32 -17
  138. package/dist/shell.d.ts +32 -17
  139. package/dist/shell.js +36 -24
  140. package/dist/shell.js.map +1 -1
  141. package/dist/signature.cjs +29 -48
  142. package/dist/signature.cjs.map +1 -1
  143. package/dist/signature.d.cts +26 -26
  144. package/dist/signature.d.ts +26 -26
  145. package/dist/signature.js +30 -48
  146. package/dist/signature.js.map +1 -1
  147. package/dist/stage-BIG6aeVd.cjs +369 -0
  148. package/dist/stage-BIG6aeVd.cjs.map +1 -0
  149. package/dist/stage-ByFPD-Fv.d.cts +117 -0
  150. package/dist/stage-DaboaheR.d.ts +117 -0
  151. package/dist/stage-DoVekb83.js +302 -0
  152. package/dist/stage-DoVekb83.js.map +1 -0
  153. package/dist/stage-scope-CQgCuf1w.cjs +46 -0
  154. package/dist/stage-scope-CQgCuf1w.cjs.map +1 -0
  155. package/dist/stage-scope-D7ZeNduT.js +23 -0
  156. package/dist/stage-scope-D7ZeNduT.js.map +1 -0
  157. package/dist/stage.cjs +12 -235
  158. package/dist/stage.d.cts +2 -116
  159. package/dist/stage.d.ts +2 -116
  160. package/dist/stage.js +3 -228
  161. package/dist/stamp.cjs +41 -35
  162. package/dist/stamp.cjs.map +1 -1
  163. package/dist/stamp.d.cts +25 -18
  164. package/dist/stamp.d.ts +25 -18
  165. package/dist/stamp.js +40 -37
  166. package/dist/stamp.js.map +1 -1
  167. package/dist/state-0K1I5cM4.d.cts +29 -0
  168. package/dist/state-0K1I5cM4.d.ts +29 -0
  169. package/dist/state-CbgxhwLA.js +62 -0
  170. package/dist/state-CbgxhwLA.js.map +1 -0
  171. package/dist/state-CsfZy_Eq.cjs +73 -0
  172. package/dist/state-CsfZy_Eq.cjs.map +1 -0
  173. package/dist/svg-shapes-Dm1yTR1b.js +40 -0
  174. package/dist/svg-shapes-Dm1yTR1b.js.map +1 -0
  175. package/dist/svg-shapes-zo0GNkc9.cjs +53 -0
  176. package/dist/svg-shapes-zo0GNkc9.cjs.map +1 -0
  177. package/dist/toolbar.cjs +297 -295
  178. package/dist/toolbar.cjs.map +1 -1
  179. package/dist/toolbar.d.cts +58 -68
  180. package/dist/toolbar.d.ts +67 -77
  181. package/dist/toolbar.js +271 -293
  182. package/dist/toolbar.js.map +1 -1
  183. package/dist/view-manager.cjs +34 -0
  184. package/dist/view-manager.cjs.map +1 -0
  185. package/dist/view-manager.d.cts +21 -0
  186. package/dist/view-manager.d.ts +21 -0
  187. package/dist/view-manager.js +23 -0
  188. package/dist/view-manager.js.map +1 -0
  189. package/package.json +37 -39
  190. package/dist/annotation-BCED_p9H.d.cts +0 -160
  191. package/dist/annotation-anchors-CetwM1g7.js +0 -15
  192. package/dist/annotation-anchors-CetwM1g7.js.map +0 -1
  193. package/dist/annotation-anchors-DuYRBJLl.cjs +0 -26
  194. package/dist/annotation-anchors-DuYRBJLl.cjs.map +0 -1
  195. package/dist/annotation-hooks-BB5ZhPpf.cjs +0 -16
  196. package/dist/annotation-hooks-BB5ZhPpf.cjs.map +0 -1
  197. package/dist/annotation-hooks-Df8EmzDO.js +0 -11
  198. package/dist/annotation-hooks-Df8EmzDO.js.map +0 -1
  199. package/dist/annotation-k38KDlIZ.d.ts +0 -160
  200. package/dist/annotation-menu.cjs +0 -64
  201. package/dist/annotation-menu.cjs.map +0 -1
  202. package/dist/annotation-menu.d.cts +0 -33
  203. package/dist/annotation-menu.d.ts +0 -33
  204. package/dist/annotation-menu.js +0 -48
  205. package/dist/annotation-menu.js.map +0 -1
  206. package/dist/annotation.cjs.map +0 -1
  207. package/dist/annotation.js.map +0 -1
  208. package/dist/form-BVumS7up.cjs +0 -926
  209. package/dist/form-BVumS7up.cjs.map +0 -1
  210. package/dist/form-Bdjk0Z3T.d.cts +0 -537
  211. package/dist/form-CK3mp99m.js +0 -895
  212. package/dist/form-CK3mp99m.js.map +0 -1
  213. package/dist/form-CYRYgthn.d.ts +0 -537
  214. package/dist/rolldown-runtime-BBjsoOtd.js +0 -27
  215. package/dist/runtime-DE76hlnl.d.ts +0 -216
  216. package/dist/runtime-umHA_xHt.d.cts +0 -216
  217. package/dist/runtime.cjs.map +0 -1
  218. package/dist/runtime.js.map +0 -1
  219. package/dist/scrollbar.cjs +0 -241
  220. package/dist/scrollbar.cjs.map +0 -1
  221. package/dist/scrollbar.d.cts +0 -42
  222. package/dist/scrollbar.d.ts +0 -42
  223. package/dist/scrollbar.js +0 -213
  224. package/dist/scrollbar.js.map +0 -1
  225. package/dist/stage.cjs.map +0 -1
  226. package/dist/stage.js.map +0 -1
  227. package/dist/views.cjs +0 -35
  228. package/dist/views.cjs.map +0 -1
  229. package/dist/views.d.cts +0 -21
  230. package/dist/views.d.ts +0 -21
  231. package/dist/views.js +0 -27
  232. package/dist/views.js.map +0 -1
package/dist/search.d.ts CHANGED
@@ -1,34 +1,47 @@
1
- import * as React from "react";
1
+ import { n as StateHook, t as SettingsHook } from "./state-0K1I5cM4.js";
2
+ import { o as SearchQuery } from "./AnnotationList-CkijRHet-HEFNUIZe.js";
3
+ import { EventHook, PageRef } from "@embedpdf/core";
4
+ import { SearchCapability, SearchHit } from "@embedpdf/plugin-search";
2
5
  export * from "@embedpdf/plugin-search";
3
6
  //#region src/search.d.ts
4
7
  interface SearchLayerProps {
5
- /** Highlight colour for hits — SOLID (default: highlighter yellow). */
6
- color?: string;
7
- /** Highlight colour for the ACTIVE hit — SOLID (default: orange). */
8
- activeColor?: string;
9
8
  /**
10
- * How the highlight composites with the page. `'multiply'` (default) is
11
- * the real-highlighter look: text stays crisp black through the colour,
12
- * only the paper tints. Pass `'normal'` (with translucent colours) for
13
- * dark/scanned documents where multiply-on-dark would vanish.
9
+ * Make matches clickable: called with the match someone clicks, for example
10
+ * to make it the active one with `search.goToHit(hit)`. The press still
11
+ * reaches the page, so a drag that starts on a match selects text, and only
12
+ * a click calls this. Without it, matches are only paint and the pointer
13
+ * goes straight through.
14
14
  */
15
- blendMode?: React.CSSProperties['mixBlendMode'];
15
+ onHitClick?: (hit: SearchHit) => void;
16
16
  }
17
- declare function SearchLayer({ color, activeColor, blendMode }: SearchLayerProps): import("react/jsx-runtime").JSX.Element | null;
18
- /** The search capability (search/clear/next/prev/…) for app chrome. */
19
- declare function useSearch(): import("@embedpdf/plugin-search").SearchCapability;
20
- /** Reactive search read-model for chrome: the query, status, counts, progress. */
21
- declare function useSearchState(): {
22
- query: import("@embedpdf/plugin-search").SearchQuery | null;
17
+ declare function SearchLayer({ onHitClick }: SearchLayerProps): import("react/jsx-runtime").JSX.Element | null;
18
+ /** The search capability (search / clear / nextHit / previousHit / …) for app chrome. */
19
+ declare function useSearch(): SearchCapability;
20
+ /** Subscribe to one search event for the mounted lifetime: `useSearchEvent((search) => search.onCompleted, handler)`. */
21
+ declare function useSearchEvent<T>(select: (search: SearchCapability) => EventHook<T>, handler: (event: T) => void): void;
22
+ /**
23
+ * The search's state: the query, status, count, active match, progress and
24
+ * error (the page's State table, declared once in `searchState`). Takes a
25
+ * selector, and re-renders only when what it returns changes.
26
+ */
27
+ declare const useSearchState: StateHook<{
28
+ query: SearchQuery | null;
23
29
  status: import("@embedpdf/plugin-search").SearchStatus;
24
30
  hitCount: number;
25
- activeIndex: number;
26
- progress: {
27
- scanned: number;
28
- total: number;
29
- };
30
- error: string | null;
31
- };
31
+ activeHitIndex: number;
32
+ activeHit: SearchHit | null;
33
+ progress: import("@embedpdf/plugin-search").SearchProgress;
34
+ error: import("@embedpdf/core").PluginErrorInfo | null;
35
+ }>;
36
+ /** The search settings (`reveal`, `highlight`), with or without a document. Takes a selector. */
37
+ declare const useSearchSettings: SettingsHook<import("@embedpdf/plugin-search").SearchSettings>;
38
+ /**
39
+ * Every match found so far, or one page's (its ref or index), for a results
40
+ * list or a count per thumbnail. The arrays are reference-stable, so a
41
+ * component re-renders only when matches land on what it shows. Empty without
42
+ * a document, and for a page that has just left the document.
43
+ */
44
+ declare function useSearchHits(page?: PageRef | number): readonly SearchHit[];
32
45
  //#endregion
33
- export { SearchLayer, SearchLayerProps, useSearch, useSearchState };
46
+ export { SearchLayer, SearchLayerProps, useSearch, useSearchEvent, useSearchHits, useSearchSettings, useSearchState };
34
47
  //# sourceMappingURL=search.d.ts.map
package/dist/search.js CHANGED
@@ -1,75 +1,103 @@
1
- import { shallowArray, useCapability, usePage, useSelector } from "./runtime.js";
1
+ import { f as useCapability, g as useDocumentScope, p as useCapabilityEvent, w as usePage, x as useKernelValue } from "./runtime-B6xcPsEb.js";
2
+ import { n as stateHook, t as settingsHook } from "./state-CbgxhwLA.js";
2
3
  import { jsx } from "react/jsx-runtime";
3
- import "react";
4
- import { SearchToken } from "@embedpdf/plugin-search";
4
+ import { createClickDetector, paint, searchHighlightsOf } from "@embedpdf/web";
5
+ import { useState } from "react";
6
+ import { SearchToken, searchState } from "@embedpdf/plugin-search";
5
7
  export * from "@embedpdf/plugin-search";
6
8
  //#region src/search.tsx
7
- function SearchLayer({ color = "#ffd500", activeColor = "#ff9632", blendMode = "multiply" }) {
9
+ function SearchLayer({ onHitClick }) {
8
10
  const page = usePage();
9
- const hits = useSelector(SearchToken, (c) => c.hitsForPage(page.pon), shallowArray);
10
- const active = useSelector(SearchToken, (c) => c.activeHit());
11
+ const hits = useSearchHits(page.ref);
12
+ const active = useSearchState((state) => state.activeHit);
13
+ const highlight = useSearchSettings((settings) => settings.highlight);
14
+ const [clicks] = useState(createClickDetector);
11
15
  if (hits.length === 0) return null;
16
+ const color = paint("search-highlight", highlight.color);
17
+ const activeColor = paint("search-highlight-active", highlight.activeColor);
18
+ const blendMode = paint("search-blend-mode", highlight.blendMode);
19
+ const clickable = onHitClick ? {
20
+ pointerEvents: "auto",
21
+ cursor: "pointer"
22
+ } : null;
23
+ const pointerHandlers = (hit) => onHitClick && {
24
+ onPointerDown: (event) => clicks.press(event),
25
+ onClick: (event) => {
26
+ if (clicks.isClick(event)) onHitClick(hit);
27
+ }
28
+ };
12
29
  return /* @__PURE__ */ jsx("div", {
13
30
  style: {
14
31
  position: "absolute",
15
32
  inset: 0,
16
33
  pointerEvents: "none"
17
34
  },
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.toPixels(q.upperStart);
22
- const br = page.transform.toPixels(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}`);
35
+ children: searchHighlightsOf(hits, {
36
+ active,
37
+ page: page.transform,
38
+ color,
39
+ activeColor
40
+ }).map((piece) => piece.box ? /* @__PURE__ */ jsx("div", {
41
+ ...pointerHandlers(piece.hit),
42
+ style: {
43
+ position: "absolute",
44
+ left: piece.box.left,
45
+ top: piece.box.top,
46
+ width: piece.box.width,
47
+ height: piece.box.height,
48
+ backgroundColor: piece.fill,
49
+ mixBlendMode: blendMode,
50
+ borderRadius: 2,
51
+ ...clickable
33
52
  }
34
- const ring = [
35
- q.upperStart,
36
- q.upperEnd,
37
- q.lowerEnd,
38
- q.lowerStart
39
- ].map((p) => page.transform.toPixels(p));
40
- return /* @__PURE__ */ jsx("svg", {
53
+ }, piece.key) : /* @__PURE__ */ jsx("svg", {
54
+ style: {
55
+ position: "absolute",
56
+ inset: 0,
57
+ width: "100%",
58
+ height: "100%",
59
+ overflow: "visible",
60
+ mixBlendMode: blendMode
61
+ },
62
+ children: /* @__PURE__ */ jsx("polygon", {
63
+ points: piece.points ?? void 0,
64
+ ...pointerHandlers(piece.hit),
41
65
  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}`);
54
- }))
66
+ fill: piece.fill,
67
+ ...clickable
68
+ }
69
+ })
70
+ }, piece.key))
55
71
  });
56
72
  }
57
- /** The search capability (search/clear/next/prev/…) for app chrome. */
73
+ /** The search capability (search / clear / nextHit / previousHit / …) for app chrome. */
58
74
  function useSearch() {
59
75
  return useCapability(SearchToken);
60
76
  }
61
- /** Reactive search read-model for chrome: the query, status, counts, progress. */
62
- function useSearchState() {
63
- return {
64
- query: useSelector(SearchToken, (c) => c.query()),
65
- status: useSelector(SearchToken, (c) => c.status()),
66
- hitCount: useSelector(SearchToken, (c) => c.hitCount()),
67
- activeIndex: useSelector(SearchToken, (c) => c.activeIndex()),
68
- progress: useSelector(SearchToken, (c) => c.progress(), (a, b) => a.scanned === b.scanned && a.total === b.total),
69
- error: useSelector(SearchToken, (c) => c.errorMessage())
70
- };
77
+ /** Subscribe to one search event for the mounted lifetime: `useSearchEvent((search) => search.onCompleted, handler)`. */
78
+ function useSearchEvent(select, handler) {
79
+ useCapabilityEvent(SearchToken, select, handler);
80
+ }
81
+ /**
82
+ * The search's state: the query, status, count, active match, progress and
83
+ * error (the page's State table, declared once in `searchState`). Takes a
84
+ * selector, and re-renders only when what it returns changes.
85
+ */
86
+ const useSearchState = stateHook(searchState);
87
+ /** The search settings (`reveal`, `highlight`), with or without a document. Takes a selector. */
88
+ const useSearchSettings = settingsHook(SearchToken);
89
+ const NO_HITS = Object.freeze([]);
90
+ /**
91
+ * Every match found so far, or one page's (its ref or index), for a results
92
+ * list or a count per thumbnail. The arrays are reference-stable, so a
93
+ * component re-renders only when matches land on what it shows. Empty without
94
+ * a document, and for a page that has just left the document.
95
+ */
96
+ function useSearchHits(page) {
97
+ const scoped = useDocumentScope();
98
+ return useKernelValue((kernel) => kernel.tryCapability(SearchToken, scoped ?? void 0)?.listHits(page === void 0 ? void 0 : { page }) ?? NO_HITS);
71
99
  }
72
100
  //#endregion
73
- export { SearchLayer, useSearch, useSearchState };
101
+ export { SearchLayer, useSearch, useSearchEvent, useSearchHits, useSearchSettings, useSearchState };
74
102
 
75
103
  //# sourceMappingURL=search.js.map
@@ -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.toPixels. 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.toPixels(q.upperStart);\n const br = page.transform.toPixels(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.toPixels(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,SAAS,EAAE,UAAU;IAC/C,MAAM,KAAK,KAAK,UAAU,SAAS,EAAE,QAAQ;IAC7C,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,SAAS,CAAC,CAC3B;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
+ {"version":3,"file":"search.js","names":[],"sources":["../src/search.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-search.\n *\n * <SearchLayer> is the SelectionLayer's twin: it reads the page's page-space\n * hit rects from the capability and paints them through PageContext.toPixels,\n * in the colors of the plugin's `highlight` setting, which the `--epdf-search-*`\n * CSS variables override. 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 { useState } from 'react';\nimport { SearchToken, searchState } from '@embedpdf/plugin-search';\nimport type { SearchCapability, SearchHit } from '@embedpdf/plugin-search';\nimport type { EventHook, PageRef } from '@embedpdf/core';\nimport { createClickDetector, paint, searchHighlightsOf } from '@embedpdf/web';\nimport {\n useCapability,\n useCapabilityEvent,\n useDocumentScope,\n useKernelValue,\n usePage,\n} from './runtime';\nimport { settingsHook, stateHook } from './state';\n\nexport interface SearchLayerProps {\n /**\n * Make matches clickable: called with the match someone clicks, for example\n * to make it the active one with `search.goToHit(hit)`. The press still\n * reaches the page, so a drag that starts on a match selects text, and only\n * a click calls this. Without it, matches are only paint and the pointer\n * goes straight through.\n */\n onHitClick?: (hit: SearchHit) => void;\n}\n\nexport function SearchLayer({ onHitClick }: SearchLayerProps) {\n const page = usePage();\n // Per-page hit arrays are reference-stable in the plugin, so this re-renders\n // only when matches land on this page.\n const hits = useSearchHits(page.ref);\n const active = useSearchState((state) => state.activeHit);\n const highlight = useSearchSettings((settings) => settings.highlight);\n // Tells a click on a match from a drag that starts there (and selects text).\n const [clicks] = useState(createClickDetector);\n\n if (hits.length === 0) return null;\n\n // Each color is its CSS variable first, then the setting: CSS wins.\n const color = paint('search-highlight', highlight.color);\n const activeColor = paint('search-highlight-active', highlight.activeColor);\n const blendMode = paint(\n 'search-blend-mode',\n highlight.blendMode,\n ) as React.CSSProperties['mixBlendMode'];\n\n // Only a clickable match takes the pointer; a plain highlight stays inert.\n // Nothing here stops the press, so it reaches the page as well.\n const clickable = onHitClick ? { pointerEvents: 'auto' as const, cursor: 'pointer' } : null;\n const pointerHandlers = (hit: SearchHit) =>\n onHitClick && {\n onPointerDown: (event: React.PointerEvent) => clicks.press(event),\n onClick: (event: React.MouseEvent) => {\n if (clicks.isClick(event)) onHitClick(hit);\n },\n };\n\n // Upright lines are a rounded box; turned lines draw their true quad.\n const pieces = searchHighlightsOf(hits, {\n active,\n page: page.transform,\n color,\n activeColor,\n });\n\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {pieces.map((piece) =>\n piece.box ? (\n <div\n key={piece.key}\n {...pointerHandlers(piece.hit)}\n style={{\n position: 'absolute',\n left: piece.box.left,\n top: piece.box.top,\n width: piece.box.width,\n height: piece.box.height,\n backgroundColor: piece.fill,\n mixBlendMode: blendMode,\n borderRadius: 2,\n ...clickable,\n }}\n />\n ) : (\n <svg\n key={piece.key}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n overflow: 'visible',\n mixBlendMode: blendMode,\n }}\n >\n {/* The fill goes in `style`: an SVG attribute doesn't read var(). */}\n <polygon\n points={piece.points ?? undefined}\n {...pointerHandlers(piece.hit)}\n style={{ fill: piece.fill, ...clickable }}\n />\n </svg>\n ),\n )}\n </div>\n );\n}\n\n/** The search capability (search / clear / nextHit / previousHit / …) for app chrome. */\nexport function useSearch(): SearchCapability {\n return useCapability(SearchToken);\n}\n\n/** Subscribe to one search event for the mounted lifetime: `useSearchEvent((search) => search.onCompleted, handler)`. */\nexport function useSearchEvent<T>(\n select: (search: SearchCapability) => EventHook<T>,\n handler: (event: T) => void,\n): void {\n useCapabilityEvent(SearchToken, select, handler);\n}\n\n/**\n * The search's state: the query, status, count, active match, progress and\n * error (the page's State table, declared once in `searchState`). Takes a\n * selector, and re-renders only when what it returns changes.\n */\nexport const useSearchState = stateHook(searchState);\n\n/** The search settings (`reveal`, `highlight`), with or without a document. Takes a selector. */\nexport const useSearchSettings = settingsHook(SearchToken);\n\nconst NO_HITS: readonly SearchHit[] = Object.freeze([]);\n\n/**\n * Every match found so far, or one page's (its ref or index), for a results\n * list or a count per thumbnail. The arrays are reference-stable, so a\n * component re-renders only when matches land on what it shows. Empty without\n * a document, and for a page that has just left the document.\n */\nexport function useSearchHits(page?: PageRef | number): readonly SearchHit[] {\n const scoped = useDocumentScope();\n // Resolved on every read, like the state hook: a document that closes reads as no hits.\n return useKernelValue(\n (kernel) =>\n kernel\n .tryCapability(SearchToken, scoped ?? undefined)\n ?.listHits(page === undefined ? undefined : { page }) ?? NO_HITS,\n );\n}\n"],"mappings":";;;;;;;;AAsCA,SAAgB,YAAY,EAAE,cAAgC;CAC5D,MAAM,OAAO,QAAQ;CAGrB,MAAM,OAAO,cAAc,KAAK,GAAG;CACnC,MAAM,SAAS,gBAAgB,UAAU,MAAM,SAAS;CACxD,MAAM,YAAY,mBAAmB,aAAa,SAAS,SAAS;CAEpE,MAAM,CAAC,UAAU,SAAS,mBAAmB;CAE7C,IAAI,KAAK,WAAW,GAAG,OAAO;CAG9B,MAAM,QAAQ,MAAM,oBAAoB,UAAU,KAAK;CACvD,MAAM,cAAc,MAAM,2BAA2B,UAAU,WAAW;CAC1E,MAAM,YAAY,MAChB,qBACA,UAAU,SACZ;CAIA,MAAM,YAAY,aAAa;EAAE,eAAe;EAAiB,QAAQ;CAAU,IAAI;CACvF,MAAM,mBAAmB,QACvB,cAAc;EACZ,gBAAgB,UAA8B,OAAO,MAAM,KAAK;EAChE,UAAU,UAA4B;GACpC,IAAI,OAAO,QAAQ,KAAK,GAAG,WAAW,GAAG;EAC3C;CACF;CAUF,OACE,oBAAC,OAAD;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG,eAAe;EAAO;EARvD,UAAA,mBAAmB,MAAM;GACtC;GACA,MAAM,KAAK;GACX;GACA;EACF,CAIU,CAAC,CAAC,KAAK,UACX,MAAM,MACJ,oBAAC,OAAD;GAEE,GAAI,gBAAgB,MAAM,GAAG;GAC7B,OAAO;IACL,UAAU;IACV,MAAM,MAAM,IAAI;IAChB,KAAK,MAAM,IAAI;IACf,OAAO,MAAM,IAAI;IACjB,QAAQ,MAAM,IAAI;IAClB,iBAAiB,MAAM;IACvB,cAAc;IACd,cAAc;IACd,GAAG;GACL;EACD,GAbM,MAAM,GAaZ,IAED,oBAAC,OAAD;GAEE,OAAO;IACL,UAAU;IACV,OAAO;IACP,OAAO;IACP,QAAQ;IACR,UAAU;IACV,cAAc;GAChB;GAGA,UAAA,oBAAC,WAAD;IACE,QAAQ,MAAM,UAAU,KAAA;IACxB,GAAI,gBAAgB,MAAM,GAAG;IAC7B,OAAO;KAAE,MAAM,MAAM;KAAM,GAAG;IAAU;GACzC,CAAA;EACE,GAhBE,MAAM,GAgBR,CAET;CACG,CAAA;AAET;;AAGA,SAAgB,YAA8B;CAC5C,OAAO,cAAc,WAAW;AAClC;;AAGA,SAAgB,eACd,QACA,SACM;CACN,mBAAmB,aAAa,QAAQ,OAAO;AACjD;;;;;;AAOA,MAAa,iBAAiB,UAAU,WAAW;;AAGnD,MAAa,oBAAoB,aAAa,WAAW;AAEzD,MAAM,UAAgC,OAAO,OAAO,CAAC,CAAC;;;;;;;AAQtD,SAAgB,cAAc,MAA+C;CAC3E,MAAM,SAAS,iBAAiB;CAEhC,OAAO,gBACJ,WACC,OACG,cAAc,aAAa,UAAU,KAAA,CAAS,CAAC,EAC9C,SAAS,SAAS,KAAA,IAAY,KAAA,IAAY,EAAE,KAAK,CAAC,KAAK,OAC/D;AACF"}
@@ -1,25 +1,32 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-DDXJien1.cjs");
3
- const require_runtime = require("./runtime.cjs");
3
+ const require_runtime = require("./runtime-fzpGv78v.cjs");
4
+ const require_state = require("./state-CsfZy_Eq.cjs");
4
5
  const require_anchored = require("./anchored.cjs");
6
+ const require_stage_scope = require("./stage-scope-CQgCuf1w.cjs");
5
7
  let react_jsx_runtime = require("react/jsx-runtime");
8
+ let _embedpdf_web = require("@embedpdf/web");
6
9
  let react = require("react");
7
10
  react = require_rolldown_runtime.__toESM(react, 1);
8
- let _embedpdf_web = require("@embedpdf/web");
9
- let _embedpdf_plugin_stage_contract = require("@embedpdf/plugin-stage/contract");
10
- let _embedpdf_core_geometry = require("@embedpdf/core-geometry");
11
11
  let _embedpdf_plugin_selection = require("@embedpdf/plugin-selection");
12
- let _embedpdf_plugin_selection_internal = require("@embedpdf/plugin-selection/internal");
12
+ let _embedpdf_plugin_selection_contract_host = require("@embedpdf/plugin-selection/contract/host");
13
13
  //#region src/selection.tsx
14
- function SelectionLayer({ color = "rgba(33, 150, 243, 0.35)" }) {
14
+ /**
15
+ * The highlight of the selected text on one page, in the selection plugin's
16
+ * `color` setting; the `--epdf-text-selection` CSS variable wins over it.
17
+ */
18
+ function SelectionLayer() {
15
19
  const page = require_runtime.usePage();
16
- const selection = require_runtime.useCapability(_embedpdf_plugin_selection_internal.SelectionToken);
17
- const segments = require_runtime.useSelector(_embedpdf_plugin_selection_internal.SelectionToken, (c) => c.segmentsForPage(page.pon), require_runtime.shallowArray);
18
- const visible = require_runtime.useSelector(_embedpdf_plugin_selection_internal.SelectionToken, (c) => c.highlightVisible());
20
+ const selection = require_runtime.useCapability(_embedpdf_plugin_selection_contract_host.SelectionToken);
21
+ const segments = require_runtime.useSelector(_embedpdf_plugin_selection_contract_host.SelectionToken, (selection) => selection.listSegments(page.ref), require_runtime.shallowArray);
22
+ const visible = require_runtime.useSelector(_embedpdf_plugin_selection_contract_host.SelectionToken, (selection) => selection.isHighlightVisible());
23
+ const color = useSelectionSettings((settings) => settings.color);
24
+ const accent = require_runtime.useViewerSettings((settings) => settings.accent);
19
25
  (0, react.useEffect)(() => {
20
- selection.ensurePage(page.pon);
21
- }, [selection, page.pon]);
26
+ selection.ensureLoaded(page.ref);
27
+ }, [selection, page.ref]);
22
28
  if (!visible) return null;
29
+ const fill = (0, _embedpdf_web.paint)("text-selection", color ?? (0, _embedpdf_web.mixAccent)("text-selection", accent));
23
30
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
24
31
  style: {
25
32
  position: "absolute",
@@ -29,47 +36,45 @@ function SelectionLayer({ color = "rgba(33, 150, 243, 0.35)" }) {
29
36
  overflow: "visible",
30
37
  pointerEvents: "none"
31
38
  },
32
- children: segments.map((s, i) => {
33
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polygon", {
34
- points: [
35
- s.quad.upperStart,
36
- s.quad.upperEnd,
37
- s.quad.lowerEnd,
38
- s.quad.lowerStart
39
- ].map((p) => page.transform.toPixels(p)).map((p) => `${p.x},${p.y}`).join(" "),
40
- fill: color
41
- }, i);
42
- })
39
+ children: segments.map((segment, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polygon", {
40
+ points: (0, _embedpdf_web.svgPoints)((0, _embedpdf_web.quadInPixels)(segment.quad, page.transform)),
41
+ style: { fill }
42
+ }, i))
43
43
  });
44
44
  }
45
- /** The PUBLIC selection capability (select(), readText(), canCopy(), …) for
45
+ /** The public selection capability (select(), readText(), canCopy(), …) for
46
46
  * app chrome — toolbars, context menus, automation. */
47
47
  function useSelection() {
48
48
  return require_runtime.useCapability(_embedpdf_plugin_selection.SelectionToken);
49
49
  }
50
- /** Structural equality for the selection's menu anchor — keeps the menu from
51
- * re-rendering on unrelated dispatches (the capability returns a fresh
52
- * object each call). */
53
- const sameMenuAnchor = (a, b) => {
54
- if (a === b) return true;
55
- if (!a || !b) return false;
56
- 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;
57
- };
50
+ /** Subscribe to one selection event for the mounted lifetime: `useSelectionEvent((selection) => selection.onCommitted, handler)`. */
51
+ function useSelectionEvent(select, handler) {
52
+ require_runtime.useCapabilityEvent(_embedpdf_plugin_selection.SelectionToken, select, handler);
53
+ }
54
+ /**
55
+ * The selection's state: whether anything is selected, whether the user is
56
+ * still selecting, the character range and the pages it's on (the page's State
57
+ * table, declared once in `selectionState`). Takes a selector, re-renders only
58
+ * when what it returns changes, and returns the empty state without a document.
59
+ */
60
+ const useSelectionState = require_state.stateHook(_embedpdf_plugin_selection.selectionState);
61
+ /** The selection settings (`dragThreshold`, `color`, `handles`), with or without a document. Takes a selector. */
62
+ const useSelectionSettings = require_state.settingsHook(_embedpdf_plugin_selection.SelectionToken);
58
63
  /**
59
- * Floats over the current TEXT selection (one anchor regardless of
64
+ * Floats over the current text selection (one anchor regardless of
60
65
  * cross-page selection; it rides the gesture's end page) — and only once the
61
- * selection SETTLES: hidden while `isSelecting()` (mid-drag), it appears at
66
+ * selection settles: hidden while a gesture is active (mid-drag), it appears at
62
67
  * pointer-up; programmatic selections show immediately (born settled). Works
63
68
  * under `<Stage>` (mount in the overlay slot) and `<PageView>` alike — the
64
69
  * surface provides the projection. Compose the contents from hooks:
65
70
  * `useSelection()` for copy/clear, `useAnnotation()` for
66
71
  * `markupFromSelection('highlight')`, `copySelection` for the clipboard.
67
72
  * For live-follow UI during the drag, compose `<Anchored>` with
68
- * `menuAnchor()` yourself — the primitive carries no policy.
73
+ * `getAnchor()` yourself — the primitive carries no policy.
69
74
  */
70
75
  function SelectionMenu({ children, gap = 8, placement = "top" }) {
71
- const selecting = require_runtime.useSelector(_embedpdf_plugin_selection.SelectionToken, (c) => c.isSelecting());
72
- const anchor = require_runtime.useSelector(_embedpdf_plugin_selection.SelectionToken, (c) => c.menuAnchor(), sameMenuAnchor);
76
+ const selecting = useSelectionState((state) => state.isSelecting);
77
+ const anchor = require_runtime.useSelector(_embedpdf_plugin_selection.SelectionToken, (selection) => selection.getAnchor(), _embedpdf_web.samePageBounds);
73
78
  if (selecting || !anchor) return null;
74
79
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_anchored.Anchored, {
75
80
  anchor,
@@ -78,60 +83,37 @@ function SelectionMenu({ children, gap = 8, placement = "top" }) {
78
83
  children
79
84
  });
80
85
  }
81
- const sameEndpoints = (a, b) => {
82
- if (a === b) return true;
83
- if (!a || !b) return false;
84
- return a.start.pon === b.start.pon && a.end.pon === b.end.pon && a.start.advance === b.start.advance && a.end.advance === b.end.advance && (0, _embedpdf_core_geometry.textQuadEquals)(a.start.glyphQuad, b.start.glyphQuad) && (0, _embedpdf_core_geometry.textQuadEquals)(a.end.glyphQuad, b.end.glyphQuad);
85
- };
86
- /** The plugin's structural view over this Stage: point-exact projection in,
87
- * page resolution out. (`pageRectToScreen` is the AABB projector — upright
88
- * overlays only — and would collapse exactly the orientation handles need.) */
89
- const handleView = (stage) => ({
90
- toOverlay: (pon, pt) => {
91
- const world = stage.pageToWorld(pon, pt);
92
- return world ? stage.toScreen(world) : null;
93
- },
94
- pageAt: (overlay) => stage.pageAt(overlay),
95
- pointOnPage: (pon, overlay) => stage.pointOnPage(pon, overlay)
96
- });
97
86
  /**
98
87
  * iOS-style draggable selection handles. Each is drawn the way the platform
99
- * draws them: a thin caret BAR that forms the selection's own start/end edge
88
+ * draws them: a thin caret bar that forms the selection's own start/end edge
100
89
  * — the boundary glyph's oriented edge, so rotated text and rotated pages
101
90
  * carry the handle with them — capped by a screen-constant circle, above the
102
91
  * first line at the start, below the last line at the end.
103
92
  *
104
- * On touch, where a caret drag isn't available, the handles ARE the way to
93
+ * On touch, where a caret drag isn't available, the handles are the way to
105
94
  * grow or shrink a selection: a long-press selects a word, then each handle
106
- * extends from the OPPOSITE endpoint, snapping to glyphs and crossing pages
107
- * exactly like a pointer drag — it rides the same `beginAt`/`extendTo`
95
+ * extends from the opposite endpoint, snapping to glyphs and crossing pages
96
+ * exactly like a pointer drag — it rides the same `beginGestureAt`/`extendTo`
108
97
  * gesture path, so highlights, menus, and commit signals all behave
109
98
  * identically. Mount in the `<Stage>` overlay slot next to `<SelectionMenu>`;
110
99
  * outside a Stage it renders nothing (a `PageView` has no camera to project
111
100
  * through). Pointer-isolated, so grabbing a handle never pans the stage.
101
+ * Painted in the plugin's `handles` setting, which the
102
+ * `--epdf-text-selection-handle` and `--epdf-text-selection-handle-shadow` CSS
103
+ * variables override.
112
104
  */
113
- function SelectionHandles({ color = "#2196f3", token = _embedpdf_plugin_stage_contract.StageToken }) {
114
- const host = require_runtime.useCapability(_embedpdf_plugin_selection_internal.SelectionToken);
105
+ function SelectionHandles({ token: explicitToken } = {}) {
106
+ const token = require_stage_scope.useStageToken(explicitToken);
107
+ const host = require_runtime.useCapability(_embedpdf_plugin_selection_contract_host.SelectionToken);
115
108
  const stage = require_runtime.useOptionalCapability(token);
116
- const selecting = require_runtime.useSelector(_embedpdf_plugin_selection.SelectionToken, (c) => c.isSelecting());
117
- const visible = require_runtime.useSelector(_embedpdf_plugin_selection_internal.SelectionToken, (c) => c.highlightVisible());
118
- const endpoints = require_runtime.useSelector(_embedpdf_plugin_selection_internal.SelectionToken, (c) => {
119
- const s = c.snapshot();
120
- if (!s.start || !s.end) return null;
121
- return {
122
- start: {
123
- pon: s.start.pon,
124
- glyphQuad: s.start.glyphQuad,
125
- advance: s.start.advance
126
- },
127
- end: {
128
- pon: s.end.pon,
129
- glyphQuad: s.end.glyphQuad,
130
- advance: s.end.advance
131
- }
132
- };
133
- }, sameEndpoints);
134
- require_runtime.useKernelValue(() => stage?.visiblePages() ?? null);
109
+ const surface = require_anchored.useOptionalProjectorBinding();
110
+ if (!surface || surface.projector.space !== "overlay") require_runtime.devWarn("selection-handles-outside-stage", "<SelectionHandles> renders nothing here: mount it in the <Stage> overlay slot (a <PageView> has no camera to project the handles through).");
111
+ const selecting = useSelectionState((state) => state.isSelecting);
112
+ const visible = require_runtime.useSelector(_embedpdf_plugin_selection_contract_host.SelectionToken, (selection) => selection.isHighlightVisible());
113
+ const handles = useSelectionSettings((settings) => settings.handles);
114
+ const accent = require_runtime.useViewerSettings((settings) => settings.accent);
115
+ const endpoints = require_runtime.useSelector(_embedpdf_plugin_selection_contract_host.SelectionToken, (selection) => (0, _embedpdf_plugin_selection.selectionHandleEndpointsOf)(selection.getSnapshot()), _embedpdf_web.sameSelectionEndpoints);
116
+ require_runtime.useKernelValue(() => stage?.listVisiblePages() ?? null);
135
117
  const [dragging, setDragging] = (0, react.useState)(null);
136
118
  const armSource = (0, react.useRef)(null);
137
119
  armSource.current = stage && endpoints ? {
@@ -140,28 +122,22 @@ function SelectionHandles({ color = "#2196f3", token = _embedpdf_plugin_stage_co
140
122
  } : null;
141
123
  const bindHandle = (0, react.useMemo)(() => {
142
124
  const detach = {};
143
- const make = (role) => (el) => {
125
+ const make = (role) => (element) => {
144
126
  detach[role]?.();
145
127
  delete detach[role];
146
- if (!el) return;
147
- detach[role] = (0, _embedpdf_web.attachSelectionHandle)(el, { arm: () => {
128
+ if (!element) return;
129
+ detach[role] = (0, _embedpdf_web.attachSelectionHandle)(element, { arm: () => {
148
130
  const src = armSource.current;
149
131
  if (!src) return null;
150
- const view = handleView(src.stage);
151
- const geom = (0, _embedpdf_plugin_selection.selectionHandleGeom)(view, src.endpoints[role], role);
152
- if (!geom) return null;
153
- const opposite = src.endpoints[role === "start" ? "end" : "start"];
154
- const drag = (0, _embedpdf_plugin_selection.createSelectionHandleDrag)(host, view, opposite, src.endpoints[role].pon);
132
+ const armed = (0, _embedpdf_plugin_selection.armSelectionHandle)(host, (0, _embedpdf_plugin_selection.selectionHandleViewOf)(src.stage), src.endpoints, role);
133
+ if (!armed) return null;
155
134
  setDragging(role);
156
135
  return {
157
- base: {
158
- x: (geom.bar.from.x + geom.bar.to.x) / 2,
159
- y: (geom.bar.from.y + geom.bar.to.y) / 2
160
- },
136
+ base: armed.base,
161
137
  session: {
162
- move: drag.move,
138
+ move: armed.drag.move,
163
139
  end: () => {
164
- drag.end();
140
+ armed.drag.end();
165
141
  setDragging(null);
166
142
  }
167
143
  }
@@ -175,22 +151,24 @@ function SelectionHandles({ color = "#2196f3", token = _embedpdf_plugin_stage_co
175
151
  }, [host]);
176
152
  if (!stage || !endpoints || !visible) return null;
177
153
  if (selecting && !dragging) return null;
178
- const view = handleView(stage);
154
+ const view = (0, _embedpdf_plugin_selection.selectionHandleViewOf)(stage);
155
+ const color = (0, _embedpdf_web.paint)("text-selection-handle", handles.color ?? accent);
156
+ const shadow = (0, _embedpdf_web.paint)("text-selection-handle-shadow", handles.shadow);
179
157
  const renderHandle = (role) => {
180
- const geom = (0, _embedpdf_plugin_selection.selectionHandleGeom)(view, endpoints[role], role);
181
- if (!geom) return null;
158
+ const geometry = (0, _embedpdf_plugin_selection.selectionHandleGeom)(view, endpoints[role], role);
159
+ if (!geometry) return null;
182
160
  const barTop = _embedpdf_plugin_selection.HANDLE_PAD + (role === "start" ? _embedpdf_plugin_selection.HANDLE_HEAD : 0);
183
161
  const pivotX = _embedpdf_plugin_selection.HANDLE_PAD + _embedpdf_plugin_selection.HANDLE_BAR / 2;
184
162
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
185
163
  ref: bindHandle[role],
186
164
  style: {
187
165
  position: "absolute",
188
- left: geom.bar.from.x - pivotX,
189
- top: geom.bar.from.y - barTop,
166
+ left: geometry.bar.from.x - pivotX,
167
+ top: geometry.bar.from.y - barTop,
190
168
  width: _embedpdf_plugin_selection.HANDLE_BAR + 2 * _embedpdf_plugin_selection.HANDLE_PAD,
191
- height: geom.length + _embedpdf_plugin_selection.HANDLE_HEAD + 2 * _embedpdf_plugin_selection.HANDLE_PAD,
192
- ...geom.upright ? null : {
193
- transform: `rotate(${geom.rotation}deg)`,
169
+ height: geometry.length + _embedpdf_plugin_selection.HANDLE_HEAD + 2 * _embedpdf_plugin_selection.HANDLE_PAD,
170
+ ...geometry.upright ? null : {
171
+ transform: `rotate(${geometry.rotation}deg)`,
194
172
  transformOrigin: `${pivotX}px ${barTop}px`
195
173
  },
196
174
  touchAction: "none",
@@ -202,33 +180,49 @@ function SelectionHandles({ color = "#2196f3", token = _embedpdf_plugin_stage_co
202
180
  left: _embedpdf_plugin_selection.HANDLE_PAD,
203
181
  top: barTop,
204
182
  width: _embedpdf_plugin_selection.HANDLE_BAR,
205
- height: geom.length,
183
+ height: geometry.length,
206
184
  background: color,
207
185
  borderRadius: _embedpdf_plugin_selection.HANDLE_BAR / 2
208
186
  } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
209
187
  position: "absolute",
210
188
  left: pivotX - _embedpdf_plugin_selection.HANDLE_HEAD / 2,
211
- top: role === "start" ? _embedpdf_plugin_selection.HANDLE_PAD : _embedpdf_plugin_selection.HANDLE_PAD + geom.length,
189
+ top: role === "start" ? _embedpdf_plugin_selection.HANDLE_PAD : _embedpdf_plugin_selection.HANDLE_PAD + geometry.length,
212
190
  width: _embedpdf_plugin_selection.HANDLE_HEAD,
213
191
  height: _embedpdf_plugin_selection.HANDLE_HEAD,
214
192
  borderRadius: "50%",
215
193
  background: color,
216
- boxShadow: "0 1px 4px rgba(0, 0, 0, 0.35)"
194
+ boxShadow: shadow
217
195
  } })]
218
196
  }, role);
219
197
  };
220
198
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [renderHandle("start"), renderHandle("end")] });
221
199
  }
222
200
  /**
223
- * Mount ONCE per viewer to wire clipboard copy: prefetches the selected text
201
+ * Mount once per viewer to wire clipboard copy: prefetches the selected text
224
202
  * when the selection settles, answers the native `copy` event synchronously,
225
203
  * and falls back to the async Clipboard API for ctrl/cmd+C when the page has
226
- * no DOM selection. Renders nothing. For a toolbar Copy button, call
227
- * `copySelection(useSelection())` from its click handler instead.
204
+ * no DOM selection. A selection the user may not copy isn't copied, and
205
+ * `onRefused` is called. Renders nothing, and wires nothing until a document
206
+ * is ready. For a toolbar Copy button, call `copySelection(useSelection())`
207
+ * from its click handler instead.
228
208
  */
229
- function SelectionClipboard({ prefetch } = {}) {
230
- const selection = require_runtime.useCapability(_embedpdf_plugin_selection.SelectionToken);
231
- (0, react.useEffect)(() => (0, _embedpdf_web.wireSelectionClipboard)(selection, prefetch === void 0 ? {} : { prefetch }), [selection, prefetch]);
209
+ function SelectionClipboard({ prefetch, onRefused, target } = {}) {
210
+ const selection = require_runtime.useOptionalCapability(_embedpdf_plugin_selection.SelectionToken);
211
+ const refused = (0, react.useRef)(onRefused);
212
+ refused.current = onRefused;
213
+ (0, react.useEffect)(() => {
214
+ const element = target ? target.current : void 0;
215
+ if (!selection || element === null) return;
216
+ return (0, _embedpdf_web.wireSelectionClipboard)(selection, {
217
+ ...prefetch === void 0 ? {} : { prefetch },
218
+ ...element ? { target: element } : {},
219
+ onRefused: () => refused.current?.()
220
+ });
221
+ }, [
222
+ selection,
223
+ prefetch,
224
+ target
225
+ ]);
232
226
  return null;
233
227
  }
234
228
  //#endregion
@@ -243,6 +237,9 @@ Object.defineProperty(exports, "copySelection", {
243
237
  }
244
238
  });
245
239
  exports.useSelection = useSelection;
240
+ exports.useSelectionEvent = useSelectionEvent;
241
+ exports.useSelectionSettings = useSelectionSettings;
242
+ exports.useSelectionState = useSelectionState;
246
243
  Object.keys(_embedpdf_plugin_selection).forEach(function(k) {
247
244
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
248
245
  enumerable: true,