@embedpdf/react 3.0.0-next.0

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 (141) hide show
  1. package/LICENSE +192 -0
  2. package/dist/AnnotationListSnapshot-W0RCsyDE-dWna4TTJ.d.cts +2593 -0
  3. package/dist/AnnotationListSnapshot-W0RCsyDE-dWna4TTJ.d.ts +2593 -0
  4. package/dist/annotation-B6jwfTCh.d.cts +173 -0
  5. package/dist/annotation-Ct6dFtzJ.cjs +913 -0
  6. package/dist/annotation-Ct6dFtzJ.cjs.map +1 -0
  7. package/dist/annotation-DjGWJljY.js +829 -0
  8. package/dist/annotation-DjGWJljY.js.map +1 -0
  9. package/dist/annotation-menu.cjs +115 -0
  10. package/dist/annotation-menu.cjs.map +1 -0
  11. package/dist/annotation-menu.d.cts +7 -0
  12. package/dist/annotation-menu.d.ts +7 -0
  13. package/dist/annotation-menu.js +99 -0
  14. package/dist/annotation-menu.js.map +1 -0
  15. package/dist/annotation-nUMefyjw.d.ts +173 -0
  16. package/dist/annotation.cjs +25 -0
  17. package/dist/annotation.d.cts +3 -0
  18. package/dist/annotation.d.ts +3 -0
  19. package/dist/annotation.js +4 -0
  20. package/dist/commands.cjs +59 -0
  21. package/dist/commands.cjs.map +1 -0
  22. package/dist/commands.d.cts +20 -0
  23. package/dist/commands.d.ts +20 -0
  24. package/dist/commands.js +47 -0
  25. package/dist/commands.js.map +1 -0
  26. package/dist/form.cjs +560 -0
  27. package/dist/form.cjs.map +1 -0
  28. package/dist/form.d.cts +52 -0
  29. package/dist/form.d.ts +52 -0
  30. package/dist/form.js +545 -0
  31. package/dist/form.js.map +1 -0
  32. package/dist/i18n.cjs +52 -0
  33. package/dist/i18n.cjs.map +1 -0
  34. package/dist/i18n.d.cts +30 -0
  35. package/dist/i18n.d.ts +30 -0
  36. package/dist/i18n.js +42 -0
  37. package/dist/i18n.js.map +1 -0
  38. package/dist/index.cjs +240 -0
  39. package/dist/index.d.cts +37 -0
  40. package/dist/index.d.ts +37 -0
  41. package/dist/index.js +37 -0
  42. package/dist/interaction.cjs +152 -0
  43. package/dist/interaction.cjs.map +1 -0
  44. package/dist/interaction.d.cts +46 -0
  45. package/dist/interaction.d.ts +46 -0
  46. package/dist/interaction.js +139 -0
  47. package/dist/interaction.js.map +1 -0
  48. package/dist/link.cjs +108 -0
  49. package/dist/link.cjs.map +1 -0
  50. package/dist/link.d.cts +16 -0
  51. package/dist/link.d.ts +16 -0
  52. package/dist/link.js +98 -0
  53. package/dist/link.js.map +1 -0
  54. package/dist/metadata.cjs +25 -0
  55. package/dist/metadata.cjs.map +1 -0
  56. package/dist/metadata.d.cts +11 -0
  57. package/dist/metadata.d.ts +11 -0
  58. package/dist/metadata.js +16 -0
  59. package/dist/metadata.js.map +1 -0
  60. package/dist/page-edit.cjs +20 -0
  61. package/dist/page-edit.cjs.map +1 -0
  62. package/dist/page-edit.d.cts +7 -0
  63. package/dist/page-edit.d.ts +7 -0
  64. package/dist/page-edit.js +11 -0
  65. package/dist/page-edit.js.map +1 -0
  66. package/dist/page-view.cjs +107 -0
  67. package/dist/page-view.cjs.map +1 -0
  68. package/dist/page-view.d.cts +23 -0
  69. package/dist/page-view.d.ts +23 -0
  70. package/dist/page-view.js +95 -0
  71. package/dist/page-view.js.map +1 -0
  72. package/dist/redaction.cjs +35 -0
  73. package/dist/redaction.cjs.map +1 -0
  74. package/dist/redaction.d.cts +14 -0
  75. package/dist/redaction.d.ts +14 -0
  76. package/dist/redaction.js +25 -0
  77. package/dist/redaction.js.map +1 -0
  78. package/dist/render.cjs +142 -0
  79. package/dist/render.cjs.map +1 -0
  80. package/dist/render.d.cts +32 -0
  81. package/dist/render.d.ts +32 -0
  82. package/dist/render.js +131 -0
  83. package/dist/render.js.map +1 -0
  84. package/dist/rolldown-runtime-DDXJien1.cjs +50 -0
  85. package/dist/runtime-CMxIuF2O.d.cts +195 -0
  86. package/dist/runtime-DV98q7I0.d.ts +195 -0
  87. package/dist/runtime.cjs +314 -0
  88. package/dist/runtime.cjs.map +1 -0
  89. package/dist/runtime.d.cts +3 -0
  90. package/dist/runtime.d.ts +3 -0
  91. package/dist/runtime.js +287 -0
  92. package/dist/runtime.js.map +1 -0
  93. package/dist/scrollbar.cjs +241 -0
  94. package/dist/scrollbar.cjs.map +1 -0
  95. package/dist/scrollbar.d.cts +42 -0
  96. package/dist/scrollbar.d.ts +42 -0
  97. package/dist/scrollbar.js +213 -0
  98. package/dist/scrollbar.js.map +1 -0
  99. package/dist/search.cjs +70 -0
  100. package/dist/search.cjs.map +1 -0
  101. package/dist/search.d.cts +35 -0
  102. package/dist/search.d.ts +35 -0
  103. package/dist/search.js +58 -0
  104. package/dist/search.js.map +1 -0
  105. package/dist/selection.cjs +60 -0
  106. package/dist/selection.cjs.map +1 -0
  107. package/dist/selection.d.cts +12 -0
  108. package/dist/selection.d.ts +12 -0
  109. package/dist/selection.js +49 -0
  110. package/dist/selection.js.map +1 -0
  111. package/dist/shell.cjs +57 -0
  112. package/dist/shell.cjs.map +1 -0
  113. package/dist/shell.d.cts +27 -0
  114. package/dist/shell.d.ts +27 -0
  115. package/dist/shell.js +46 -0
  116. package/dist/shell.js.map +1 -0
  117. package/dist/stage.cjs +343 -0
  118. package/dist/stage.cjs.map +1 -0
  119. package/dist/stage.d.cts +104 -0
  120. package/dist/stage.d.ts +104 -0
  121. package/dist/stage.js +327 -0
  122. package/dist/stage.js.map +1 -0
  123. package/dist/stamp.cjs +76 -0
  124. package/dist/stamp.cjs.map +1 -0
  125. package/dist/stamp.d.cts +28 -0
  126. package/dist/stamp.d.ts +28 -0
  127. package/dist/stamp.js +63 -0
  128. package/dist/stamp.js.map +1 -0
  129. package/dist/toolbar.cjs +515 -0
  130. package/dist/toolbar.cjs.map +1 -0
  131. package/dist/toolbar.d.cts +100 -0
  132. package/dist/toolbar.d.ts +100 -0
  133. package/dist/toolbar.js +505 -0
  134. package/dist/toolbar.js.map +1 -0
  135. package/dist/views.cjs +35 -0
  136. package/dist/views.cjs.map +1 -0
  137. package/dist/views.d.cts +21 -0
  138. package/dist/views.d.ts +21 -0
  139. package/dist/views.js +26 -0
  140. package/dist/views.js.map +1 -0
  141. package/package.json +158 -0
@@ -0,0 +1,152 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_rolldown_runtime = require("./rolldown-runtime-DDXJien1.cjs");
3
+ const require_runtime = require("./runtime.cjs");
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ let react = require("react");
6
+ react = require_rolldown_runtime.__toESM(react, 1);
7
+ let _embedpdf_plugin_interaction = require("@embedpdf/plugin-interaction");
8
+ let _embedpdf_web = require("@embedpdf/web");
9
+ //#region src/interaction.tsx
10
+ const mods = (e) => ({
11
+ shift: e.shiftKey,
12
+ alt: e.altKey,
13
+ ctrl: e.ctrlKey,
14
+ meta: e.metaKey
15
+ });
16
+ /**
17
+ * Robust multi-click counter. `pointerdown.detail` is 0/1 in several browsers, so
18
+ * we count clicks ourselves from timing + proximity — the standard double/triple
19
+ * detection. Input normalization belongs in the adapter; the hub/handlers stay pure.
20
+ */
21
+ function createClickCounter(maxGapMs = 400, maxDistPx = 6) {
22
+ let last = 0;
23
+ let lx = 0;
24
+ let ly = 0;
25
+ let count = 0;
26
+ return (now, x, y) => {
27
+ count = now - last <= maxGapMs && Math.hypot(x - lx, y - ly) <= maxDistPx ? count + 1 : 1;
28
+ last = now;
29
+ lx = x;
30
+ ly = y;
31
+ return count;
32
+ };
33
+ }
34
+ function PagePointerSource() {
35
+ const page = require_runtime.usePage();
36
+ const interaction = require_runtime.useCapability(_embedpdf_plugin_interaction.InteractionToken);
37
+ const cursor = require_runtime.useSelector(_embedpdf_plugin_interaction.InteractionToken, (c) => c.cursor());
38
+ const ref = (0, react.useRef)(null);
39
+ (0, react.useEffect)(() => {
40
+ const el = ref.current;
41
+ if (!el) return;
42
+ const clicks = createClickCounter();
43
+ const sample = (phase, e, clickCount = 1) => {
44
+ const r = el.getBoundingClientRect();
45
+ return {
46
+ phase,
47
+ viewport: {
48
+ x: e.clientX - r.left,
49
+ y: e.clientY - r.top
50
+ },
51
+ page: {
52
+ pon: page.pon,
53
+ point: page.toPagePoint(e.clientX, e.clientY),
54
+ scale: page.transform.viewScale,
55
+ rotation: page.transform.rotation,
56
+ zoom: page.transform.zoom
57
+ },
58
+ project: (pon) => pon === page.pon ? page.toPagePoint(e.clientX, e.clientY) : null,
59
+ modifiers: mods(e),
60
+ clickCount
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
+ };
92
+ }, [interaction, page]);
93
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
94
+ ref,
95
+ style: {
96
+ position: "absolute",
97
+ inset: 0,
98
+ cursor,
99
+ touchAction: "none"
100
+ }
101
+ });
102
+ }
103
+ /** Read + switch the active tool (for a toolbar). */
104
+ function useTool() {
105
+ const interaction = require_runtime.useCapability(_embedpdf_plugin_interaction.InteractionToken);
106
+ return {
107
+ activeToolId: require_runtime.useSelector(_embedpdf_plugin_interaction.InteractionToken, (c) => c.activeToolId()),
108
+ activate: interaction.activateTool,
109
+ tools: interaction.tools()
110
+ };
111
+ }
112
+ const toCursor = (img, fallback) => typeof img === "string" ? img : (0, _embedpdf_web.svgCursor)(img.fallback === void 0 && fallback ? {
113
+ ...img,
114
+ fallback
115
+ } : img);
116
+ /**
117
+ * Give a tool IMAGE cursors — the armed-tool indicator. The cursor is the only
118
+ * zero-latency pointer-locked pixel the platform has, and the hub already
119
+ * arbitrates it: unmapped hover claims beat it over annotations/text, page
120
+ * gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill
121
+ * controls) carries its own CSS cursor — so nothing chases the pointer in
122
+ * DOM. `null` installs nothing. A re-render with new content rebuilds the
123
+ * cursor (live recolor from tool defaults); unmount restores the tool's
124
+ * declared cursors.
125
+ */
126
+ function useToolCursor(spec) {
127
+ const interaction = require_runtime.useCapability(_embedpdf_plugin_interaction.InteractionToken);
128
+ const key = spec && JSON.stringify(spec);
129
+ const ref = (0, react.useRef)(spec);
130
+ ref.current = spec;
131
+ (0, react.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);
136
+ }, [interaction, key]);
137
+ }
138
+ //#endregion
139
+ exports.PagePointerSource = PagePointerSource;
140
+ exports.createClickCounter = createClickCounter;
141
+ exports.useTool = useTool;
142
+ exports.useToolCursor = useToolCursor;
143
+ Object.keys(_embedpdf_plugin_interaction).forEach(function(k) {
144
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
145
+ enumerable: true,
146
+ get: function() {
147
+ return _embedpdf_plugin_interaction[k];
148
+ }
149
+ });
150
+ });
151
+
152
+ //# sourceMappingURL=interaction.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interaction.cjs","names":["usePage","useCapability","InteractionToken","useSelector"],"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.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-interaction';\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.toPagePoint(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 — toPagePoint 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.toPagePoint(e.clientX, e.clientY) : null),\n modifiers: mods(e),\n clickCount,\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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"}
@@ -0,0 +1,46 @@
1
+ import { SvgCursorOptions } from "@embedpdf/web";
2
+ export * from "@embedpdf/plugin-interaction";
3
+ //#region src/interaction.d.ts
4
+ /**
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.
8
+ */
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
+ }
33
+ /**
34
+ * Give a tool IMAGE cursors — the armed-tool indicator. The cursor is the only
35
+ * zero-latency pointer-locked pixel the platform has, and the hub already
36
+ * arbitrates it: unmapped hover claims beat it over annotations/text, page
37
+ * gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill
38
+ * controls) carries its own CSS cursor — so nothing chases the pointer in
39
+ * DOM. `null` installs nothing. A re-render with new content rebuilds the
40
+ * cursor (live recolor from tool defaults); unmount restores the tool's
41
+ * declared cursors.
42
+ */
43
+ declare function useToolCursor(spec: ToolCursorSpec | null): void;
44
+ //#endregion
45
+ export { PagePointerSource, ToolCursorImage, ToolCursorSpec, createClickCounter, useTool, useToolCursor };
46
+ //# sourceMappingURL=interaction.d.cts.map
@@ -0,0 +1,46 @@
1
+ import { SvgCursorOptions } from "@embedpdf/web";
2
+ export * from "@embedpdf/plugin-interaction";
3
+ //#region src/interaction.d.ts
4
+ /**
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.
8
+ */
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
+ }
33
+ /**
34
+ * Give a tool IMAGE cursors — the armed-tool indicator. The cursor is the only
35
+ * zero-latency pointer-locked pixel the platform has, and the hub already
36
+ * arbitrates it: unmapped hover claims beat it over annotations/text, page
37
+ * gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill
38
+ * controls) carries its own CSS cursor — so nothing chases the pointer in
39
+ * DOM. `null` installs nothing. A re-render with new content rebuilds the
40
+ * cursor (live recolor from tool defaults); unmount restores the tool's
41
+ * declared cursors.
42
+ */
43
+ declare function useToolCursor(spec: ToolCursorSpec | null): void;
44
+ //#endregion
45
+ export { PagePointerSource, ToolCursorImage, ToolCursorSpec, createClickCounter, useTool, useToolCursor };
46
+ //# sourceMappingURL=interaction.d.ts.map
@@ -0,0 +1,139 @@
1
+ import { useCapability, usePage, useSelector } from "./runtime.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from "react";
4
+ import { InteractionToken } from "@embedpdf/plugin-interaction";
5
+ import { svgCursor } from "@embedpdf/web";
6
+ export * from "@embedpdf/plugin-interaction";
7
+ //#region src/interaction.tsx
8
+ const mods = (e) => ({
9
+ shift: e.shiftKey,
10
+ alt: e.altKey,
11
+ ctrl: e.ctrlKey,
12
+ meta: e.metaKey
13
+ });
14
+ /**
15
+ * Robust multi-click counter. `pointerdown.detail` is 0/1 in several browsers, so
16
+ * we count clicks ourselves from timing + proximity — the standard double/triple
17
+ * detection. Input normalization belongs in the adapter; the hub/handlers stay pure.
18
+ */
19
+ function createClickCounter(maxGapMs = 400, maxDistPx = 6) {
20
+ let last = 0;
21
+ let lx = 0;
22
+ let ly = 0;
23
+ let count = 0;
24
+ return (now, x, y) => {
25
+ count = now - last <= maxGapMs && Math.hypot(x - lx, y - ly) <= maxDistPx ? count + 1 : 1;
26
+ last = now;
27
+ lx = x;
28
+ ly = y;
29
+ return count;
30
+ };
31
+ }
32
+ function PagePointerSource() {
33
+ const page = usePage();
34
+ const interaction = useCapability(InteractionToken);
35
+ const cursor = useSelector(InteractionToken, (c) => c.cursor());
36
+ const ref = useRef(null);
37
+ useEffect(() => {
38
+ const el = ref.current;
39
+ if (!el) return;
40
+ const clicks = createClickCounter();
41
+ const sample = (phase, e, clickCount = 1) => {
42
+ const r = el.getBoundingClientRect();
43
+ return {
44
+ phase,
45
+ viewport: {
46
+ x: e.clientX - r.left,
47
+ y: e.clientY - r.top
48
+ },
49
+ page: {
50
+ pon: page.pon,
51
+ point: page.toPagePoint(e.clientX, e.clientY),
52
+ scale: page.transform.viewScale,
53
+ rotation: page.transform.rotation,
54
+ zoom: page.transform.zoom
55
+ },
56
+ project: (pon) => pon === page.pon ? page.toPagePoint(e.clientX, e.clientY) : null,
57
+ modifiers: mods(e),
58
+ clickCount
59
+ };
60
+ };
61
+ let dragging = false;
62
+ const down = (e) => {
63
+ if (e.button !== 0) return;
64
+ dragging = true;
65
+ interaction.dispatch(sample("down", e, clicks(Date.now(), e.clientX, e.clientY)));
66
+ };
67
+ const hover = (e) => {
68
+ if (dragging) return;
69
+ interaction.dispatch(sample("move", e));
70
+ };
71
+ const drag = (e) => {
72
+ if (!dragging) return;
73
+ interaction.dispatch(sample("move", e));
74
+ };
75
+ const up = (e) => {
76
+ if (!dragging) return;
77
+ dragging = false;
78
+ interaction.dispatch(sample("up", e));
79
+ };
80
+ el.addEventListener("pointerdown", down);
81
+ el.addEventListener("pointermove", hover);
82
+ window.addEventListener("pointermove", drag);
83
+ window.addEventListener("pointerup", up);
84
+ return () => {
85
+ el.removeEventListener("pointerdown", down);
86
+ el.removeEventListener("pointermove", hover);
87
+ window.removeEventListener("pointermove", drag);
88
+ window.removeEventListener("pointerup", up);
89
+ };
90
+ }, [interaction, page]);
91
+ return /* @__PURE__ */ jsx("div", {
92
+ ref,
93
+ style: {
94
+ position: "absolute",
95
+ inset: 0,
96
+ cursor,
97
+ touchAction: "none"
98
+ }
99
+ });
100
+ }
101
+ /** Read + switch the active tool (for a toolbar). */
102
+ function useTool() {
103
+ const interaction = useCapability(InteractionToken);
104
+ return {
105
+ activeToolId: useSelector(InteractionToken, (c) => c.activeToolId()),
106
+ activate: interaction.activateTool,
107
+ tools: interaction.tools()
108
+ };
109
+ }
110
+ const toCursor = (img, fallback) => typeof img === "string" ? img : svgCursor(img.fallback === void 0 && fallback ? {
111
+ ...img,
112
+ fallback
113
+ } : img);
114
+ /**
115
+ * Give a tool IMAGE cursors — the armed-tool indicator. The cursor is the only
116
+ * zero-latency pointer-locked pixel the platform has, and the hub already
117
+ * arbitrates it: unmapped hover claims beat it over annotations/text, page
118
+ * gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill
119
+ * controls) carries its own CSS cursor — so nothing chases the pointer in
120
+ * DOM. `null` installs nothing. A re-render with new content rebuilds the
121
+ * cursor (live recolor from tool defaults); unmount restores the tool's
122
+ * declared cursors.
123
+ */
124
+ function useToolCursor(spec) {
125
+ const interaction = useCapability(InteractionToken);
126
+ const key = spec && JSON.stringify(spec);
127
+ const ref = useRef(spec);
128
+ ref.current = spec;
129
+ useEffect(() => {
130
+ const s = ref.current;
131
+ if (!s) return;
132
+ interaction.setToolCursor(s.toolId, Object.fromEntries(Object.entries(s.cursors).map(([k, v]) => [k, toCursor(v, k)])));
133
+ return () => interaction.setToolCursor(s.toolId, null);
134
+ }, [interaction, key]);
135
+ }
136
+ //#endregion
137
+ export { PagePointerSource, createClickCounter, useTool, useToolCursor };
138
+
139
+ //# sourceMappingURL=interaction.js.map
@@ -0,0 +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.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.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-interaction';\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.toPagePoint(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 — toPagePoint 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.toPagePoint(e.clientX, e.clientY) : null),\n modifiers: mods(e),\n clickCount,\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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"}
package/dist/link.cjs ADDED
@@ -0,0 +1,108 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_rolldown_runtime = require("./rolldown-runtime-DDXJien1.cjs");
3
+ const require_runtime = require("./runtime.cjs");
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ let react = require("react");
6
+ react = require_rolldown_runtime.__toESM(react, 1);
7
+ let _embedpdf_web = require("@embedpdf/web");
8
+ let _embedpdf_plugin_link = require("@embedpdf/plugin-link");
9
+ //#region src/link.tsx
10
+ /** Content rect → view px (the page wrapper's own space) — the same idiom as
11
+ * the annotation and form layers: never re-derive `x * scale`. */
12
+ function boxOf(item, page) {
13
+ const tl = page.transform.pageToContent({
14
+ x: item.rect.x,
15
+ y: item.rect.y
16
+ });
17
+ const br = page.transform.pageToContent({
18
+ x: item.rect.x + item.rect.width,
19
+ y: item.rect.y + item.rect.height
20
+ });
21
+ return {
22
+ left: tl.x,
23
+ top: tl.y,
24
+ width: br.x - tl.x,
25
+ height: br.y - tl.y
26
+ };
27
+ }
28
+ /** A human label for the hover tooltip. Apps with i18n render their own via
29
+ * `renderLink`; this is the sensible default. */
30
+ function labelOf(item) {
31
+ switch (item.target.kind) {
32
+ case "uri": return item.target.uri;
33
+ case "goto": return "Go to destination";
34
+ case "named": return item.target.name;
35
+ default: return "Link";
36
+ }
37
+ }
38
+ function LinkLayer({ renderLink } = {}) {
39
+ const page = require_runtime.usePage();
40
+ const link = require_runtime.useCapability(_embedpdf_plugin_link.LinkToken);
41
+ const items = require_runtime.useSelector(_embedpdf_plugin_link.LinkToken, (c) => c.linksOn(page.pon), require_runtime.shallowArray);
42
+ const engaged = require_runtime.useSelector(_embedpdf_plugin_link.LinkToken, (c) => c.engaged());
43
+ (0, react.useEffect)(() => {
44
+ link.ensurePage(page.pon);
45
+ }, [link, page.pon]);
46
+ if (!engaged || !items.length) return null;
47
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
48
+ style: {
49
+ position: "absolute",
50
+ inset: 0,
51
+ pointerEvents: "none"
52
+ },
53
+ children: items.map((item) => {
54
+ const box = boxOf(item, page);
55
+ const native = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
56
+ href: (item.target.kind === "uri" ? (0, _embedpdf_web.sanitizeExternalUri)(item.target.uri) : null) ?? void 0,
57
+ target: "_blank",
58
+ rel: "noopener noreferrer",
59
+ role: "link",
60
+ tabIndex: 0,
61
+ title: labelOf(item),
62
+ "aria-label": labelOf(item),
63
+ onClick: (e) => {
64
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
65
+ e.preventDefault();
66
+ link.activate(item.target);
67
+ },
68
+ onKeyDown: (e) => {
69
+ if (e.key === "Enter" || e.key === " ") {
70
+ e.preventDefault();
71
+ link.activate(item.target);
72
+ }
73
+ },
74
+ onPointerDown: (e) => e.stopPropagation(),
75
+ style: {
76
+ position: "absolute",
77
+ left: box.left,
78
+ top: box.top,
79
+ width: box.width,
80
+ height: box.height,
81
+ cursor: "pointer",
82
+ pointerEvents: "auto"
83
+ }
84
+ });
85
+ const out = renderLink?.({
86
+ item,
87
+ nativeComponent: native
88
+ }) ?? native;
89
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: out }, item.id);
90
+ })
91
+ });
92
+ }
93
+ function useLink() {
94
+ return require_runtime.useCapability(_embedpdf_plugin_link.LinkToken);
95
+ }
96
+ //#endregion
97
+ exports.LinkLayer = LinkLayer;
98
+ exports.useLink = useLink;
99
+ Object.keys(_embedpdf_plugin_link).forEach(function(k) {
100
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
101
+ enumerable: true,
102
+ get: function() {
103
+ return _embedpdf_plugin_link[k];
104
+ }
105
+ });
106
+ });
107
+
108
+ //# sourceMappingURL=link.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.cjs","names":["usePage","useCapability","LinkToken","useSelector","shallowArray","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 } from 'react';\nimport { LinkToken, type LinkNavItem } from '@embedpdf/plugin-link';\nimport { sanitizeExternalUri } from '@embedpdf/web';\n\nimport { shallowArray, useCapability, usePage, useSelector } from './runtime';\nimport type { PageContextValue } from './runtime';\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.pageToContent({ x: item.rect.x, y: item.rect.y });\n const br = page.transform.pageToContent({\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 const items = useSelector(LinkToken, (c) => c.linksOn(page.pon), shallowArray);\n const engaged = useSelector(LinkToken, (c) => c.engaged());\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\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {items.map((item) => {\n const box = boxOf(item, page);\n // Real href ONLY for sanitized external URIs; blocked schemes and\n // internal targets activate through the plugin instead.\n const href = item.target.kind === 'uri' ? sanitizeExternalUri(item.target.uri) : null;\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 // Plain left-click activates through the plugin (stage reveal /\n // policy / analytics). Modified clicks and middle-clicks on a\n // real href keep their native browser behaviour.\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n e.preventDefault();\n link.activate(item.target);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n link.activate(item.target);\n }\n }}\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) => e.stopPropagation()}\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":";;;;;;;;;;;AAuBA,SAAS,MAAM,MAAmB,MAAwB;CACxD,MAAM,KAAK,KAAK,UAAU,cAAc;EAAE,GAAG,KAAK,KAAK;EAAG,GAAG,KAAK,KAAK;CAAE,CAAC;CAC1E,MAAM,KAAK,KAAK,UAAU,cAAc;EACtC,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK;EAC3B,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK;CAC7B,CAAC;CACD,OAAO;EAAE,MAAM,GAAG;EAAG,KAAK,GAAG;EAAG,OAAO,GAAG,IAAI,GAAG;EAAG,QAAQ,GAAG,IAAI,GAAG;CAAE;AAC1E;;;AAIA,SAAS,QAAQ,MAA2B;CAC1C,QAAQ,KAAK,OAAO,MAApB;EACE,KAAK,OACH,OAAO,KAAK,OAAO;EACrB,KAAK,QACH,OAAO;EACT,KAAK,SACH,OAAO,KAAK,OAAO;EACrB,SACE,OAAO;CACX;AACF;AAUA,SAAgB,UAAU,EAAE,eAA+B,CAAC,GAAG;CAC7D,MAAM,OAAOA,gBAAAA,QAAQ;CACrB,MAAM,OAAOC,gBAAAA,cAAcC,sBAAAA,SAAS;CACpC,MAAM,QAAQC,gBAAAA,YAAYD,sBAAAA,YAAY,MAAM,EAAE,QAAQ,KAAK,GAAG,GAAGE,gBAAAA,YAAY;CAC7E,MAAM,UAAUD,gBAAAA,YAAYD,sBAAAA,YAAY,MAAM,EAAE,QAAQ,CAAC;CAEzD,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,KAAK,WAAW,KAAK,GAAG;CAC1B,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC;CAInB,IAAI,CAAC,WAAW,CAAC,MAAM,QAAQ,OAAO;CAEtC,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG,eAAe;EAAO;EACjE,UAAA,MAAM,KAAK,SAAS;GACnB,MAAM,MAAM,MAAM,MAAM,IAAI;GAI5B,MAAM,SACJ,iBAAA,GAAA,kBAAA,IAAA,CAAC,KAAD;IACE,OAHS,KAAK,OAAO,SAAS,SAAA,GAAA,cAAA,oBAAA,CAA4B,KAAK,OAAO,GAAG,IAAI,SAG/D,KAAA;IACd,QAAO;IACP,KAAI;IACJ,MAAK;IACL,UAAU;IACV,OAAO,QAAQ,IAAI;IACnB,cAAY,QAAQ,IAAI;IACxB,UAAU,MAAM;KAId,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ;KACtD,EAAE,eAAe;KACjB,KAAK,SAAS,KAAK,MAAM;IAC3B;IACA,YAAY,MAAM;KAChB,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;MACtC,EAAE,eAAe;MACjB,KAAK,SAAS,KAAK,MAAM;KAC3B;IACF;IAGA,gBAAgB,MAAM,EAAE,gBAAgB;IACxC,OAAO;KACL,UAAU;KACV,MAAM,IAAI;KACV,KAAK,IAAI;KACT,OAAO,IAAI;KACX,QAAQ,IAAI;KACZ,QAAQ;KACR,eAAe;IACjB;GACD,CAAA;GAEH,MAAM,MAAM,aAAa;IAAE;IAAM,iBAAiB;GAAO,CAAC,KAAK;GAC/D,OAAO,iBAAA,GAAA,kBAAA,IAAA,CAACG,MAAM,UAAP,EAAA,UAA+B,IAAoB,GAA9B,KAAK,EAAyB;EAC5D,CAAC;CACE,CAAA;AAET;AAEA,SAAgB,UAAU;CACxB,OAAOJ,gBAAAA,cAAcC,sBAAAA,SAAS;AAChC"}
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import { LinkNavItem } from "@embedpdf/plugin-link";
3
+ export * from "@embedpdf/plugin-link";
4
+ //#region src/link.d.ts
5
+ interface LinkLayerProps {
6
+ /** Wrap or replace a link's native anchor (badging, custom tooltips). */
7
+ renderLink?: (args: {
8
+ item: LinkNavItem;
9
+ nativeComponent: React.ReactNode;
10
+ }) => React.ReactNode | undefined;
11
+ }
12
+ declare function LinkLayer({ renderLink }?: LinkLayerProps): import("react/jsx-runtime").JSX.Element | null;
13
+ declare function useLink(): import("@embedpdf/plugin-link").LinkCapability;
14
+ //#endregion
15
+ export { LinkLayer, LinkLayerProps, useLink };
16
+ //# sourceMappingURL=link.d.cts.map
package/dist/link.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import { LinkNavItem } from "@embedpdf/plugin-link";
3
+ export * from "@embedpdf/plugin-link";
4
+ //#region src/link.d.ts
5
+ interface LinkLayerProps {
6
+ /** Wrap or replace a link's native anchor (badging, custom tooltips). */
7
+ renderLink?: (args: {
8
+ item: LinkNavItem;
9
+ nativeComponent: React.ReactNode;
10
+ }) => React.ReactNode | undefined;
11
+ }
12
+ declare function LinkLayer({ renderLink }?: LinkLayerProps): import("react/jsx-runtime").JSX.Element | null;
13
+ declare function useLink(): import("@embedpdf/plugin-link").LinkCapability;
14
+ //#endregion
15
+ export { LinkLayer, LinkLayerProps, useLink };
16
+ //# sourceMappingURL=link.d.ts.map