@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/link.d.ts CHANGED
@@ -1,25 +1,30 @@
1
- import * as React from "react";
2
- import { LinkActivateContext, LinkActivation, LinkCapability, LinkNavItem, PdfLinkTarget } from "@embedpdf/plugin-link";
1
+ import { EventHook } from "@embedpdf/core";
2
+ import * as React$1 from "react";
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$1.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$1.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.ts.map
package/dist/link.js CHANGED
@@ -1,71 +1,55 @@
1
- import "./rolldown-runtime-BBjsoOtd.js";
2
- import { shallowArray, useCapability, useOptionalCapability, useOptionalSelector, usePage, useSelector } from "./runtime.js";
1
+ import { C as useOptionalSelector, E as useSelector, S as useOptionalCapability, f as useCapability, p as useCapabilityEvent, u as shallowArray, w as usePage } from "./runtime-B6xcPsEb.js";
2
+ import { n as useStageToken } from "./stage-scope-D7ZeNduT.js";
3
3
  import { jsx } from "react/jsx-runtime";
4
- import * as React from "react";
5
- import { useEffect, useMemo } from "react";
6
- import { sanitizeExternalUri } from "@embedpdf/web";
7
- import { InteractionToken } from "@embedpdf/plugin-interaction/contract";
4
+ import { hoverLink, isModifiedClick, isolatePointerDown, linkActivateContextOf, linkAnchorOf, navigableLinksOf, openExternalUri, sendLinkEvent } from "@embedpdf/web";
5
+ import * as React$1 from "react";
6
+ import { useEffect, useMemo, useRef } from "react";
7
+ import { InteractionToken } from "@embedpdf/plugin-interaction/contract/host";
8
8
  import { LinkToken } from "@embedpdf/plugin-link";
9
9
  import { ActionsToken, createHoverPump } from "@embedpdf/plugin-actions/contract";
10
+ import { LinkToken as LinkToken$1 } from "@embedpdf/plugin-link/contract/host";
10
11
  export * from "@embedpdf/plugin-link";
11
12
  //#region src/link.tsx
12
13
  /**
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.
14
+ * A link's anchor. A press inside it must not reach the Stage's native listener (it would start
15
+ * the active tool's gesture, or end an edit as a click outside), so it stops with a native
16
+ * listener on the anchor itself: React's own `onPointerDown` runs from the root, after the Stage
17
+ * has already seen the press. The same listener sends the link's "mouse down" action.
19
18
  */
20
- function openLinkTarget(link, target, context) {
21
- const activation = link.activate(target, context);
22
- if (activation.outcome === "uri") {
23
- const href = 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
34
- });
35
- const br = page.transform.toPixels({
36
- x: item.rect.x + item.rect.width,
37
- y: item.rect.y + item.rect.height
19
+ function LinkAnchor({ onPress, ...props }) {
20
+ const ref = useRef(null);
21
+ const press = useRef(onPress);
22
+ press.current = onPress;
23
+ useEffect(() => ref.current ? isolatePointerDown(ref.current, () => press.current()) : void 0, []);
24
+ return /* @__PURE__ */ jsx("a", {
25
+ ref,
26
+ ...props
38
27
  });
39
- return {
40
- left: tl.x,
41
- top: tl.y,
42
- width: br.x - tl.x,
43
- height: br.y - tl.y
44
- };
45
28
  }
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
- }
29
+ /**
30
+ * Hand the link plugin this binding's website opener (`openExternalUri`)
31
+ * while the component is mounted: an allowed address opens in a new tab,
32
+ * anything else is refused and the plugin reports it.
33
+ */
34
+ function useUriOpener() {
35
+ const host = useOptionalCapability(LinkToken$1);
36
+ useEffect(() => host?.registerUriOpener(openExternalUri), [host]);
55
37
  }
56
38
  function LinkLayer({ renderLink } = {}) {
57
39
  const page = usePage();
58
40
  const link = useCapability(LinkToken);
41
+ useUriOpener();
42
+ const stage = useOptionalCapability(useStageToken());
59
43
  const actions = useOptionalCapability(ActionsToken);
60
44
  const linkPump = useMemo(() => actions ? createHoverPump(actions.dispatch) : null, [actions]);
61
- const items = useSelector(LinkToken, (c) => c.linksOn(page.pon), shallowArray);
62
- const engaged = useSelector(LinkToken, (c) => c.engaged());
63
- const editEnabled = useOptionalSelector(InteractionToken, (c) => c.activeTool()?.enables.has("annotation-edit") ?? false, false);
45
+ const items = useSelector(LinkToken, (link) => link.listLinks(page.ref), shallowArray);
46
+ const engaged = useSelector(LinkToken$1, (link) => link.isNavigationEngaged());
47
+ const editEnabled = useOptionalSelector(InteractionToken, (interaction) => interaction.activeToolEnables("annotation-edit"), false);
64
48
  useEffect(() => {
65
- link.ensurePage(page.pon);
66
- }, [link, page.pon]);
49
+ link.ensureLoaded(page.ref);
50
+ }, [link, page.ref]);
67
51
  if (!engaged || !items.length) return null;
68
- const visible = editEnabled ? items.filter((i) => !i.attached) : items;
52
+ const visible = navigableLinksOf(items, editEnabled);
69
53
  if (!visible.length) return null;
70
54
  return /* @__PURE__ */ jsx("div", {
71
55
  style: {
@@ -74,66 +58,33 @@ function LinkLayer({ renderLink } = {}) {
74
58
  pointerEvents: "none"
75
59
  },
76
60
  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 ? 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__ */ jsx("a", {
61
+ const { box, href, label } = linkAnchorOf(item, page.transform, (each) => link.getLabel(each));
62
+ const context = linkActivateContextOf(item, page.ref, stage);
63
+ const native = /* @__PURE__ */ jsx(LinkAnchor, {
64
+ onPress: () => sendLinkEvent(actions, item, page.ref, "mouseDown"),
101
65
  href: href ?? void 0,
102
66
  target: "_blank",
103
67
  rel: "noopener noreferrer",
104
68
  role: "link",
105
69
  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);
70
+ title: label,
71
+ "aria-label": label,
72
+ onClick: (event) => {
73
+ if (href && isModifiedClick(event)) return;
74
+ event.preventDefault();
75
+ link.activate(item, context);
113
76
  },
114
- onKeyDown: (e) => {
115
- if (e.key === "Enter" || e.key === " ") {
116
- e.preventDefault();
117
- openLinkTarget(link, item.target, context);
77
+ onKeyDown: (event) => {
78
+ if (event.key === "Enter" || event.key === " ") {
79
+ event.preventDefault();
80
+ link.activate(item, context);
118
81
  }
119
82
  },
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
- },
83
+ onPointerEnter: () => hoverLink(linkPump, item, page.ref),
129
84
  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
- },
85
+ onPointerUp: () => sendLinkEvent(actions, item, page.ref, "mouseUp"),
86
+ onFocus: () => sendLinkEvent(actions, item, page.ref, "focus"),
87
+ onBlur: () => sendLinkEvent(actions, item, page.ref, "blur"),
137
88
  style: {
138
89
  position: "absolute",
139
90
  left: box.left,
@@ -144,18 +95,27 @@ function LinkLayer({ renderLink } = {}) {
144
95
  pointerEvents: "auto"
145
96
  }
146
97
  });
147
- const out = renderLink?.({
148
- item,
149
- nativeComponent: native
98
+ const drawn = renderLink?.({
99
+ link: item,
100
+ native
150
101
  }) ?? native;
151
- return /* @__PURE__ */ jsx(React.Fragment, { children: out }, item.id);
102
+ return /* @__PURE__ */ jsx(React$1.Fragment, { children: drawn }, item.id);
152
103
  })
153
104
  });
154
105
  }
106
+ /**
107
+ * The link capability (`listLinks`, `activate`, …) for app code. While a
108
+ * component uses it, `activate()` opens a website in a new tab.
109
+ */
155
110
  function useLink() {
111
+ useUriOpener();
156
112
  return useCapability(LinkToken);
157
113
  }
114
+ /** Subscribe to one link event for the mounted lifetime: `useLinkEvent((link) => link.onActivated, handler)`. */
115
+ function useLinkEvent(select, handler) {
116
+ useCapabilityEvent(LinkToken, select, handler);
117
+ }
158
118
  //#endregion
159
- export { LinkLayer, openLinkTarget, useLink };
119
+ export { LinkLayer, useLink, useLinkEvent };
160
120
 
161
121
  //# sourceMappingURL=link.js.map
package/dist/link.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"link.js","names":[],"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.js","names":["LinkHostToken","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":"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"}
@@ -1,23 +1,48 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  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
  let _embedpdf_plugin_measurement = require("@embedpdf/plugin-measurement");
5
6
  //#region src/measurement.tsx
7
+ /** The measurement capability: scales, calibration, readouts and creating measurements. */
6
8
  function useMeasurement() {
7
- const cap = require_runtime.useCapability(_embedpdf_plugin_measurement.MeasurementToken);
8
- const busy = require_runtime.useSelector(_embedpdf_plugin_measurement.MeasurementToken, (c) => c.isBusy());
9
- const canCalibratePage = require_runtime.useSelector(_embedpdf_plugin_measurement.MeasurementToken, (c) => c.canCalibrate());
10
- return {
11
- ...cap,
12
- busy,
13
- canCalibratePage
14
- };
9
+ return require_runtime.useCapability(_embedpdf_plugin_measurement.MeasurementToken);
10
+ }
11
+ /** Subscribe to one measurement event for the mounted lifetime: `useMeasurementEvent((measurement) => measurement.onScaleChanged, handler)`. */
12
+ function useMeasurementEvent(select, handler) {
13
+ require_runtime.useCapabilityEvent(_embedpdf_plugin_measurement.MeasurementToken, select, handler);
14
+ }
15
+ /**
16
+ * The measurement state: whether a scale change runs, the calibration
17
+ * request waiting for its length, and the last change's reports (the page's
18
+ * State table, declared once in `measurementState`). Takes a selector, and
19
+ * re-renders only when what it returns changes.
20
+ */
21
+ const useMeasurementState = require_state.stateHook(_embedpdf_plugin_measurement.measurementState);
22
+ /** The measurement settings (`defaultScale`, `presets`), with or without a document. Takes a selector. */
23
+ const useMeasurementSettings = require_state.settingsHook(_embedpdf_plugin_measurement.MeasurementToken);
24
+ function usePageScale(page) {
25
+ const scoped = require_runtime.useDocumentScope();
26
+ return require_runtime.useKernelValue((kernel) => {
27
+ if (page === null) return null;
28
+ return kernel.tryCapability(_embedpdf_plugin_measurement.MeasurementToken, scoped ?? void 0)?.getPageScale(page) ?? _embedpdf_plugin_measurement.NO_PAGE_SCALE;
29
+ });
30
+ }
31
+ /**
32
+ * A measurement's `{ kind, value, label }`, or `{ unavailable }` with why there's none.
33
+ * Readouts are reference-stable in the plugin, so this re-renders only when the measurement's
34
+ * changes.
35
+ */
36
+ function useMeasurementReadout(ref) {
37
+ const scoped = require_runtime.useDocumentScope();
38
+ return require_runtime.useKernelValue((kernel) => kernel.tryCapability(_embedpdf_plugin_measurement.MeasurementToken, scoped ?? void 0)?.getReadout(ref) ?? _embedpdf_plugin_measurement.NO_READOUT);
15
39
  }
16
- const usePageScale = (pon) => require_runtime.useSelector(_embedpdf_plugin_measurement.MeasurementToken, (c) => c.pageScale(pon));
17
- const useMeasurementReadout = (ref) => require_runtime.useSelector(_embedpdf_plugin_measurement.MeasurementToken, (c) => c.readout(ref), (a, b) => JSON.stringify(a) === JSON.stringify(b));
18
40
  //#endregion
19
41
  exports.useMeasurement = useMeasurement;
42
+ exports.useMeasurementEvent = useMeasurementEvent;
20
43
  exports.useMeasurementReadout = useMeasurementReadout;
44
+ exports.useMeasurementSettings = useMeasurementSettings;
45
+ exports.useMeasurementState = useMeasurementState;
21
46
  exports.usePageScale = usePageScale;
22
47
  Object.keys(_embedpdf_plugin_measurement).forEach(function(k) {
23
48
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
@@ -1 +1 @@
1
- {"version":3,"file":"measurement.cjs","names":["useCapability","MeasurementToken","useSelector"],"sources":["../src/measurement.tsx"],"sourcesContent":["import { MeasurementToken } from '@embedpdf/plugin-measurement';\nimport type { MeasurementCapability } from '@embedpdf/plugin-measurement';\nimport type { AnnotationRef } from '@embedpdf/plugin-measurement';\nimport { useCapability, useSelector } from './runtime';\n\nexport * from '@embedpdf/plugin-measurement';\n\nexport function useMeasurement(): MeasurementCapability & {\n busy: boolean;\n canCalibratePage: boolean;\n} {\n const cap = useCapability(MeasurementToken);\n const busy = useSelector(MeasurementToken, (c) => c.isBusy());\n const canCalibratePage = useSelector(MeasurementToken, (c) => c.canCalibrate());\n return { ...cap, busy, canCalibratePage };\n}\n\nexport const usePageScale = (pon: number) => useSelector(MeasurementToken, (c) => c.pageScale(pon));\n\nexport const useMeasurementReadout = (ref: AnnotationRef) =>\n useSelector(\n MeasurementToken,\n (c) => c.readout(ref),\n (a, b) => JSON.stringify(a) === JSON.stringify(b),\n );\n"],"mappings":";;;;;AAOA,SAAgB,iBAGd;CACA,MAAM,MAAMA,gBAAAA,cAAcC,6BAAAA,gBAAgB;CAC1C,MAAM,OAAOC,gBAAAA,YAAYD,6BAAAA,mBAAmB,MAAM,EAAE,OAAO,CAAC;CAC5D,MAAM,mBAAmBC,gBAAAA,YAAYD,6BAAAA,mBAAmB,MAAM,EAAE,aAAa,CAAC;CAC9E,OAAO;EAAE,GAAG;EAAK;EAAM;CAAiB;AAC1C;AAEA,MAAa,gBAAgB,QAAgBC,gBAAAA,YAAYD,6BAAAA,mBAAmB,MAAM,EAAE,UAAU,GAAG,CAAC;AAElG,MAAa,yBAAyB,QACpCC,gBAAAA,YACED,6BAAAA,mBACC,MAAM,EAAE,QAAQ,GAAG,IACnB,GAAG,MAAM,KAAK,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC,CAClD"}
1
+ {"version":3,"file":"measurement.cjs","names":["useCapability","MeasurementToken","stateHook","measurementState","settingsHook","useDocumentScope","useKernelValue","NO_PAGE_SCALE","NO_READOUT"],"sources":["../src/measurement.tsx"],"sourcesContent":["/**\n * The React view of @embedpdf/plugin-measurement: the four hooks every\n * plugin has, plus one per page scale and one per measurement, so a\n * component re-renders only for its own page or measurement. Every hook\n * works before a document opens.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-measurement';\nimport type { EventHook, PageRef } from '@embedpdf/core';\nimport {\n MeasurementToken,\n measurementState,\n NO_PAGE_SCALE,\n NO_READOUT,\n} from '@embedpdf/plugin-measurement';\nimport type {\n AnnotationRef,\n MeasurementCapability,\n MeasurementReadout,\n MeasurementUnavailable,\n PageScale,\n} from '@embedpdf/plugin-measurement';\nimport { useCapability, useCapabilityEvent, useDocumentScope, useKernelValue } from './runtime';\nimport { settingsHook, stateHook } from './state';\n\n/** The measurement capability: scales, calibration, readouts and creating measurements. */\nexport function useMeasurement(): MeasurementCapability {\n return useCapability(MeasurementToken);\n}\n\n/** Subscribe to one measurement event for the mounted lifetime: `useMeasurementEvent((measurement) => measurement.onScaleChanged, handler)`. */\nexport function useMeasurementEvent<T>(\n select: (measurement: MeasurementCapability) => EventHook<T>,\n handler: (event: T) => void,\n): void {\n useCapabilityEvent(MeasurementToken, select, handler);\n}\n\n/**\n * The measurement state: whether a scale change runs, the calibration\n * request waiting for its length, and the last change's reports (the page's\n * State table, declared once in `measurementState`). Takes a selector, and\n * re-renders only when what it returns changes.\n */\nexport const useMeasurementState = stateHook(measurementState);\n\n/** The measurement settings (`defaultScale`, `presets`), with or without a document. Takes a selector. */\nexport const useMeasurementSettings = settingsHook(MeasurementToken);\n\n/**\n * A page's measurement scale (its ref or its index), for a scale bar or a\n * \"1:100\" badge. Accepts `null` (no current page) and answers `null` then,\n * so chrome can stay mounted without inventing a page.\n */\nexport function usePageScale(page: PageRef | number): PageScale;\nexport function usePageScale(page: PageRef | number | null): PageScale | null;\nexport function usePageScale(page: PageRef | number | null): PageScale | null {\n const scoped = useDocumentScope();\n // Scales are reference-stable in the plugin, so this re-renders only when this page's changes.\n return useKernelValue((kernel) => {\n if (page === null) return null;\n return (\n kernel.tryCapability(MeasurementToken, scoped ?? undefined)?.getPageScale(page) ??\n NO_PAGE_SCALE\n );\n });\n}\n\n/**\n * A measurement's `{ kind, value, label }`, or `{ unavailable }` with why there's none.\n * Readouts are reference-stable in the plugin, so this re-renders only when the measurement's\n * changes.\n */\nexport function useMeasurementReadout(\n ref: AnnotationRef,\n): MeasurementReadout | MeasurementUnavailable {\n const scoped = useDocumentScope();\n return useKernelValue(\n (kernel) =>\n kernel.tryCapability(MeasurementToken, scoped ?? undefined)?.getReadout(ref) ?? NO_READOUT,\n );\n}\n"],"mappings":";;;;;;;AA2BA,SAAgB,iBAAwC;CACtD,OAAOA,gBAAAA,cAAcC,6BAAAA,gBAAgB;AACvC;;AAGA,SAAgB,oBACd,QACA,SACM;CACN,gBAAA,mBAAmBA,6BAAAA,kBAAkB,QAAQ,OAAO;AACtD;;;;;;;AAQA,MAAa,sBAAsBC,cAAAA,UAAUC,6BAAAA,gBAAgB;;AAG7D,MAAa,yBAAyBC,cAAAA,aAAaH,6BAAAA,gBAAgB;AASnE,SAAgB,aAAa,MAAiD;CAC5E,MAAM,SAASI,gBAAAA,iBAAiB;CAEhC,OAAOC,gBAAAA,gBAAgB,WAAW;EAChC,IAAI,SAAS,MAAM,OAAO;EAC1B,OACE,OAAO,cAAcL,6BAAAA,kBAAkB,UAAU,KAAA,CAAS,CAAC,EAAE,aAAa,IAAI,KAC9EM,6BAAAA;CAEJ,CAAC;AACH;;;;;;AAOA,SAAgB,sBACd,KAC6C;CAC7C,MAAM,SAASF,gBAAAA,iBAAiB;CAChC,OAAOC,gBAAAA,gBACJ,WACC,OAAO,cAAcL,6BAAAA,kBAAkB,UAAU,KAAA,CAAS,CAAC,EAAE,WAAW,GAAG,KAAKO,6BAAAA,UACpF;AACF"}
@@ -1,12 +1,38 @@
1
- import { AnnotationRef, MeasurementCapability } from "@embedpdf/plugin-measurement";
1
+ import { n as StateHook, t as SettingsHook } from "./state-0K1I5cM4.cjs";
2
+ import { EventHook, PageRef } from "@embedpdf/core";
3
+ import { AnnotationRef, MeasurementCapability, MeasurementReadout, MeasurementUnavailable, PageScale } from "@embedpdf/plugin-measurement";
2
4
  export * from "@embedpdf/plugin-measurement";
3
5
  //#region src/measurement.d.ts
4
- declare function useMeasurement(): MeasurementCapability & {
6
+ /** The measurement capability: scales, calibration, readouts and creating measurements. */
7
+ declare function useMeasurement(): MeasurementCapability;
8
+ /** Subscribe to one measurement event for the mounted lifetime: `useMeasurementEvent((measurement) => measurement.onScaleChanged, handler)`. */
9
+ declare function useMeasurementEvent<T>(select: (measurement: MeasurementCapability) => EventHook<T>, handler: (event: T) => void): void;
10
+ /**
11
+ * The measurement state: whether a scale change runs, the calibration
12
+ * request waiting for its length, and the last change's reports (the page's
13
+ * State table, declared once in `measurementState`). Takes a selector, and
14
+ * re-renders only when what it returns changes.
15
+ */
16
+ declare const useMeasurementState: StateHook<{
5
17
  busy: boolean;
6
- canCalibratePage: boolean;
7
- };
8
- declare const usePageScale: (pon: number) => import("@embedpdf/plugin-measurement").PageScale;
9
- declare const useMeasurementReadout: (ref: AnnotationRef) => import("@embedpdf/plugin-measurement").MeasurementReadout | import("@embedpdf/plugin-measurement").MeasurementUnavailable;
18
+ calibrationRequest: import("@embedpdf/plugin-measurement").CalibrationRequest | null;
19
+ lastReports: readonly import("@embedpdf/plugin-measurement").ScaleChangeReport[];
20
+ }>;
21
+ /** The measurement settings (`defaultScale`, `presets`), with or without a document. Takes a selector. */
22
+ declare const useMeasurementSettings: SettingsHook<import("@embedpdf/plugin-measurement").MeasurementSettings>;
23
+ /**
24
+ * A page's measurement scale (its ref or its index), for a scale bar or a
25
+ * "1:100" badge. Accepts `null` (no current page) and answers `null` then,
26
+ * so chrome can stay mounted without inventing a page.
27
+ */
28
+ declare function usePageScale(page: PageRef | number): PageScale;
29
+ declare function usePageScale(page: PageRef | number | null): PageScale | null;
30
+ /**
31
+ * A measurement's `{ kind, value, label }`, or `{ unavailable }` with why there's none.
32
+ * Readouts are reference-stable in the plugin, so this re-renders only when the measurement's
33
+ * changes.
34
+ */
35
+ declare function useMeasurementReadout(ref: AnnotationRef): MeasurementReadout | MeasurementUnavailable;
10
36
  //#endregion
11
- export { useMeasurement, useMeasurementReadout, usePageScale };
37
+ export { useMeasurement, useMeasurementEvent, useMeasurementReadout, useMeasurementSettings, useMeasurementState, usePageScale };
12
38
  //# sourceMappingURL=measurement.d.cts.map
@@ -1,12 +1,38 @@
1
- import { AnnotationRef, MeasurementCapability } from "@embedpdf/plugin-measurement";
1
+ import { n as StateHook, t as SettingsHook } from "./state-0K1I5cM4.js";
2
+ import { EventHook, PageRef } from "@embedpdf/core";
3
+ import { AnnotationRef, MeasurementCapability, MeasurementReadout, MeasurementUnavailable, PageScale } from "@embedpdf/plugin-measurement";
2
4
  export * from "@embedpdf/plugin-measurement";
3
5
  //#region src/measurement.d.ts
4
- declare function useMeasurement(): MeasurementCapability & {
6
+ /** The measurement capability: scales, calibration, readouts and creating measurements. */
7
+ declare function useMeasurement(): MeasurementCapability;
8
+ /** Subscribe to one measurement event for the mounted lifetime: `useMeasurementEvent((measurement) => measurement.onScaleChanged, handler)`. */
9
+ declare function useMeasurementEvent<T>(select: (measurement: MeasurementCapability) => EventHook<T>, handler: (event: T) => void): void;
10
+ /**
11
+ * The measurement state: whether a scale change runs, the calibration
12
+ * request waiting for its length, and the last change's reports (the page's
13
+ * State table, declared once in `measurementState`). Takes a selector, and
14
+ * re-renders only when what it returns changes.
15
+ */
16
+ declare const useMeasurementState: StateHook<{
5
17
  busy: boolean;
6
- canCalibratePage: boolean;
7
- };
8
- declare const usePageScale: (pon: number) => import("@embedpdf/plugin-measurement").PageScale;
9
- declare const useMeasurementReadout: (ref: AnnotationRef) => import("@embedpdf/plugin-measurement").MeasurementReadout | import("@embedpdf/plugin-measurement").MeasurementUnavailable;
18
+ calibrationRequest: import("@embedpdf/plugin-measurement").CalibrationRequest | null;
19
+ lastReports: readonly import("@embedpdf/plugin-measurement").ScaleChangeReport[];
20
+ }>;
21
+ /** The measurement settings (`defaultScale`, `presets`), with or without a document. Takes a selector. */
22
+ declare const useMeasurementSettings: SettingsHook<import("@embedpdf/plugin-measurement").MeasurementSettings>;
23
+ /**
24
+ * A page's measurement scale (its ref or its index), for a scale bar or a
25
+ * "1:100" badge. Accepts `null` (no current page) and answers `null` then,
26
+ * so chrome can stay mounted without inventing a page.
27
+ */
28
+ declare function usePageScale(page: PageRef | number): PageScale;
29
+ declare function usePageScale(page: PageRef | number | null): PageScale | null;
30
+ /**
31
+ * A measurement's `{ kind, value, label }`, or `{ unavailable }` with why there's none.
32
+ * Readouts are reference-stable in the plugin, so this re-renders only when the measurement's
33
+ * changes.
34
+ */
35
+ declare function useMeasurementReadout(ref: AnnotationRef): MeasurementReadout | MeasurementUnavailable;
10
36
  //#endregion
11
- export { useMeasurement, useMeasurementReadout, usePageScale };
37
+ export { useMeasurement, useMeasurementEvent, useMeasurementReadout, useMeasurementSettings, useMeasurementState, usePageScale };
12
38
  //# sourceMappingURL=measurement.d.ts.map
@@ -1,21 +1,42 @@
1
- import "./rolldown-runtime-BBjsoOtd.js";
2
- import { useCapability, useSelector } from "./runtime.js";
3
- import { MeasurementToken } from "@embedpdf/plugin-measurement";
1
+ import { f as useCapability, g as useDocumentScope, p as useCapabilityEvent, x as useKernelValue } from "./runtime-B6xcPsEb.js";
2
+ import { n as stateHook, t as settingsHook } from "./state-CbgxhwLA.js";
3
+ import { MeasurementToken, NO_PAGE_SCALE, NO_READOUT, measurementState } from "@embedpdf/plugin-measurement";
4
4
  export * from "@embedpdf/plugin-measurement";
5
5
  //#region src/measurement.tsx
6
+ /** The measurement capability: scales, calibration, readouts and creating measurements. */
6
7
  function useMeasurement() {
7
- const cap = useCapability(MeasurementToken);
8
- const busy = useSelector(MeasurementToken, (c) => c.isBusy());
9
- const canCalibratePage = useSelector(MeasurementToken, (c) => c.canCalibrate());
10
- return {
11
- ...cap,
12
- busy,
13
- canCalibratePage
14
- };
8
+ return useCapability(MeasurementToken);
9
+ }
10
+ /** Subscribe to one measurement event for the mounted lifetime: `useMeasurementEvent((measurement) => measurement.onScaleChanged, handler)`. */
11
+ function useMeasurementEvent(select, handler) {
12
+ useCapabilityEvent(MeasurementToken, select, handler);
13
+ }
14
+ /**
15
+ * The measurement state: whether a scale change runs, the calibration
16
+ * request waiting for its length, and the last change's reports (the page's
17
+ * State table, declared once in `measurementState`). Takes a selector, and
18
+ * re-renders only when what it returns changes.
19
+ */
20
+ const useMeasurementState = stateHook(measurementState);
21
+ /** The measurement settings (`defaultScale`, `presets`), with or without a document. Takes a selector. */
22
+ const useMeasurementSettings = settingsHook(MeasurementToken);
23
+ function usePageScale(page) {
24
+ const scoped = useDocumentScope();
25
+ return useKernelValue((kernel) => {
26
+ if (page === null) return null;
27
+ return kernel.tryCapability(MeasurementToken, scoped ?? void 0)?.getPageScale(page) ?? NO_PAGE_SCALE;
28
+ });
29
+ }
30
+ /**
31
+ * A measurement's `{ kind, value, label }`, or `{ unavailable }` with why there's none.
32
+ * Readouts are reference-stable in the plugin, so this re-renders only when the measurement's
33
+ * changes.
34
+ */
35
+ function useMeasurementReadout(ref) {
36
+ const scoped = useDocumentScope();
37
+ return useKernelValue((kernel) => kernel.tryCapability(MeasurementToken, scoped ?? void 0)?.getReadout(ref) ?? NO_READOUT);
15
38
  }
16
- const usePageScale = (pon) => useSelector(MeasurementToken, (c) => c.pageScale(pon));
17
- const useMeasurementReadout = (ref) => useSelector(MeasurementToken, (c) => c.readout(ref), (a, b) => JSON.stringify(a) === JSON.stringify(b));
18
39
  //#endregion
19
- export { useMeasurement, useMeasurementReadout, usePageScale };
40
+ export { useMeasurement, useMeasurementEvent, useMeasurementReadout, useMeasurementSettings, useMeasurementState, usePageScale };
20
41
 
21
42
  //# sourceMappingURL=measurement.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"measurement.js","names":[],"sources":["../src/measurement.tsx"],"sourcesContent":["import { MeasurementToken } from '@embedpdf/plugin-measurement';\nimport type { MeasurementCapability } from '@embedpdf/plugin-measurement';\nimport type { AnnotationRef } from '@embedpdf/plugin-measurement';\nimport { useCapability, useSelector } from './runtime';\n\nexport * from '@embedpdf/plugin-measurement';\n\nexport function useMeasurement(): MeasurementCapability & {\n busy: boolean;\n canCalibratePage: boolean;\n} {\n const cap = useCapability(MeasurementToken);\n const busy = useSelector(MeasurementToken, (c) => c.isBusy());\n const canCalibratePage = useSelector(MeasurementToken, (c) => c.canCalibrate());\n return { ...cap, busy, canCalibratePage };\n}\n\nexport const usePageScale = (pon: number) => useSelector(MeasurementToken, (c) => c.pageScale(pon));\n\nexport const useMeasurementReadout = (ref: AnnotationRef) =>\n useSelector(\n MeasurementToken,\n (c) => c.readout(ref),\n (a, b) => JSON.stringify(a) === JSON.stringify(b),\n );\n"],"mappings":";;;;;AAOA,SAAgB,iBAGd;CACA,MAAM,MAAM,cAAc,gBAAgB;CAC1C,MAAM,OAAO,YAAY,mBAAmB,MAAM,EAAE,OAAO,CAAC;CAC5D,MAAM,mBAAmB,YAAY,mBAAmB,MAAM,EAAE,aAAa,CAAC;CAC9E,OAAO;EAAE,GAAG;EAAK;EAAM;CAAiB;AAC1C;AAEA,MAAa,gBAAgB,QAAgB,YAAY,mBAAmB,MAAM,EAAE,UAAU,GAAG,CAAC;AAElG,MAAa,yBAAyB,QACpC,YACE,mBACC,MAAM,EAAE,QAAQ,GAAG,IACnB,GAAG,MAAM,KAAK,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC,CAClD"}
1
+ {"version":3,"file":"measurement.js","names":[],"sources":["../src/measurement.tsx"],"sourcesContent":["/**\n * The React view of @embedpdf/plugin-measurement: the four hooks every\n * plugin has, plus one per page scale and one per measurement, so a\n * component re-renders only for its own page or measurement. Every hook\n * works before a document opens.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-measurement';\nimport type { EventHook, PageRef } from '@embedpdf/core';\nimport {\n MeasurementToken,\n measurementState,\n NO_PAGE_SCALE,\n NO_READOUT,\n} from '@embedpdf/plugin-measurement';\nimport type {\n AnnotationRef,\n MeasurementCapability,\n MeasurementReadout,\n MeasurementUnavailable,\n PageScale,\n} from '@embedpdf/plugin-measurement';\nimport { useCapability, useCapabilityEvent, useDocumentScope, useKernelValue } from './runtime';\nimport { settingsHook, stateHook } from './state';\n\n/** The measurement capability: scales, calibration, readouts and creating measurements. */\nexport function useMeasurement(): MeasurementCapability {\n return useCapability(MeasurementToken);\n}\n\n/** Subscribe to one measurement event for the mounted lifetime: `useMeasurementEvent((measurement) => measurement.onScaleChanged, handler)`. */\nexport function useMeasurementEvent<T>(\n select: (measurement: MeasurementCapability) => EventHook<T>,\n handler: (event: T) => void,\n): void {\n useCapabilityEvent(MeasurementToken, select, handler);\n}\n\n/**\n * The measurement state: whether a scale change runs, the calibration\n * request waiting for its length, and the last change's reports (the page's\n * State table, declared once in `measurementState`). Takes a selector, and\n * re-renders only when what it returns changes.\n */\nexport const useMeasurementState = stateHook(measurementState);\n\n/** The measurement settings (`defaultScale`, `presets`), with or without a document. Takes a selector. */\nexport const useMeasurementSettings = settingsHook(MeasurementToken);\n\n/**\n * A page's measurement scale (its ref or its index), for a scale bar or a\n * \"1:100\" badge. Accepts `null` (no current page) and answers `null` then,\n * so chrome can stay mounted without inventing a page.\n */\nexport function usePageScale(page: PageRef | number): PageScale;\nexport function usePageScale(page: PageRef | number | null): PageScale | null;\nexport function usePageScale(page: PageRef | number | null): PageScale | null {\n const scoped = useDocumentScope();\n // Scales are reference-stable in the plugin, so this re-renders only when this page's changes.\n return useKernelValue((kernel) => {\n if (page === null) return null;\n return (\n kernel.tryCapability(MeasurementToken, scoped ?? undefined)?.getPageScale(page) ??\n NO_PAGE_SCALE\n );\n });\n}\n\n/**\n * A measurement's `{ kind, value, label }`, or `{ unavailable }` with why there's none.\n * Readouts are reference-stable in the plugin, so this re-renders only when the measurement's\n * changes.\n */\nexport function useMeasurementReadout(\n ref: AnnotationRef,\n): MeasurementReadout | MeasurementUnavailable {\n const scoped = useDocumentScope();\n return useKernelValue(\n (kernel) =>\n kernel.tryCapability(MeasurementToken, scoped ?? undefined)?.getReadout(ref) ?? NO_READOUT,\n );\n}\n"],"mappings":";;;;;;AA2BA,SAAgB,iBAAwC;CACtD,OAAO,cAAc,gBAAgB;AACvC;;AAGA,SAAgB,oBACd,QACA,SACM;CACN,mBAAmB,kBAAkB,QAAQ,OAAO;AACtD;;;;;;;AAQA,MAAa,sBAAsB,UAAU,gBAAgB;;AAG7D,MAAa,yBAAyB,aAAa,gBAAgB;AASnE,SAAgB,aAAa,MAAiD;CAC5E,MAAM,SAAS,iBAAiB;CAEhC,OAAO,gBAAgB,WAAW;EAChC,IAAI,SAAS,MAAM,OAAO;EAC1B,OACE,OAAO,cAAc,kBAAkB,UAAU,KAAA,CAAS,CAAC,EAAE,aAAa,IAAI,KAC9E;CAEJ,CAAC;AACH;;;;;;AAOA,SAAgB,sBACd,KAC6C;CAC7C,MAAM,SAAS,iBAAiB;CAChC,OAAO,gBACJ,WACC,OAAO,cAAc,kBAAkB,UAAU,KAAA,CAAS,CAAC,EAAE,WAAW,GAAG,KAAK,UACpF;AACF"}