@embedpdf/react 3.0.0-next.13 → 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 -783
  24. package/dist/annotation.d.cts +2 -2
  25. package/dist/annotation.d.ts +2 -2
  26. package/dist/annotation.js +2 -768
  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 +32 -25
  39. package/dist/form.d.ts +32 -25
  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 +213 -57
  54. package/dist/index.d.cts +32 -31
  55. package/dist/index.d.ts +32 -31
  56. package/dist/index.js +33 -29
  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 +56 -0
  74. package/dist/measurement.cjs.map +1 -0
  75. package/dist/measurement.d.cts +38 -0
  76. package/dist/measurement.d.ts +38 -0
  77. package/dist/measurement.js +42 -0
  78. package/dist/measurement.js.map +1 -0
  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 +36 -24
  126. package/dist/search.d.ts +36 -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 +41 -38
  190. package/dist/AnnotationListSnapshot-D3mFut1e-C_GMpK25.d.cts +0 -546
  191. package/dist/AnnotationListSnapshot-D3mFut1e-C_GMpK25.d.ts +0 -546
  192. package/dist/annotation-BCED_p9H.d.cts +0 -160
  193. package/dist/annotation-anchors-CetwM1g7.js +0 -15
  194. package/dist/annotation-anchors-CetwM1g7.js.map +0 -1
  195. package/dist/annotation-anchors-DuYRBJLl.cjs +0 -26
  196. package/dist/annotation-anchors-DuYRBJLl.cjs.map +0 -1
  197. package/dist/annotation-hooks-BB5ZhPpf.cjs +0 -16
  198. package/dist/annotation-hooks-BB5ZhPpf.cjs.map +0 -1
  199. package/dist/annotation-hooks-Df8EmzDO.js +0 -11
  200. package/dist/annotation-hooks-Df8EmzDO.js.map +0 -1
  201. package/dist/annotation-k38KDlIZ.d.ts +0 -160
  202. package/dist/annotation-menu.cjs +0 -64
  203. package/dist/annotation-menu.cjs.map +0 -1
  204. package/dist/annotation-menu.d.cts +0 -33
  205. package/dist/annotation-menu.d.ts +0 -33
  206. package/dist/annotation-menu.js +0 -48
  207. package/dist/annotation-menu.js.map +0 -1
  208. package/dist/annotation.cjs.map +0 -1
  209. package/dist/annotation.js.map +0 -1
  210. package/dist/form-BVumS7up.cjs +0 -926
  211. package/dist/form-BVumS7up.cjs.map +0 -1
  212. package/dist/form-CK3mp99m.js +0 -895
  213. package/dist/form-CK3mp99m.js.map +0 -1
  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
@@ -1,46 +1,37 @@
1
- import { SvgCursorOptions, vibrationFeedback, wkFeedback } from "@embedpdf/web";
1
+ import { n as StateHook, t as SettingsHook } from "./state-0K1I5cM4.js";
2
+ import { EventHook } from "@embedpdf/core";
3
+ import { SvgCursorOptions, ToolCursorImage, ToolCursorSpec, ToolCursorSpec as ToolCursorSpec$1, createClickCounter, svgCursor, vibrationFeedback, wkFeedback } from "@embedpdf/web";
4
+ import { InteractionCapability } from "@embedpdf/plugin-interaction";
2
5
  export * from "@embedpdf/plugin-interaction";
3
6
  //#region src/interaction.d.ts
7
+ declare function PagePointerSource(): import("react/jsx-runtime").JSX.Element;
8
+ /** The interaction capability: switch tools, add your own, set their cursors. */
9
+ declare function useInteraction(): InteractionCapability;
10
+ /** Subscribe to one interaction event for the mounted lifetime: `useInteractionEvent((interaction) => interaction.onGestureStarted, handler)`. */
11
+ declare function useInteractionEvent<T>(select: (interaction: InteractionCapability) => EventHook<T>, handler: (event: T) => void): void;
4
12
  /**
5
- * Robust multi-click counter. `pointerdown.detail` is 0/1 in several browsers, so
6
- * we count clicks ourselves from timing + proximity — the standard double/triple
7
- * detection. Input normalization belongs in the adapter; the hub/handlers stay pure.
13
+ * The tools' state: the active tool's id and every tool you can switch to
14
+ * (the page's State table, declared once in `interactionState`). Takes a
15
+ * selector, and re-renders only when what it returns changes. Without a
16
+ * document there is no active tool (`null`) and no tool.
8
17
  */
9
- declare function createClickCounter(maxGapMs?: number, maxDistPx?: number): (now: number, x: number, y: number) => number;
10
- declare function PagePointerSource(): import("react/jsx-runtime").JSX.Element;
11
- /** Read + switch the active tool (for a toolbar). */
12
- declare function useTool(): {
13
- activeToolId: string;
14
- activate: (id: import("@embedpdf/plugin-interaction").ToolId) => void;
15
- tools: import("@embedpdf/plugin-interaction").Tool[];
16
- };
17
- /** One cursor slot: an SVG image ({@link SvgCursorOptions}) or a plain CSS
18
- * cursor string ('crosshair', 'url(…) 4 4, copy'). */
19
- type ToolCursorImage = SvgCursorOptions | string;
20
- /** What {@link useToolCursor} installs: the tool to reskin + its keyword map.
21
- * Apps build `svg` from the SAME icon their toolbar renders, so the cursor
22
- * and the button can never drift apart. */
23
- interface ToolCursorSpec {
24
- toolId: string;
25
- /** keyword → cursor: restyle the keywords this tool can show — its declared
26
- * base ('crosshair', 'copy') and hover claims ('text' over text) — in the
27
- * tool's identity. Keywords the map omits render as-is: a markup tool's
28
- * 'default' base stays the bare arrow, a foreign 'move' claim drops the
29
- * icon. An SVG value defaults its keyword `fallback` to the keyword it
30
- * replaces. */
31
- cursors: Record<string, ToolCursorImage>;
32
- }
18
+ declare const useInteractionState: StateHook<{
19
+ activeToolId: import("@embedpdf/plugin-interaction").ToolId | null;
20
+ tools: readonly import("@embedpdf/plugin-interaction").Tool[];
21
+ }>;
22
+ /** The interaction settings (`defaultTool`, `tools`), with or without a document. Takes a selector. */
23
+ declare const useInteractionSettings: SettingsHook<import("@embedpdf/plugin-interaction").InteractionSettings>;
33
24
  /**
34
- * Give a tool IMAGE cursors — the armed-tool indicator. The cursor is the only
25
+ * Give a tool image cursors — the armed-tool indicator. The cursor is the only
35
26
  * zero-latency pointer-locked pixel the platform has, and the hub already
36
27
  * arbitrates it: unmapped hover claims beat it over annotations/text, page
37
28
  * gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill
38
29
  * controls) carries its own CSS cursor — so nothing chases the pointer in
39
30
  * DOM. `null` installs nothing. A re-render with new content rebuilds the
40
31
  * cursor (live recolor from tool defaults); unmount restores the tool's
41
- * declared cursors.
32
+ * declared cursors. Without a document it installs nothing until one is ready.
42
33
  */
43
- declare function useToolCursor(spec: ToolCursorSpec | null): void;
34
+ declare function useToolCursor(spec: ToolCursorSpec$1 | null): void;
44
35
  //#endregion
45
- export { PagePointerSource, ToolCursorImage, ToolCursorSpec, createClickCounter, useTool, useToolCursor, vibrationFeedback, wkFeedback };
36
+ export { PagePointerSource, type SvgCursorOptions, type ToolCursorImage, type ToolCursorSpec, createClickCounter, svgCursor, useInteraction, useInteractionEvent, useInteractionSettings, useInteractionState, useToolCursor, vibrationFeedback, wkFeedback };
46
37
  //# sourceMappingURL=interaction.d.ts.map
@@ -1,94 +1,21 @@
1
- import "./rolldown-runtime-BBjsoOtd.js";
2
- import { useCapability, usePage, useSelector } from "./runtime.js";
1
+ import { E as useSelector, S as useOptionalCapability, f as useCapability, p as useCapabilityEvent, w as usePage } from "./runtime-B6xcPsEb.js";
2
+ import { n as stateHook, t as settingsHook } from "./state-CbgxhwLA.js";
3
3
  import { jsx } from "react/jsx-runtime";
4
+ import { attachPagePointer, createClickCounter, svgCursor, toolCursorsOf, vibrationFeedback, wkFeedback } from "@embedpdf/web";
4
5
  import { useEffect, useRef } from "react";
5
- import { svgCursor, vibrationFeedback, wkFeedback } from "@embedpdf/web";
6
- import { InteractionToken } from "@embedpdf/plugin-interaction";
6
+ import { InteractionToken } from "@embedpdf/plugin-interaction/contract/host";
7
+ import { InteractionToken as InteractionToken$1, interactionState } from "@embedpdf/plugin-interaction";
7
8
  export * from "@embedpdf/plugin-interaction";
8
9
  //#region src/interaction.tsx
9
- const mods = (e) => ({
10
- shift: e.shiftKey,
11
- alt: e.altKey,
12
- ctrl: e.ctrlKey,
13
- meta: e.metaKey
14
- });
15
- /**
16
- * Robust multi-click counter. `pointerdown.detail` is 0/1 in several browsers, so
17
- * we count clicks ourselves from timing + proximity — the standard double/triple
18
- * detection. Input normalization belongs in the adapter; the hub/handlers stay pure.
19
- */
20
- function createClickCounter(maxGapMs = 400, maxDistPx = 6) {
21
- let last = 0;
22
- let lx = 0;
23
- let ly = 0;
24
- let count = 0;
25
- return (now, x, y) => {
26
- count = now - last <= maxGapMs && Math.hypot(x - lx, y - ly) <= maxDistPx ? count + 1 : 1;
27
- last = now;
28
- lx = x;
29
- ly = y;
30
- return count;
31
- };
32
- }
33
10
  function PagePointerSource() {
34
11
  const page = usePage();
35
12
  const interaction = useCapability(InteractionToken);
36
- const cursor = useSelector(InteractionToken, (c) => c.cursor());
13
+ const cursor = useSelector(InteractionToken, (interaction) => interaction.getCursor());
37
14
  const ref = useRef(null);
38
15
  useEffect(() => {
39
- const el = ref.current;
40
- if (!el) return;
41
- const clicks = createClickCounter();
42
- const sample = (phase, e, clickCount = 1) => {
43
- const r = el.getBoundingClientRect();
44
- return {
45
- phase,
46
- viewport: {
47
- x: e.clientX - r.left,
48
- y: e.clientY - r.top
49
- },
50
- page: {
51
- pon: page.pon,
52
- point: page.toContentPoint(e.clientX, e.clientY),
53
- scale: page.transform.viewScale,
54
- rotation: page.transform.rotation,
55
- zoom: page.transform.zoom
56
- },
57
- project: (pon) => pon === page.pon ? page.toContentPoint(e.clientX, e.clientY) : null,
58
- modifiers: mods(e),
59
- clickCount,
60
- pointerType: e.pointerType || "mouse"
61
- };
62
- };
63
- let dragging = false;
64
- const down = (e) => {
65
- if (e.button !== 0) return;
66
- dragging = true;
67
- interaction.dispatch(sample("down", e, clicks(Date.now(), e.clientX, e.clientY)));
68
- };
69
- const hover = (e) => {
70
- if (dragging) return;
71
- interaction.dispatch(sample("move", e));
72
- };
73
- const drag = (e) => {
74
- if (!dragging) return;
75
- interaction.dispatch(sample("move", e));
76
- };
77
- const up = (e) => {
78
- if (!dragging) return;
79
- dragging = false;
80
- interaction.dispatch(sample("up", e));
81
- };
82
- el.addEventListener("pointerdown", down);
83
- el.addEventListener("pointermove", hover);
84
- window.addEventListener("pointermove", drag);
85
- window.addEventListener("pointerup", up);
86
- return () => {
87
- el.removeEventListener("pointerdown", down);
88
- el.removeEventListener("pointermove", hover);
89
- window.removeEventListener("pointermove", drag);
90
- window.removeEventListener("pointerup", up);
91
- };
16
+ const element = ref.current;
17
+ if (!element) return;
18
+ return attachPagePointer(element, interaction, () => page);
92
19
  }, [interaction, page]);
93
20
  return /* @__PURE__ */ jsx("div", {
94
21
  ref,
@@ -100,42 +27,46 @@ function PagePointerSource() {
100
27
  }
101
28
  });
102
29
  }
103
- /** Read + switch the active tool (for a toolbar). */
104
- function useTool() {
105
- const interaction = useCapability(InteractionToken);
106
- return {
107
- activeToolId: useSelector(InteractionToken, (c) => c.activeToolId()),
108
- activate: interaction.activateTool,
109
- tools: interaction.tools()
110
- };
30
+ /** The interaction capability: switch tools, add your own, set their cursors. */
31
+ function useInteraction() {
32
+ return useCapability(InteractionToken$1);
111
33
  }
112
- const toCursor = (img, fallback) => typeof img === "string" ? img : svgCursor(img.fallback === void 0 && fallback ? {
113
- ...img,
114
- fallback
115
- } : img);
34
+ /** Subscribe to one interaction event for the mounted lifetime: `useInteractionEvent((interaction) => interaction.onGestureStarted, handler)`. */
35
+ function useInteractionEvent(select, handler) {
36
+ useCapabilityEvent(InteractionToken$1, select, handler);
37
+ }
38
+ /**
39
+ * The tools' state: the active tool's id and every tool you can switch to
40
+ * (the page's State table, declared once in `interactionState`). Takes a
41
+ * selector, and re-renders only when what it returns changes. Without a
42
+ * document there is no active tool (`null`) and no tool.
43
+ */
44
+ const useInteractionState = stateHook(interactionState);
45
+ /** The interaction settings (`defaultTool`, `tools`), with or without a document. Takes a selector. */
46
+ const useInteractionSettings = settingsHook(InteractionToken$1);
116
47
  /**
117
- * Give a tool IMAGE cursors — the armed-tool indicator. The cursor is the only
48
+ * Give a tool image cursors — the armed-tool indicator. The cursor is the only
118
49
  * zero-latency pointer-locked pixel the platform has, and the hub already
119
50
  * arbitrates it: unmapped hover claims beat it over annotations/text, page
120
51
  * gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill
121
52
  * controls) carries its own CSS cursor — so nothing chases the pointer in
122
53
  * DOM. `null` installs nothing. A re-render with new content rebuilds the
123
54
  * cursor (live recolor from tool defaults); unmount restores the tool's
124
- * declared cursors.
55
+ * declared cursors. Without a document it installs nothing until one is ready.
125
56
  */
126
57
  function useToolCursor(spec) {
127
- const interaction = useCapability(InteractionToken);
58
+ const interaction = useOptionalCapability(InteractionToken);
128
59
  const key = spec && JSON.stringify(spec);
129
60
  const ref = useRef(spec);
130
61
  ref.current = spec;
131
62
  useEffect(() => {
132
- const s = ref.current;
133
- if (!s) return;
134
- interaction.setToolCursor(s.toolId, Object.fromEntries(Object.entries(s.cursors).map(([k, v]) => [k, toCursor(v, k)])));
135
- return () => interaction.setToolCursor(s.toolId, null);
63
+ const cursorSpec = ref.current;
64
+ if (!cursorSpec || !interaction) return;
65
+ interaction.setToolCursor(cursorSpec.toolId, toolCursorsOf(cursorSpec.cursors));
66
+ return () => interaction.setToolCursor(cursorSpec.toolId, null);
136
67
  }, [interaction, key]);
137
68
  }
138
69
  //#endregion
139
- export { PagePointerSource, createClickCounter, useTool, useToolCursor, vibrationFeedback, wkFeedback };
70
+ export { PagePointerSource, createClickCounter, svgCursor, useInteraction, useInteractionEvent, useInteractionSettings, useInteractionState, useToolCursor, vibrationFeedback, wkFeedback };
140
71
 
141
72
  //# sourceMappingURL=interaction.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"interaction.js","names":[],"sources":["../src/interaction.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-interaction.\n *\n * <PagePointerSource> is the ONE pointer listener per page: it converts events to\n * page space via PageContext.toContentPoint and forwards normalized samples to the\n * hub. It binds only to the page context, so it works identically inside a\n * virtualized <Stage> page and a standalone <PageView>. Features never attach\n * their own pointer listeners — they register handlers with the hub.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-interaction';\n// The browser feedback providers live in @embedpdf/web (the plugin is\n// DOM-free); re-exported here so app code has one import for the feature.\nexport { vibrationFeedback, wkFeedback } from '@embedpdf/web';\nimport * as React from 'react';\nimport { useEffect, useRef } from 'react';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport type { Modifiers, PointerSample } from '@embedpdf/plugin-interaction';\nimport { svgCursor } from '@embedpdf/web';\nimport type { SvgCursorOptions } from '@embedpdf/web';\nimport { useCapability, usePage, useSelector } from './runtime';\n\nconst mods = (e: PointerEvent): Modifiers => ({\n shift: e.shiftKey,\n alt: e.altKey,\n ctrl: e.ctrlKey,\n meta: e.metaKey,\n});\n\n/**\n * Robust multi-click counter. `pointerdown.detail` is 0/1 in several browsers, so\n * we count clicks ourselves from timing + proximity — the standard double/triple\n * detection. Input normalization belongs in the adapter; the hub/handlers stay pure.\n */\nexport function createClickCounter(maxGapMs = 400, maxDistPx = 6) {\n let last = 0;\n let lx = 0;\n let ly = 0;\n let count = 0;\n return (now: number, x: number, y: number): number => {\n count = now - last <= maxGapMs && Math.hypot(x - lx, y - ly) <= maxDistPx ? count + 1 : 1;\n last = now;\n lx = x;\n ly = y;\n return count;\n };\n}\n\nexport function PagePointerSource() {\n const page = usePage();\n const interaction = useCapability(InteractionToken);\n const cursor = useSelector(InteractionToken, (c) => c.cursor());\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const clicks = createClickCounter();\n const sample = (\n phase: PointerSample['phase'],\n e: PointerEvent,\n clickCount = 1,\n ): PointerSample => {\n const r = el.getBoundingClientRect();\n return {\n phase,\n viewport: { x: e.clientX - r.left, y: e.clientY - r.top },\n // `scale`/`rotation`/`zoom` carry the same per-page environmental\n // context the Stage source resolves via `pageAt` — read off the page\n // transform so a standalone <PageView> drives handlers identically.\n page: {\n pon: page.pon,\n point: page.toContentPoint(e.clientX, e.clientY),\n scale: page.transform.viewScale,\n rotation: page.transform.rotation,\n zoom: page.transform.zoom,\n },\n // A per-page source can only project onto its OWN page — toContentPoint is\n // already unclamped (the drag listener lives on window), so a gesture\n // anchored here keeps tracking past the page bounds.\n project: (pon) => (pon === page.pon ? page.toContentPoint(e.clientX, e.clientY) : null),\n modifiers: mods(e),\n clickCount,\n pointerType: (e.pointerType || 'mouse') as PointerSample['pointerType'],\n };\n };\n let dragging = false;\n\n const down = (e: PointerEvent) => {\n if (e.button !== 0) return;\n dragging = true;\n interaction.dispatch(sample('down', e, clicks(Date.now(), e.clientX, e.clientY)));\n };\n // hover (no gesture): drive cursor feedback only — fires from the element\n const hover = (e: PointerEvent) => {\n if (dragging) return;\n interaction.dispatch(sample('move', e));\n };\n // active drag: track on window so it survives leaving the page bounds\n const drag = (e: PointerEvent) => {\n if (!dragging) return;\n interaction.dispatch(sample('move', e));\n };\n const up = (e: PointerEvent) => {\n if (!dragging) return;\n dragging = false;\n interaction.dispatch(sample('up', e));\n };\n\n el.addEventListener('pointerdown', down);\n el.addEventListener('pointermove', hover);\n window.addEventListener('pointermove', drag);\n window.addEventListener('pointerup', up);\n return () => {\n el.removeEventListener('pointerdown', down);\n el.removeEventListener('pointermove', hover);\n window.removeEventListener('pointermove', drag);\n window.removeEventListener('pointerup', up);\n };\n }, [interaction, page]);\n\n // Sits on top as the page's event surface; visual layers below use pointerEvents:none.\n return <div ref={ref} style={{ position: 'absolute', inset: 0, cursor, touchAction: 'none' }} />;\n}\n\n/** Read + switch the active tool (for a toolbar). */\nexport function useTool() {\n const interaction = useCapability(InteractionToken);\n const activeToolId = useSelector(InteractionToken, (c) => c.activeToolId());\n return {\n activeToolId,\n activate: interaction.activateTool,\n tools: interaction.tools(),\n };\n}\n\n/** One cursor slot: an SVG image ({@link SvgCursorOptions}) or a plain CSS\n * cursor string ('crosshair', 'url(…) 4 4, copy'). */\nexport type ToolCursorImage = SvgCursorOptions | string;\n\n/** What {@link useToolCursor} installs: the tool to reskin + its keyword map.\n * Apps build `svg` from the SAME icon their toolbar renders, so the cursor\n * and the button can never drift apart. */\nexport interface ToolCursorSpec {\n toolId: string;\n /** keyword → cursor: restyle the keywords this tool can show — its declared\n * base ('crosshair', 'copy') and hover claims ('text' over text) — in the\n * tool's identity. Keywords the map omits render as-is: a markup tool's\n * 'default' base stays the bare arrow, a foreign 'move' claim drops the\n * icon. An SVG value defaults its keyword `fallback` to the keyword it\n * replaces. */\n cursors: Record<string, ToolCursorImage>;\n}\n\nconst toCursor = (img: ToolCursorImage, fallback?: string): string =>\n typeof img === 'string'\n ? img\n : svgCursor(img.fallback === undefined && fallback ? { ...img, fallback } : img);\n\n/**\n * Give a tool IMAGE cursors — the armed-tool indicator. The cursor is the only\n * zero-latency pointer-locked pixel the platform has, and the hub already\n * arbitrates it: unmapped hover claims beat it over annotations/text, page\n * gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill\n * controls) carries its own CSS cursor — so nothing chases the pointer in\n * DOM. `null` installs nothing. A re-render with new content rebuilds the\n * cursor (live recolor from tool defaults); unmount restores the tool's\n * declared cursors.\n */\nexport function useToolCursor(spec: ToolCursorSpec | null): void {\n const interaction = useCapability(InteractionToken);\n // Key the effect by VALUE: specs are built inline in render, and a\n // fresh-but-identical object must not thrash the skin.\n const key = spec && JSON.stringify(spec);\n const ref = useRef(spec);\n ref.current = spec;\n useEffect(() => {\n const s = ref.current;\n if (!s) return;\n interaction.setToolCursor(\n s.toolId,\n Object.fromEntries(Object.entries(s.cursors).map(([k, v]) => [k, toCursor(v, k)])),\n );\n return () => interaction.setToolCursor(s.toolId, null);\n }, [interaction, key]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"interaction.js","names":["InteractionHostToken","InteractionToken"],"sources":["../src/interaction.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-interaction.\n *\n * <PagePointerSource> is the one pointer listener per page: it converts events to\n * page space via PageContext.toPagePoint and forwards normalized samples to the\n * hub. It binds only to the page context, so it works identically inside a\n * virtualized <Stage> page and a standalone <PageView>. Features never attach\n * their own pointer listeners: they register handlers with the hub, and a tool\n * of your own brings its pointer methods (`registerTool`).\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-interaction';\n// The browser helpers live in @embedpdf/web (the plugin is DOM-free); they are\n// re-exported here so app code has one import for the feature.\nexport { createClickCounter, svgCursor, vibrationFeedback, wkFeedback } from '@embedpdf/web';\nexport type { SvgCursorOptions, ToolCursorImage, ToolCursorSpec } from '@embedpdf/web';\nimport * as React from 'react';\nimport { useEffect, useRef } from 'react';\nimport { InteractionToken as InteractionHostToken } from '@embedpdf/plugin-interaction/contract/host';\nimport { InteractionToken, interactionState } from '@embedpdf/plugin-interaction';\nimport type { InteractionCapability } from '@embedpdf/plugin-interaction';\nimport type { EventHook } from '@embedpdf/core';\nimport { attachPagePointer, toolCursorsOf } from '@embedpdf/web';\nimport type { ToolCursorSpec } from '@embedpdf/web';\nimport {\n useCapability,\n useCapabilityEvent,\n useOptionalCapability,\n usePage,\n useSelector,\n} from './runtime';\nimport { settingsHook, stateHook } from './state';\n\nexport function PagePointerSource() {\n const page = usePage();\n // The pointer source is a host of the hub (it dispatches samples).\n const interaction = useCapability(InteractionHostToken);\n const cursor = useSelector(InteractionHostToken, (interaction) => interaction.getCursor());\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const element = ref.current;\n if (!element) return;\n // The listener is `@embedpdf/web`'s, shared with every framework.\n return attachPagePointer(element, interaction, () => page);\n }, [interaction, page]);\n\n // Sits on top as the page's event surface; visual layers below use pointerEvents:none.\n return <div ref={ref} style={{ position: 'absolute', inset: 0, cursor, touchAction: 'none' }} />;\n}\n\n/** The interaction capability: switch tools, add your own, set their cursors. */\nexport function useInteraction(): InteractionCapability {\n return useCapability(InteractionToken);\n}\n\n/** Subscribe to one interaction event for the mounted lifetime: `useInteractionEvent((interaction) => interaction.onGestureStarted, handler)`. */\nexport function useInteractionEvent<T>(\n select: (interaction: InteractionCapability) => EventHook<T>,\n handler: (event: T) => void,\n): void {\n useCapabilityEvent(InteractionToken, select, handler);\n}\n\n/**\n * The tools' state: the active tool's id and every tool you can switch to\n * (the page's State table, declared once in `interactionState`). Takes a\n * selector, and re-renders only when what it returns changes. Without a\n * document there is no active tool (`null`) and no tool.\n */\nexport const useInteractionState = stateHook(interactionState);\n\n/** The interaction settings (`defaultTool`, `tools`), with or without a document. Takes a selector. */\nexport const useInteractionSettings = settingsHook(InteractionToken);\n\n/**\n * Give a tool image cursors — the armed-tool indicator. The cursor is the only\n * zero-latency pointer-locked pixel the platform has, and the hub already\n * arbitrates it: unmapped hover claims beat it over annotations/text, page\n * gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill\n * controls) carries its own CSS cursor — so nothing chases the pointer in\n * DOM. `null` installs nothing. A re-render with new content rebuilds the\n * cursor (live recolor from tool defaults); unmount restores the tool's\n * declared cursors. Without a document it installs nothing until one is ready.\n */\nexport function useToolCursor(spec: ToolCursorSpec | null): void {\n const interaction = useOptionalCapability(InteractionHostToken);\n // Key the effect by value: specs are built inline in render, and a\n // fresh-but-identical object must not thrash the skin.\n const key = spec && JSON.stringify(spec);\n const ref = useRef(spec);\n ref.current = spec;\n useEffect(() => {\n const cursorSpec = ref.current;\n if (!cursorSpec || !interaction) return;\n interaction.setToolCursor(cursorSpec.toolId, toolCursorsOf(cursorSpec.cursors));\n return () => interaction.setToolCursor(cursorSpec.toolId, null);\n }, [interaction, key]);\n}\n"],"mappings":";;;;;;;;;AAkCA,SAAgB,oBAAoB;CAClC,MAAM,OAAO,QAAQ;CAErB,MAAM,cAAc,cAAcA,gBAAoB;CACtD,MAAM,SAAS,YAAYA,mBAAuB,gBAAgB,YAAY,UAAU,CAAC;CACzF,MAAM,MAAM,OAAuB,IAAI;CAEvC,gBAAgB;EACd,MAAM,UAAU,IAAI;EACpB,IAAI,CAAC,SAAS;EAEd,OAAO,kBAAkB,SAAS,mBAAmB,IAAI;CAC3D,GAAG,CAAC,aAAa,IAAI,CAAC;CAGtB,OAAO,oBAAC,OAAD;EAAU;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG;GAAQ,aAAa;EAAO;CAAI,CAAA;AACjG;;AAGA,SAAgB,iBAAwC;CACtD,OAAO,cAAcC,kBAAgB;AACvC;;AAGA,SAAgB,oBACd,QACA,SACM;CACN,mBAAmBA,oBAAkB,QAAQ,OAAO;AACtD;;;;;;;AAQA,MAAa,sBAAsB,UAAU,gBAAgB;;AAG7D,MAAa,yBAAyB,aAAaA,kBAAgB;;;;;;;;;;;AAYnE,SAAgB,cAAc,MAAmC;CAC/D,MAAM,cAAc,sBAAsBD,gBAAoB;CAG9D,MAAM,MAAM,QAAQ,KAAK,UAAU,IAAI;CACvC,MAAM,MAAM,OAAO,IAAI;CACvB,IAAI,UAAU;CACd,gBAAgB;EACd,MAAM,aAAa,IAAI;EACvB,IAAI,CAAC,cAAc,CAAC,aAAa;EACjC,YAAY,cAAc,WAAW,QAAQ,cAAc,WAAW,OAAO,CAAC;EAC9E,aAAa,YAAY,cAAc,WAAW,QAAQ,IAAI;CAChE,GAAG,CAAC,aAAa,GAAG,CAAC;AACvB"}
package/dist/link.cjs CHANGED
@@ -1,71 +1,56 @@
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_stage_scope = require("./stage-scope-CQgCuf1w.cjs");
4
5
  let react_jsx_runtime = require("react/jsx-runtime");
6
+ let _embedpdf_web = require("@embedpdf/web");
5
7
  let react = require("react");
6
8
  react = require_rolldown_runtime.__toESM(react, 1);
7
- let _embedpdf_web = require("@embedpdf/web");
8
- let _embedpdf_plugin_interaction_contract = require("@embedpdf/plugin-interaction/contract");
9
+ let _embedpdf_plugin_interaction_contract_host = require("@embedpdf/plugin-interaction/contract/host");
9
10
  let _embedpdf_plugin_link = require("@embedpdf/plugin-link");
10
11
  let _embedpdf_plugin_actions_contract = require("@embedpdf/plugin-actions/contract");
12
+ let _embedpdf_plugin_link_contract_host = require("@embedpdf/plugin-link/contract/host");
11
13
  //#region src/link.tsx
12
14
  /**
13
- * Resolve a target through the plugin and PERFORM the `uri` outcome — the ONE
14
- * place in the codebase that turns a link target into a browser tab. The
15
- * plugin owns resolution (goto → stage reveal, policy, analytics) and stays
16
- * DOM-free; opening is this framework layer's job. Every click path — the nav
17
- * anchors below, the selection menu's "Open link", the style editor's "Go to
18
- * link" — goes through here, so none of them can drop the uri outcome again.
15
+ * A link's anchor. A press inside it must not reach the Stage's native listener (it would start
16
+ * the active tool's gesture, or end an edit as a click outside), so it stops with a native
17
+ * listener on the anchor itself: React's own `onPointerDown` runs from the root, after the Stage
18
+ * has already seen the press. The same listener sends the link's "mouse down" action.
19
19
  */
20
- function openLinkTarget(link, target, context) {
21
- const activation = link.activate(target, context);
22
- if (activation.outcome === "uri") {
23
- const href = (0, _embedpdf_web.sanitizeExternalUri)(activation.uri);
24
- if (href && typeof window !== "undefined") window.open(href, "_blank", "noopener,noreferrer");
25
- }
26
- return activation;
27
- }
28
- /** Content rect → view px (the page wrapper's own space) — the same idiom as
29
- * the annotation and form layers: never re-derive `x * scale`. */
30
- function boxOf(item, page) {
31
- const tl = page.transform.toPixels({
32
- x: item.rect.x,
33
- y: item.rect.y
20
+ function LinkAnchor({ onPress, ...props }) {
21
+ const ref = (0, react.useRef)(null);
22
+ const press = (0, react.useRef)(onPress);
23
+ press.current = onPress;
24
+ (0, react.useEffect)(() => ref.current ? (0, _embedpdf_web.isolatePointerDown)(ref.current, () => press.current()) : void 0, []);
25
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
26
+ ref,
27
+ ...props
34
28
  });
35
- const br = page.transform.toPixels({
36
- x: item.rect.x + item.rect.width,
37
- y: item.rect.y + item.rect.height
38
- });
39
- return {
40
- left: tl.x,
41
- top: tl.y,
42
- width: br.x - tl.x,
43
- height: br.y - tl.y
44
- };
45
29
  }
46
- /** A human label for the hover tooltip. Apps with i18n render their own via
47
- * `renderLink`; this is the sensible default. */
48
- function labelOf(item) {
49
- switch (item.target.kind) {
50
- case "uri": return item.target.uri;
51
- case "goto": return "Go to destination";
52
- case "named": return item.target.name;
53
- default: return "Link";
54
- }
30
+ /**
31
+ * Hand the link plugin this binding's website opener (`openExternalUri`)
32
+ * while the component is mounted: an allowed address opens in a new tab,
33
+ * anything else is refused and the plugin reports it.
34
+ */
35
+ function useUriOpener() {
36
+ const host = require_runtime.useOptionalCapability(_embedpdf_plugin_link_contract_host.LinkToken);
37
+ (0, react.useEffect)(() => host?.registerUriOpener(_embedpdf_web.openExternalUri), [host]);
55
38
  }
56
39
  function LinkLayer({ renderLink } = {}) {
57
40
  const page = require_runtime.usePage();
58
41
  const link = require_runtime.useCapability(_embedpdf_plugin_link.LinkToken);
42
+ useUriOpener();
43
+ const stage = require_runtime.useOptionalCapability(require_stage_scope.useStageToken());
59
44
  const actions = require_runtime.useOptionalCapability(_embedpdf_plugin_actions_contract.ActionsToken);
60
45
  const linkPump = (0, react.useMemo)(() => actions ? (0, _embedpdf_plugin_actions_contract.createHoverPump)(actions.dispatch) : null, [actions]);
61
- const items = require_runtime.useSelector(_embedpdf_plugin_link.LinkToken, (c) => c.linksOn(page.pon), require_runtime.shallowArray);
62
- const engaged = require_runtime.useSelector(_embedpdf_plugin_link.LinkToken, (c) => c.engaged());
63
- const editEnabled = require_runtime.useOptionalSelector(_embedpdf_plugin_interaction_contract.InteractionToken, (c) => c.activeTool()?.enables.has("annotation-edit") ?? false, false);
46
+ const items = require_runtime.useSelector(_embedpdf_plugin_link.LinkToken, (link) => link.listLinks(page.ref), require_runtime.shallowArray);
47
+ const engaged = require_runtime.useSelector(_embedpdf_plugin_link_contract_host.LinkToken, (link) => link.isNavigationEngaged());
48
+ const editEnabled = require_runtime.useOptionalSelector(_embedpdf_plugin_interaction_contract_host.InteractionToken, (interaction) => interaction.activeToolEnables("annotation-edit"), false);
64
49
  (0, react.useEffect)(() => {
65
- link.ensurePage(page.pon);
66
- }, [link, page.pon]);
50
+ link.ensureLoaded(page.ref);
51
+ }, [link, page.ref]);
67
52
  if (!engaged || !items.length) return null;
68
- const visible = editEnabled ? items.filter((i) => !i.attached) : items;
53
+ const visible = (0, _embedpdf_web.navigableLinksOf)(items, editEnabled);
69
54
  if (!visible.length) return null;
70
55
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
71
56
  style: {
@@ -74,66 +59,33 @@ function LinkLayer({ renderLink } = {}) {
74
59
  pointerEvents: "none"
75
60
  },
76
61
  children: visible.map((item) => {
77
- const box = boxOf(item, page);
78
- const chained = (item.activate?.root?.next.length ?? 0) > 0;
79
- const href = item.target.kind === "uri" && !chained ? (0, _embedpdf_web.sanitizeExternalUri)(item.target.uri) : null;
80
- const context = {
81
- activate: item.activate,
82
- ref: item.ref,
83
- pon: page.pon
84
- };
85
- const linkSource = item.ref ? {
86
- kind: "link",
87
- annotation: item.ref,
88
- pon: page.pon
89
- } : null;
90
- const notify = (event) => {
91
- if (!actions || !item.ref || !linkSource) return;
92
- actions.dispatch({
93
- scope: "annotation",
94
- event,
95
- ref: item.ref,
96
- pon: page.pon,
97
- source: linkSource
98
- });
99
- };
100
- const native = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
62
+ const { box, href, label } = (0, _embedpdf_web.linkAnchorOf)(item, page.transform, (each) => link.getLabel(each));
63
+ const context = (0, _embedpdf_web.linkActivateContextOf)(item, page.ref, stage);
64
+ const native = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkAnchor, {
65
+ onPress: () => (0, _embedpdf_web.sendLinkEvent)(actions, item, page.ref, "mouseDown"),
101
66
  href: href ?? void 0,
102
67
  target: "_blank",
103
68
  rel: "noopener noreferrer",
104
69
  role: "link",
105
70
  tabIndex: 0,
106
- title: labelOf(item),
107
- "aria-label": labelOf(item),
108
- onClick: (e) => {
109
- if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
110
- if (href) return;
111
- e.preventDefault();
112
- openLinkTarget(link, item.target, context);
71
+ title: label,
72
+ "aria-label": label,
73
+ onClick: (event) => {
74
+ if (href && (0, _embedpdf_web.isModifiedClick)(event)) return;
75
+ event.preventDefault();
76
+ link.activate(item, context);
113
77
  },
114
- onKeyDown: (e) => {
115
- if (e.key === "Enter" || e.key === " ") {
116
- e.preventDefault();
117
- openLinkTarget(link, item.target, context);
78
+ onKeyDown: (event) => {
79
+ if (event.key === "Enter" || event.key === " ") {
80
+ event.preventDefault();
81
+ link.activate(item, context);
118
82
  }
119
83
  },
120
- onPointerEnter: () => {
121
- if (!linkPump || !item.ref || !item.hoverEvents) return;
122
- linkPump.hover({
123
- ref: item.ref,
124
- pon: page.pon,
125
- ...linkSource ? { source: linkSource } : {},
126
- events: item.hoverEvents
127
- });
128
- },
84
+ onPointerEnter: () => (0, _embedpdf_web.hoverLink)(linkPump, item, page.ref),
129
85
  onPointerLeave: () => linkPump?.hover(null),
130
- onPointerUp: () => notify("mouseUp"),
131
- onFocus: () => notify("focus"),
132
- onBlur: () => notify("blur"),
133
- onPointerDown: (e) => {
134
- e.stopPropagation();
135
- notify("mouseDown");
136
- },
86
+ onPointerUp: () => (0, _embedpdf_web.sendLinkEvent)(actions, item, page.ref, "mouseUp"),
87
+ onFocus: () => (0, _embedpdf_web.sendLinkEvent)(actions, item, page.ref, "focus"),
88
+ onBlur: () => (0, _embedpdf_web.sendLinkEvent)(actions, item, page.ref, "blur"),
137
89
  style: {
138
90
  position: "absolute",
139
91
  left: box.left,
@@ -144,21 +96,30 @@ function LinkLayer({ renderLink } = {}) {
144
96
  pointerEvents: "auto"
145
97
  }
146
98
  });
147
- const out = renderLink?.({
148
- item,
149
- nativeComponent: native
99
+ const drawn = renderLink?.({
100
+ link: item,
101
+ native
150
102
  }) ?? native;
151
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: out }, item.id);
103
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: drawn }, item.id);
152
104
  })
153
105
  });
154
106
  }
107
+ /**
108
+ * The link capability (`listLinks`, `activate`, …) for app code. While a
109
+ * component uses it, `activate()` opens a website in a new tab.
110
+ */
155
111
  function useLink() {
112
+ useUriOpener();
156
113
  return require_runtime.useCapability(_embedpdf_plugin_link.LinkToken);
157
114
  }
115
+ /** Subscribe to one link event for the mounted lifetime: `useLinkEvent((link) => link.onActivated, handler)`. */
116
+ function useLinkEvent(select, handler) {
117
+ require_runtime.useCapabilityEvent(_embedpdf_plugin_link.LinkToken, select, handler);
118
+ }
158
119
  //#endregion
159
120
  exports.LinkLayer = LinkLayer;
160
- exports.openLinkTarget = openLinkTarget;
161
121
  exports.useLink = useLink;
122
+ exports.useLinkEvent = useLinkEvent;
162
123
  Object.keys(_embedpdf_plugin_link).forEach(function(k) {
163
124
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
164
125
  enumerable: true,
package/dist/link.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"link.cjs","names":["usePage","useCapability","LinkToken","useOptionalCapability","ActionsToken","useSelector","shallowArray","useOptionalSelector","InteractionToken","React"],"sources":["../src/link.tsx"],"sourcesContent":["/**\n * The React view of @embedpdf/plugin-link — the navigation plane's paint.\n *\n * One absolutely-positioned <a> per clickable link area, real anchors on\n * purpose: URIs get an `href` (middle-click, copy-link, status-bar preview\n * and keyboard focus for free), internal destinations activate through the\n * plugin (stage reveal). The layer stands down entirely while the active\n * tool doesn't enable `link-nav` — the annotation plane owns links then\n * (select / move / resize / retarget through the selection editor).\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-link';\nimport * as React from 'react';\nimport { useEffect, useMemo } from 'react';\nimport { ActionsToken, createHoverPump } from '@embedpdf/plugin-actions/contract';\nimport type {\n ActionSource,\n PdfAnnotationEventKind,\n} from '@embedpdf/plugin-actions/contract';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport {\n LinkToken,\n type LinkActivateContext,\n type LinkActivation,\n type LinkCapability,\n type LinkNavItem,\n type PdfLinkTarget,\n} from '@embedpdf/plugin-link';\nimport { sanitizeExternalUri } from '@embedpdf/web';\n\nimport {\n shallowArray,\n useCapability,\n useOptionalCapability,\n useOptionalSelector,\n usePage,\n useSelector,\n} from './runtime';\nimport type { PageContextValue } from './runtime';\n\n/**\n * Resolve a target through the plugin and PERFORM the `uri` outcome — the ONE\n * place in the codebase that turns a link target into a browser tab. The\n * plugin owns resolution (goto → stage reveal, policy, analytics) and stays\n * DOM-free; opening is this framework layer's job. Every click path — the nav\n * anchors below, the selection menu's \"Open link\", the style editor's \"Go to\n * link\" — goes through here, so none of them can drop the uri outcome again.\n */\nexport function openLinkTarget(\n link: LinkCapability,\n target: PdfLinkTarget,\n context?: LinkActivateContext,\n): LinkActivation {\n const activation = link.activate(target, context);\n if (activation.outcome === 'uri') {\n const href = sanitizeExternalUri(activation.uri);\n if (href && typeof window !== 'undefined') window.open(href, '_blank', 'noopener,noreferrer');\n }\n // 'dispatched': the action engine took it — the actions UI adapter owns any\n // URI open (the no-double-open rule); this opener must do NOTHING.\n return activation;\n}\n\n/** Content rect → view px (the page wrapper's own space) — the same idiom as\n * the annotation and form layers: never re-derive `x * scale`. */\nfunction boxOf(item: LinkNavItem, page: PageContextValue) {\n const tl = page.transform.toPixels({ x: item.rect.x, y: item.rect.y });\n const br = page.transform.toPixels({\n x: item.rect.x + item.rect.width,\n y: item.rect.y + item.rect.height,\n });\n return { left: tl.x, top: tl.y, width: br.x - tl.x, height: br.y - tl.y };\n}\n\n/** A human label for the hover tooltip. Apps with i18n render their own via\n * `renderLink`; this is the sensible default. */\nfunction labelOf(item: LinkNavItem): string {\n switch (item.target.kind) {\n case 'uri':\n return item.target.uri;\n case 'goto':\n return 'Go to destination';\n case 'named':\n return item.target.name;\n default:\n return 'Link';\n }\n}\n\nexport interface LinkLayerProps {\n /** Wrap or replace a link's native anchor (badging, custom tooltips). */\n renderLink?: (args: {\n item: LinkNavItem;\n nativeComponent: React.ReactNode;\n }) => React.ReactNode | undefined;\n}\n\nexport function LinkLayer({ renderLink }: LinkLayerProps = {}) {\n const page = usePage();\n const link = useCapability(LinkToken);\n // The link plane's /AA event feed: links are behavior-inert to the\n // annotation plane's hover feed while navigable (their pixels are THESE\n // anchors), so E/X/D/U/Fo/Bl can only fire from here. One shared pump per\n // layer — crossing layers still orders Exit before Enter because both\n // sides submit synchronously in DOM event order.\n const actions = useOptionalCapability(ActionsToken);\n const linkPump = useMemo(() => (actions ? createHoverPump(actions.dispatch) : null), [actions]);\n const items = useSelector(LinkToken, (c) => c.linksOn(page.pon), shallowArray);\n const engaged = useSelector(LinkToken, (c) => c.engaged());\n // One owner per pixel: an ATTACHED link is a property of its parent — while\n // the active tool can edit annotations, the parent owns those pixels and\n // the anchor stands down (select/move/resize work; no tooltip, no swallowed\n // pointer). Standalone document links navigate under any link-nav tool.\n const editEnabled = useOptionalSelector(\n InteractionToken,\n (c) => c.activeTool()?.enables.has('annotation-edit') ?? false,\n false,\n );\n\n useEffect(() => {\n link.ensurePage(page.pon);\n }, [link, page.pon]);\n\n // An authoring tool is active → the annotation plane owns links (they're\n // plain editable rects there); no nav anchors, no swallowed pointer events.\n if (!engaged || !items.length) return null;\n const visible = editEnabled ? items.filter((i) => !i.attached) : items;\n if (!visible.length) return null;\n\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {visible.map((item) => {\n const box = boxOf(item, page);\n // Real href ONLY for a chain-free sanitized external URI; blocked\n // schemes, internal targets, and chain-bearing trees (/Next after the\n // URI) activate through the plugin instead — a native navigation\n // would perform the first action and silently drop the rest.\n const chained = (item.activate?.root?.next.length ?? 0) > 0;\n const href =\n item.target.kind === 'uri' && !chained ? sanitizeExternalUri(item.target.uri) : null;\n const context: LinkActivateContext = { activate: item.activate, ref: item.ref, pon: page.pon };\n const linkSource: ActionSource | null = item.ref\n ? { kind: 'link', annotation: item.ref, pon: page.pon }\n : null;\n const notify = (event: Exclude<PdfAnnotationEventKind, 'cursorEnter' | 'cursorExit'>) => {\n if (!actions || !item.ref || !linkSource) return;\n void actions.dispatch({\n scope: 'annotation',\n event,\n ref: item.ref,\n pon: page.pon,\n source: linkSource,\n });\n };\n const native = (\n <a\n href={href ?? undefined}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n role=\"link\"\n tabIndex={0}\n title={labelOf(item)}\n aria-label={labelOf(item)}\n onClick={(e) => {\n // Modified clicks and middle-clicks on a real href keep their\n // native browser behaviour (new tab / copy link).\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n // A plain left-click on a real href: let the anchor be an\n // anchor — native navigation, target=_blank, status bar. Only\n // non-href targets (goto / named / blocked schemes) route\n // through the plugin, whose uri outcome the opener PERFORMS\n // (the old code preventDefault-ed AND dropped the outcome, so\n // clicking a URL link did nothing at all).\n if (href) return;\n e.preventDefault();\n openLinkTarget(link, item.target, context);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openLinkTarget(link, item.target, context);\n }\n }}\n onPointerEnter={() => {\n if (!linkPump || !item.ref || !item.hoverEvents) return;\n linkPump.hover({\n ref: item.ref,\n pon: page.pon,\n ...(linkSource ? { source: linkSource } : {}),\n events: item.hoverEvents,\n });\n }}\n onPointerLeave={() => linkPump?.hover(null)}\n onPointerUp={() => notify('mouseUp')}\n onFocus={() => notify('focus')}\n onBlur={() => notify('blur')}\n // Keep the hub out of it: a down inside the anchor must not reach\n // the Stage's native listener (same isolation idiom as FreeText).\n onPointerDown={(e) => {\n e.stopPropagation();\n notify('mouseDown');\n }}\n style={{\n position: 'absolute',\n left: box.left,\n top: box.top,\n width: box.width,\n height: box.height,\n cursor: 'pointer',\n pointerEvents: 'auto',\n }}\n />\n );\n const out = renderLink?.({ item, nativeComponent: native }) ?? native;\n return <React.Fragment key={item.id}>{out}</React.Fragment>;\n })}\n </div>\n );\n}\n\nexport function useLink() {\n return useCapability(LinkToken);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"link.cjs","names":["useOptionalCapability","LinkHostToken","openExternalUri","usePage","useCapability","LinkToken","useStageToken","ActionsToken","useSelector","shallowArray","useOptionalSelector","InteractionToken","React"],"sources":["../src/link.tsx"],"sourcesContent":["/**\n * The React view of @embedpdf/plugin-link — the navigation plane's paint.\n *\n * One absolutely-positioned <a> per clickable link area, real anchors on\n * purpose: URIs get an `href` (middle-click, copy-link, status-bar preview\n * and keyboard focus for free). A click or a key follows the link through\n * the plugin's `activate()`, so every way of following one fires\n * `onActivated`; the plugin opens websites through the opener this binding\n * registers. The layer stands down entirely while the active tool doesn't\n * enable `link-nav` — the annotation plane owns links then (select / move /\n * resize / retarget through the selection editor).\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-link';\nimport * as React from 'react';\nimport { useEffect, useMemo, useRef } from 'react';\nimport { ActionsToken, createHoverPump } from '@embedpdf/plugin-actions/contract';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract/host';\nimport { LinkToken, type Link, type LinkCapability } from '@embedpdf/plugin-link';\n// The layer paints anchors only while a navigation tool is active, and opens\n// websites for the plugin: both host facts.\nimport { LinkToken as LinkHostToken } from '@embedpdf/plugin-link/contract/host';\nimport {\n hoverLink,\n isolatePointerDown,\n isModifiedClick,\n linkActivateContextOf,\n linkAnchorOf,\n navigableLinksOf,\n openExternalUri,\n sendLinkEvent,\n} from '@embedpdf/web';\nimport type { EventHook } from '@embedpdf/core';\n\nimport {\n shallowArray,\n useCapability,\n useCapabilityEvent,\n useOptionalCapability,\n useOptionalSelector,\n usePage,\n useSelector,\n} from './runtime';\nimport { useStageToken } from './stage-scope';\n\n/**\n * A link's anchor. A press inside it must not reach the Stage's native listener (it would start\n * the active tool's gesture, or end an edit as a click outside), so it stops with a native\n * listener on the anchor itself: React's own `onPointerDown` runs from the root, after the Stage\n * has already seen the press. The same listener sends the link's \"mouse down\" action.\n */\nfunction LinkAnchor({\n onPress,\n ...props\n}: { onPress: () => void } & React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n const ref = useRef<HTMLAnchorElement>(null);\n const press = useRef(onPress);\n press.current = onPress;\n useEffect(\n () => (ref.current ? isolatePointerDown(ref.current, () => press.current()) : undefined),\n [],\n );\n return <a ref={ref} {...props} />;\n}\n\n/**\n * Hand the link plugin this binding's website opener (`openExternalUri`)\n * while the component is mounted: an allowed address opens in a new tab,\n * anything else is refused and the plugin reports it.\n */\nfunction useUriOpener(): void {\n const host = useOptionalCapability(LinkHostToken);\n useEffect(() => host?.registerUriOpener(openExternalUri), [host]);\n}\n\n/** What `renderLink` receives for each link. */\nexport interface LinkRenderProps {\n /** The link: its `id`, its `bounds` on the page and its `target`. */\n link: Link;\n /** The layer's own clickable area: wrap it to keep what a click does. */\n native: React.ReactNode;\n}\n\nexport interface LinkLayerProps {\n /**\n * Draw a link yourself, usually around `native`. Return `undefined` for a\n * link you don't want to change.\n */\n renderLink?: (props: LinkRenderProps) => React.ReactNode | undefined;\n}\n\nexport function LinkLayer({ renderLink }: LinkLayerProps = {}) {\n const page = usePage();\n const link = useCapability(LinkToken);\n useUriOpener();\n // A page destination moves the view this page is shown in.\n const stage = useOptionalCapability(useStageToken());\n // The link plane's /AA event feed: links are behavior-inert to the\n // annotation plane's hover feed while navigable (their pixels are these\n // anchors), so E/X/D/U/Fo/Bl can only fire from here. One shared pump per\n // layer — crossing layers still orders Exit before Enter because both\n // sides submit synchronously in DOM event order.\n const actions = useOptionalCapability(ActionsToken);\n const linkPump = useMemo(() => (actions ? createHoverPump(actions.dispatch) : null), [actions]);\n const items = useSelector(LinkToken, (link) => link.listLinks(page.ref), shallowArray);\n const engaged = useSelector(LinkHostToken, (link) => link.isNavigationEngaged());\n // One owner per pixel: an attached link is a property of its parent — while\n // the active tool can edit annotations, the parent owns those pixels and\n // the anchor stands down (select/move/resize work; no tooltip, no swallowed\n // pointer). Standalone document links navigate under any link-nav tool.\n const editEnabled = useOptionalSelector(\n InteractionToken,\n (interaction) => interaction.activeToolEnables('annotation-edit'),\n false,\n );\n\n useEffect(() => {\n void link.ensureLoaded(page.ref);\n }, [link, page.ref]);\n\n // An authoring tool is active → the annotation plane owns links (they're\n // plain editable rects there); no nav anchors, no swallowed pointer events.\n if (!engaged || !items.length) return null;\n const visible = navigableLinksOf(items, editEnabled);\n if (!visible.length) return null;\n\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {visible.map((item) => {\n // Its box, its label, and a native href only for a website with nothing chained after it.\n const { box, href, label } = linkAnchorOf(item, page.transform, (each) =>\n link.getLabel(each),\n );\n const context = linkActivateContextOf(item, page.ref, stage);\n const native = (\n <LinkAnchor\n onPress={() => sendLinkEvent(actions, item, page.ref, 'mouseDown')}\n href={href ?? undefined}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n role=\"link\"\n tabIndex={0}\n title={label}\n aria-label={label}\n onClick={(event) => {\n // A modified click on a real href keeps the browser's own\n // behaviour (a background tab, a new window).\n if (href && isModifiedClick(event)) return;\n // Every other click follows the link through the plugin, which\n // opens a website through this binding's opener.\n event.preventDefault();\n link.activate(item, context);\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n link.activate(item, context);\n }\n }}\n onPointerEnter={() => hoverLink(linkPump, item, page.ref)}\n onPointerLeave={() => linkPump?.hover(null)}\n onPointerUp={() => sendLinkEvent(actions, item, page.ref, 'mouseUp')}\n onFocus={() => sendLinkEvent(actions, item, page.ref, 'focus')}\n onBlur={() => sendLinkEvent(actions, item, page.ref, 'blur')}\n style={{\n position: 'absolute',\n left: box.left,\n top: box.top,\n width: box.width,\n height: box.height,\n cursor: 'pointer',\n pointerEvents: 'auto',\n }}\n />\n );\n const drawn = renderLink?.({ link: item, native }) ?? native;\n return <React.Fragment key={item.id}>{drawn}</React.Fragment>;\n })}\n </div>\n );\n}\n\n/**\n * The link capability (`listLinks`, `activate`, …) for app code. While a\n * component uses it, `activate()` opens a website in a new tab.\n */\nexport function useLink(): LinkCapability {\n useUriOpener();\n return useCapability(LinkToken);\n}\n\n/** Subscribe to one link event for the mounted lifetime: `useLinkEvent((link) => link.onActivated, handler)`. */\nexport function useLinkEvent<T>(\n select: (link: LinkCapability) => EventHook<T>,\n handler: (event: T) => void,\n): void {\n useCapabilityEvent(LinkToken, select, handler);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoDA,SAAS,WAAW,EAClB,SACA,GAAG,SACuE;CAC1E,MAAM,OAAA,GAAA,MAAA,OAAA,CAAgC,IAAI;CAC1C,MAAM,SAAA,GAAA,MAAA,OAAA,CAAe,OAAO;CAC5B,MAAM,UAAU;CAChB,CAAA,GAAA,MAAA,UAAA,OACS,IAAI,WAAA,GAAA,cAAA,mBAAA,CAA6B,IAAI,eAAe,MAAM,QAAQ,CAAC,IAAI,KAAA,GAC9E,CAAC,CACH;CACA,OAAO,iBAAA,GAAA,kBAAA,IAAA,CAAC,KAAD;EAAQ;EAAK,GAAI;CAAQ,CAAA;AAClC;;;;;;AAOA,SAAS,eAAqB;CAC5B,MAAM,OAAOA,gBAAAA,sBAAsBC,oCAAAA,SAAa;CAChD,CAAA,GAAA,MAAA,UAAA,OAAgB,MAAM,kBAAkBC,cAAAA,eAAe,GAAG,CAAC,IAAI,CAAC;AAClE;AAkBA,SAAgB,UAAU,EAAE,eAA+B,CAAC,GAAG;CAC7D,MAAM,OAAOC,gBAAAA,QAAQ;CACrB,MAAM,OAAOC,gBAAAA,cAAcC,sBAAAA,SAAS;CACpC,aAAa;CAEb,MAAM,QAAQL,gBAAAA,sBAAsBM,oBAAAA,cAAc,CAAC;CAMnD,MAAM,UAAUN,gBAAAA,sBAAsBO,kCAAAA,YAAY;CAClD,MAAM,YAAA,GAAA,MAAA,QAAA,OAA0B,WAAA,GAAA,kCAAA,gBAAA,CAA0B,QAAQ,QAAQ,IAAI,MAAO,CAAC,OAAO,CAAC;CAC9F,MAAM,QAAQC,gBAAAA,YAAYH,sBAAAA,YAAY,SAAS,KAAK,UAAU,KAAK,GAAG,GAAGI,gBAAAA,YAAY;CACrF,MAAM,UAAUD,gBAAAA,YAAYP,oCAAAA,YAAgB,SAAS,KAAK,oBAAoB,CAAC;CAK/E,MAAM,cAAcS,gBAAAA,oBAClBC,2CAAAA,mBACC,gBAAgB,YAAY,kBAAkB,iBAAiB,GAChE,KACF;CAEA,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,KAAU,aAAa,KAAK,GAAG;CACjC,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC;CAInB,IAAI,CAAC,WAAW,CAAC,MAAM,QAAQ,OAAO;CACtC,MAAM,WAAA,GAAA,cAAA,iBAAA,CAA2B,OAAO,WAAW;CACnD,IAAI,CAAC,QAAQ,QAAQ,OAAO;CAE5B,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG,eAAe;EAAO;EACjE,UAAA,QAAQ,KAAK,SAAS;GAErB,MAAM,EAAE,KAAK,MAAM,WAAA,GAAA,cAAA,aAAA,CAAuB,MAAM,KAAK,YAAY,SAC/D,KAAK,SAAS,IAAI,CACpB;GACA,MAAM,WAAA,GAAA,cAAA,sBAAA,CAAgC,MAAM,KAAK,KAAK,KAAK;GAC3D,MAAM,SACJ,iBAAA,GAAA,kBAAA,IAAA,CAAC,YAAD;IACE,gBAAA,GAAA,cAAA,cAAA,CAA6B,SAAS,MAAM,KAAK,KAAK,WAAW;IACjE,MAAM,QAAQ,KAAA;IACd,QAAO;IACP,KAAI;IACJ,MAAK;IACL,UAAU;IACV,OAAO;IACP,cAAY;IACZ,UAAU,UAAU;KAGlB,IAAI,SAAA,GAAA,cAAA,gBAAA,CAAwB,KAAK,GAAG;KAGpC,MAAM,eAAe;KACrB,KAAK,SAAS,MAAM,OAAO;IAC7B;IACA,YAAY,UAAU;KACpB,IAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;MAC9C,MAAM,eAAe;MACrB,KAAK,SAAS,MAAM,OAAO;KAC7B;IACF;IACA,uBAAA,GAAA,cAAA,UAAA,CAAgC,UAAU,MAAM,KAAK,GAAG;IACxD,sBAAsB,UAAU,MAAM,IAAI;IAC1C,oBAAA,GAAA,cAAA,cAAA,CAAiC,SAAS,MAAM,KAAK,KAAK,SAAS;IACnE,gBAAA,GAAA,cAAA,cAAA,CAA6B,SAAS,MAAM,KAAK,KAAK,OAAO;IAC7D,eAAA,GAAA,cAAA,cAAA,CAA4B,SAAS,MAAM,KAAK,KAAK,MAAM;IAC3D,OAAO;KACL,UAAU;KACV,MAAM,IAAI;KACV,KAAK,IAAI;KACT,OAAO,IAAI;KACX,QAAQ,IAAI;KACZ,QAAQ;KACR,eAAe;IACjB;GACD,CAAA;GAEH,MAAM,QAAQ,aAAa;IAAE,MAAM;IAAM;GAAO,CAAC,KAAK;GACtD,OAAO,iBAAA,GAAA,kBAAA,IAAA,CAACC,MAAM,UAAP,EAAA,UAA+B,MAAsB,GAAhC,KAAK,EAA2B;EAC9D,CAAC;CACE,CAAA;AAET;;;;;AAMA,SAAgB,UAA0B;CACxC,aAAa;CACb,OAAOR,gBAAAA,cAAcC,sBAAAA,SAAS;AAChC;;AAGA,SAAgB,aACd,QACA,SACM;CACN,gBAAA,mBAAmBA,sBAAAA,WAAW,QAAQ,OAAO;AAC/C"}
package/dist/link.d.cts CHANGED
@@ -1,25 +1,30 @@
1
+ import { EventHook } from "@embedpdf/core";
1
2
  import * as React from "react";
2
- import { LinkActivateContext, LinkActivation, LinkCapability, LinkNavItem, PdfLinkTarget } from "@embedpdf/plugin-link";
3
+ import { Link, LinkCapability } from "@embedpdf/plugin-link";
3
4
  export * from "@embedpdf/plugin-link";
4
5
  //#region src/link.d.ts
5
- /**
6
- * Resolve a target through the plugin and PERFORM the `uri` outcome — the ONE
7
- * place in the codebase that turns a link target into a browser tab. The
8
- * plugin owns resolution (goto → stage reveal, policy, analytics) and stays
9
- * DOM-free; opening is this framework layer's job. Every click path — the nav
10
- * anchors below, the selection menu's "Open link", the style editor's "Go to
11
- * link" — goes through here, so none of them can drop the uri outcome again.
12
- */
13
- declare function openLinkTarget(link: LinkCapability, target: PdfLinkTarget, context?: LinkActivateContext): LinkActivation;
6
+ /** What `renderLink` receives for each link. */
7
+ interface LinkRenderProps {
8
+ /** The link: its `id`, its `bounds` on the page and its `target`. */
9
+ link: Link;
10
+ /** The layer's own clickable area: wrap it to keep what a click does. */
11
+ native: React.ReactNode;
12
+ }
14
13
  interface LinkLayerProps {
15
- /** Wrap or replace a link's native anchor (badging, custom tooltips). */
16
- renderLink?: (args: {
17
- item: LinkNavItem;
18
- nativeComponent: React.ReactNode;
19
- }) => React.ReactNode | undefined;
14
+ /**
15
+ * Draw a link yourself, usually around `native`. Return `undefined` for a
16
+ * link you don't want to change.
17
+ */
18
+ renderLink?: (props: LinkRenderProps) => React.ReactNode | undefined;
20
19
  }
21
20
  declare function LinkLayer({ renderLink }?: LinkLayerProps): import("react/jsx-runtime").JSX.Element | null;
21
+ /**
22
+ * The link capability (`listLinks`, `activate`, …) for app code. While a
23
+ * component uses it, `activate()` opens a website in a new tab.
24
+ */
22
25
  declare function useLink(): LinkCapability;
26
+ /** Subscribe to one link event for the mounted lifetime: `useLinkEvent((link) => link.onActivated, handler)`. */
27
+ declare function useLinkEvent<T>(select: (link: LinkCapability) => EventHook<T>, handler: (event: T) => void): void;
23
28
  //#endregion
24
- export { LinkLayer, LinkLayerProps, openLinkTarget, useLink };
29
+ export { LinkLayer, LinkLayerProps, LinkRenderProps, useLink, useLinkEvent };
25
30
  //# sourceMappingURL=link.d.cts.map