@embedpdf/react 3.0.0-next.13 → 3.0.0-next.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AnnotationList-CkijRHet-HEFNUIZe.d.cts +877 -0
- package/dist/AnnotationList-CkijRHet-HEFNUIZe.d.ts +877 -0
- package/dist/actions.cjs +25 -5
- package/dist/actions.cjs.map +1 -1
- package/dist/actions.d.cts +18 -5
- package/dist/actions.d.ts +18 -5
- package/dist/actions.js +22 -6
- package/dist/actions.js.map +1 -1
- package/dist/anchored-DeX95xSd.d.ts +85 -0
- package/dist/anchored-DzN3JywS.d.cts +85 -0
- package/dist/anchored.cjs +71 -28
- package/dist/anchored.cjs.map +1 -1
- package/dist/anchored.d.cts +2 -45
- package/dist/anchored.d.ts +2 -45
- package/dist/anchored.js +56 -19
- package/dist/anchored.js.map +1 -1
- package/dist/annotation-B48UT2ln.d.ts +302 -0
- package/dist/annotation-C1ttM8Wj.js +708 -0
- package/dist/annotation-C1ttM8Wj.js.map +1 -0
- package/dist/annotation-C9dVwo9b.d.cts +302 -0
- package/dist/annotation-Dex5EBda.cjs +830 -0
- package/dist/annotation-Dex5EBda.cjs.map +1 -0
- package/dist/annotation.cjs +20 -783
- package/dist/annotation.d.cts +2 -2
- package/dist/annotation.d.ts +2 -2
- package/dist/annotation.js +2 -768
- package/dist/commands.cjs +68 -28
- package/dist/commands.cjs.map +1 -1
- package/dist/commands.d.cts +37 -10
- package/dist/commands.d.ts +37 -10
- package/dist/commands.js +68 -31
- package/dist/commands.js.map +1 -1
- package/dist/form-55izw0jS.js +511 -0
- package/dist/form-55izw0jS.js.map +1 -0
- package/dist/form-BNqQfGGH.cjs +548 -0
- package/dist/form-BNqQfGGH.cjs.map +1 -0
- package/dist/form.cjs +5 -5
- package/dist/form.d.cts +32 -25
- package/dist/form.d.ts +32 -25
- package/dist/form.js +2 -3
- package/dist/history.cjs +38 -0
- package/dist/history.cjs.map +1 -0
- package/dist/history.d.cts +27 -0
- package/dist/history.d.ts +27 -0
- package/dist/history.js +27 -0
- package/dist/history.js.map +1 -0
- package/dist/i18n.cjs +26 -30
- package/dist/i18n.cjs.map +1 -1
- package/dist/i18n.d.cts +24 -21
- package/dist/i18n.d.ts +24 -21
- package/dist/i18n.js +24 -31
- package/dist/i18n.js.map +1 -1
- package/dist/index.cjs +213 -57
- package/dist/index.d.cts +32 -31
- package/dist/index.d.ts +32 -31
- package/dist/index.js +33 -29
- package/dist/inert-CmBEbKCn.cjs +19 -0
- package/dist/inert-CmBEbKCn.cjs.map +1 -0
- package/dist/inert-D8fkennb.js +14 -0
- package/dist/inert-D8fkennb.js.map +1 -0
- package/dist/interaction.cjs +48 -102
- package/dist/interaction.cjs.map +1 -1
- package/dist/interaction.d.cts +23 -32
- package/dist/interaction.d.ts +23 -32
- package/dist/interaction.js +33 -102
- package/dist/interaction.js.map +1 -1
- package/dist/link.cjs +65 -104
- package/dist/link.cjs.map +1 -1
- package/dist/link.d.cts +21 -16
- package/dist/link.d.ts +22 -17
- package/dist/link.js +67 -107
- package/dist/link.js.map +1 -1
- package/dist/measurement.cjs +56 -0
- package/dist/measurement.cjs.map +1 -0
- package/dist/measurement.d.cts +38 -0
- package/dist/measurement.d.ts +38 -0
- package/dist/measurement.js +42 -0
- package/dist/measurement.js.map +1 -0
- package/dist/metadata.cjs +20 -7
- package/dist/metadata.cjs.map +1 -1
- package/dist/metadata.d.cts +22 -7
- package/dist/metadata.d.ts +22 -7
- package/dist/metadata.js +20 -10
- package/dist/metadata.js.map +1 -1
- package/dist/page-edit.cjs +7 -3
- package/dist/page-edit.cjs.map +1 -1
- package/dist/page-edit.d.cts +6 -2
- package/dist/page-edit.d.ts +6 -2
- package/dist/page-edit.js +7 -4
- package/dist/page-edit.js.map +1 -1
- package/dist/page-layers-Bkaaz8Zf.js +26 -0
- package/dist/page-layers-Bkaaz8Zf.js.map +1 -0
- package/dist/page-layers-BloZDoOV.cjs +37 -0
- package/dist/page-layers-BloZDoOV.cjs.map +1 -0
- package/dist/page-view.cjs +89 -82
- package/dist/page-view.cjs.map +1 -1
- package/dist/page-view.d.cts +14 -7
- package/dist/page-view.d.ts +18 -11
- package/dist/page-view.js +86 -81
- package/dist/page-view.js.map +1 -1
- package/dist/redaction.cjs +30 -15
- package/dist/redaction.cjs.map +1 -1
- package/dist/redaction.d.cts +27 -9
- package/dist/redaction.d.ts +27 -9
- package/dist/redaction.js +29 -18
- package/dist/redaction.js.map +1 -1
- package/dist/render.cjs +71 -46
- package/dist/render.cjs.map +1 -1
- package/dist/render.d.cts +17 -5
- package/dist/render.d.ts +17 -5
- package/dist/render.js +70 -49
- package/dist/render.js.map +1 -1
- package/dist/runtime-B6xcPsEb.js +398 -0
- package/dist/runtime-B6xcPsEb.js.map +1 -0
- package/dist/runtime-DBEBlpQV.d.cts +237 -0
- package/dist/runtime-V3MVMb91.d.ts +237 -0
- package/dist/runtime-fzpGv78v.cjs +555 -0
- package/dist/runtime-fzpGv78v.cjs.map +1 -0
- package/dist/runtime.cjs +45 -324
- package/dist/runtime.d.cts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +2 -306
- package/dist/search.cjs +81 -50
- package/dist/search.cjs.map +1 -1
- package/dist/search.d.cts +36 -24
- package/dist/search.d.ts +36 -24
- package/dist/search.js +81 -53
- package/dist/search.js.map +1 -1
- package/dist/selection.cjs +105 -108
- package/dist/selection.cjs.map +1 -1
- package/dist/selection.d.cts +54 -26
- package/dist/selection.d.ts +56 -28
- package/dist/selection.js +103 -110
- package/dist/selection.js.map +1 -1
- package/dist/shell.cjs +36 -21
- package/dist/shell.cjs.map +1 -1
- package/dist/shell.d.cts +32 -17
- package/dist/shell.d.ts +32 -17
- package/dist/shell.js +36 -24
- package/dist/shell.js.map +1 -1
- package/dist/signature.cjs +29 -48
- package/dist/signature.cjs.map +1 -1
- package/dist/signature.d.cts +26 -26
- package/dist/signature.d.ts +26 -26
- package/dist/signature.js +30 -48
- package/dist/signature.js.map +1 -1
- package/dist/stage-BIG6aeVd.cjs +369 -0
- package/dist/stage-BIG6aeVd.cjs.map +1 -0
- package/dist/stage-ByFPD-Fv.d.cts +117 -0
- package/dist/stage-DaboaheR.d.ts +117 -0
- package/dist/stage-DoVekb83.js +302 -0
- package/dist/stage-DoVekb83.js.map +1 -0
- package/dist/stage-scope-CQgCuf1w.cjs +46 -0
- package/dist/stage-scope-CQgCuf1w.cjs.map +1 -0
- package/dist/stage-scope-D7ZeNduT.js +23 -0
- package/dist/stage-scope-D7ZeNduT.js.map +1 -0
- package/dist/stage.cjs +12 -235
- package/dist/stage.d.cts +2 -116
- package/dist/stage.d.ts +2 -116
- package/dist/stage.js +3 -228
- package/dist/stamp.cjs +41 -35
- package/dist/stamp.cjs.map +1 -1
- package/dist/stamp.d.cts +25 -18
- package/dist/stamp.d.ts +25 -18
- package/dist/stamp.js +40 -37
- package/dist/stamp.js.map +1 -1
- package/dist/state-0K1I5cM4.d.cts +29 -0
- package/dist/state-0K1I5cM4.d.ts +29 -0
- package/dist/state-CbgxhwLA.js +62 -0
- package/dist/state-CbgxhwLA.js.map +1 -0
- package/dist/state-CsfZy_Eq.cjs +73 -0
- package/dist/state-CsfZy_Eq.cjs.map +1 -0
- package/dist/svg-shapes-Dm1yTR1b.js +40 -0
- package/dist/svg-shapes-Dm1yTR1b.js.map +1 -0
- package/dist/svg-shapes-zo0GNkc9.cjs +53 -0
- package/dist/svg-shapes-zo0GNkc9.cjs.map +1 -0
- package/dist/toolbar.cjs +297 -295
- package/dist/toolbar.cjs.map +1 -1
- package/dist/toolbar.d.cts +58 -68
- package/dist/toolbar.d.ts +67 -77
- package/dist/toolbar.js +271 -293
- package/dist/toolbar.js.map +1 -1
- package/dist/view-manager.cjs +34 -0
- package/dist/view-manager.cjs.map +1 -0
- package/dist/view-manager.d.cts +21 -0
- package/dist/view-manager.d.ts +21 -0
- package/dist/view-manager.js +23 -0
- package/dist/view-manager.js.map +1 -0
- package/package.json +41 -38
- package/dist/AnnotationListSnapshot-D3mFut1e-C_GMpK25.d.cts +0 -546
- package/dist/AnnotationListSnapshot-D3mFut1e-C_GMpK25.d.ts +0 -546
- package/dist/annotation-BCED_p9H.d.cts +0 -160
- package/dist/annotation-anchors-CetwM1g7.js +0 -15
- package/dist/annotation-anchors-CetwM1g7.js.map +0 -1
- package/dist/annotation-anchors-DuYRBJLl.cjs +0 -26
- package/dist/annotation-anchors-DuYRBJLl.cjs.map +0 -1
- package/dist/annotation-hooks-BB5ZhPpf.cjs +0 -16
- package/dist/annotation-hooks-BB5ZhPpf.cjs.map +0 -1
- package/dist/annotation-hooks-Df8EmzDO.js +0 -11
- package/dist/annotation-hooks-Df8EmzDO.js.map +0 -1
- package/dist/annotation-k38KDlIZ.d.ts +0 -160
- package/dist/annotation-menu.cjs +0 -64
- package/dist/annotation-menu.cjs.map +0 -1
- package/dist/annotation-menu.d.cts +0 -33
- package/dist/annotation-menu.d.ts +0 -33
- package/dist/annotation-menu.js +0 -48
- package/dist/annotation-menu.js.map +0 -1
- package/dist/annotation.cjs.map +0 -1
- package/dist/annotation.js.map +0 -1
- package/dist/form-BVumS7up.cjs +0 -926
- package/dist/form-BVumS7up.cjs.map +0 -1
- package/dist/form-CK3mp99m.js +0 -895
- package/dist/form-CK3mp99m.js.map +0 -1
- package/dist/rolldown-runtime-BBjsoOtd.js +0 -27
- package/dist/runtime-DE76hlnl.d.ts +0 -216
- package/dist/runtime-umHA_xHt.d.cts +0 -216
- package/dist/runtime.cjs.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/scrollbar.cjs +0 -241
- package/dist/scrollbar.cjs.map +0 -1
- package/dist/scrollbar.d.cts +0 -42
- package/dist/scrollbar.d.ts +0 -42
- package/dist/scrollbar.js +0 -213
- package/dist/scrollbar.js.map +0 -1
- package/dist/stage.cjs.map +0 -1
- package/dist/stage.js.map +0 -1
- package/dist/views.cjs +0 -35
- package/dist/views.cjs.map +0 -1
- package/dist/views.d.cts +0 -21
- package/dist/views.d.ts +0 -21
- package/dist/views.js +0 -27
- package/dist/views.js.map +0 -1
package/dist/interaction.d.ts
CHANGED
|
@@ -1,46 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as StateHook, t as SettingsHook } from "./state-0K1I5cM4.js";
|
|
2
|
+
import { EventHook } from "@embedpdf/core";
|
|
3
|
+
import { SvgCursorOptions, ToolCursorImage, ToolCursorSpec, ToolCursorSpec as ToolCursorSpec$1, createClickCounter, svgCursor, vibrationFeedback, wkFeedback } from "@embedpdf/web";
|
|
4
|
+
import { InteractionCapability } from "@embedpdf/plugin-interaction";
|
|
2
5
|
export * from "@embedpdf/plugin-interaction";
|
|
3
6
|
//#region src/interaction.d.ts
|
|
7
|
+
declare function PagePointerSource(): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
/** The interaction capability: switch tools, add your own, set their cursors. */
|
|
9
|
+
declare function useInteraction(): InteractionCapability;
|
|
10
|
+
/** Subscribe to one interaction event for the mounted lifetime: `useInteractionEvent((interaction) => interaction.onGestureStarted, handler)`. */
|
|
11
|
+
declare function useInteractionEvent<T>(select: (interaction: InteractionCapability) => EventHook<T>, handler: (event: T) => void): void;
|
|
4
12
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
13
|
+
* The tools' state: the active tool's id and every tool you can switch to
|
|
14
|
+
* (the page's State table, declared once in `interactionState`). Takes a
|
|
15
|
+
* selector, and re-renders only when what it returns changes. Without a
|
|
16
|
+
* document there is no active tool (`null`) and no tool.
|
|
8
17
|
*/
|
|
9
|
-
declare
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
tools: import("@embedpdf/plugin-interaction").Tool[];
|
|
16
|
-
};
|
|
17
|
-
/** One cursor slot: an SVG image ({@link SvgCursorOptions}) or a plain CSS
|
|
18
|
-
* cursor string ('crosshair', 'url(…) 4 4, copy'). */
|
|
19
|
-
type ToolCursorImage = SvgCursorOptions | string;
|
|
20
|
-
/** What {@link useToolCursor} installs: the tool to reskin + its keyword map.
|
|
21
|
-
* Apps build `svg` from the SAME icon their toolbar renders, so the cursor
|
|
22
|
-
* and the button can never drift apart. */
|
|
23
|
-
interface ToolCursorSpec {
|
|
24
|
-
toolId: string;
|
|
25
|
-
/** keyword → cursor: restyle the keywords this tool can show — its declared
|
|
26
|
-
* base ('crosshair', 'copy') and hover claims ('text' over text) — in the
|
|
27
|
-
* tool's identity. Keywords the map omits render as-is: a markup tool's
|
|
28
|
-
* 'default' base stays the bare arrow, a foreign 'move' claim drops the
|
|
29
|
-
* icon. An SVG value defaults its keyword `fallback` to the keyword it
|
|
30
|
-
* replaces. */
|
|
31
|
-
cursors: Record<string, ToolCursorImage>;
|
|
32
|
-
}
|
|
18
|
+
declare const useInteractionState: StateHook<{
|
|
19
|
+
activeToolId: import("@embedpdf/plugin-interaction").ToolId | null;
|
|
20
|
+
tools: readonly import("@embedpdf/plugin-interaction").Tool[];
|
|
21
|
+
}>;
|
|
22
|
+
/** The interaction settings (`defaultTool`, `tools`), with or without a document. Takes a selector. */
|
|
23
|
+
declare const useInteractionSettings: SettingsHook<import("@embedpdf/plugin-interaction").InteractionSettings>;
|
|
33
24
|
/**
|
|
34
|
-
* Give a tool
|
|
25
|
+
* Give a tool image cursors — the armed-tool indicator. The cursor is the only
|
|
35
26
|
* zero-latency pointer-locked pixel the platform has, and the hub already
|
|
36
27
|
* arbitrates it: unmapped hover claims beat it over annotations/text, page
|
|
37
28
|
* gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill
|
|
38
29
|
* controls) carries its own CSS cursor — so nothing chases the pointer in
|
|
39
30
|
* DOM. `null` installs nothing. A re-render with new content rebuilds the
|
|
40
31
|
* cursor (live recolor from tool defaults); unmount restores the tool's
|
|
41
|
-
* declared cursors.
|
|
32
|
+
* declared cursors. Without a document it installs nothing until one is ready.
|
|
42
33
|
*/
|
|
43
|
-
declare function useToolCursor(spec: ToolCursorSpec | null): void;
|
|
34
|
+
declare function useToolCursor(spec: ToolCursorSpec$1 | null): void;
|
|
44
35
|
//#endregion
|
|
45
|
-
export { PagePointerSource, ToolCursorImage, ToolCursorSpec, createClickCounter,
|
|
36
|
+
export { PagePointerSource, type SvgCursorOptions, type ToolCursorImage, type ToolCursorSpec, createClickCounter, svgCursor, useInteraction, useInteractionEvent, useInteractionSettings, useInteractionState, useToolCursor, vibrationFeedback, wkFeedback };
|
|
46
37
|
//# sourceMappingURL=interaction.d.ts.map
|
package/dist/interaction.js
CHANGED
|
@@ -1,94 +1,21 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import {
|
|
1
|
+
import { E as useSelector, S as useOptionalCapability, f as useCapability, p as useCapabilityEvent, w as usePage } from "./runtime-B6xcPsEb.js";
|
|
2
|
+
import { n as stateHook, t as settingsHook } from "./state-CbgxhwLA.js";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { attachPagePointer, createClickCounter, svgCursor, toolCursorsOf, vibrationFeedback, wkFeedback } from "@embedpdf/web";
|
|
4
5
|
import { useEffect, useRef } from "react";
|
|
5
|
-
import {
|
|
6
|
-
import { InteractionToken } from "@embedpdf/plugin-interaction";
|
|
6
|
+
import { InteractionToken } from "@embedpdf/plugin-interaction/contract/host";
|
|
7
|
+
import { InteractionToken as InteractionToken$1, interactionState } from "@embedpdf/plugin-interaction";
|
|
7
8
|
export * from "@embedpdf/plugin-interaction";
|
|
8
9
|
//#region src/interaction.tsx
|
|
9
|
-
const mods = (e) => ({
|
|
10
|
-
shift: e.shiftKey,
|
|
11
|
-
alt: e.altKey,
|
|
12
|
-
ctrl: e.ctrlKey,
|
|
13
|
-
meta: e.metaKey
|
|
14
|
-
});
|
|
15
|
-
/**
|
|
16
|
-
* Robust multi-click counter. `pointerdown.detail` is 0/1 in several browsers, so
|
|
17
|
-
* we count clicks ourselves from timing + proximity — the standard double/triple
|
|
18
|
-
* detection. Input normalization belongs in the adapter; the hub/handlers stay pure.
|
|
19
|
-
*/
|
|
20
|
-
function createClickCounter(maxGapMs = 400, maxDistPx = 6) {
|
|
21
|
-
let last = 0;
|
|
22
|
-
let lx = 0;
|
|
23
|
-
let ly = 0;
|
|
24
|
-
let count = 0;
|
|
25
|
-
return (now, x, y) => {
|
|
26
|
-
count = now - last <= maxGapMs && Math.hypot(x - lx, y - ly) <= maxDistPx ? count + 1 : 1;
|
|
27
|
-
last = now;
|
|
28
|
-
lx = x;
|
|
29
|
-
ly = y;
|
|
30
|
-
return count;
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
10
|
function PagePointerSource() {
|
|
34
11
|
const page = usePage();
|
|
35
12
|
const interaction = useCapability(InteractionToken);
|
|
36
|
-
const cursor = useSelector(InteractionToken, (
|
|
13
|
+
const cursor = useSelector(InteractionToken, (interaction) => interaction.getCursor());
|
|
37
14
|
const ref = useRef(null);
|
|
38
15
|
useEffect(() => {
|
|
39
|
-
const
|
|
40
|
-
if (!
|
|
41
|
-
|
|
42
|
-
const sample = (phase, e, clickCount = 1) => {
|
|
43
|
-
const r = el.getBoundingClientRect();
|
|
44
|
-
return {
|
|
45
|
-
phase,
|
|
46
|
-
viewport: {
|
|
47
|
-
x: e.clientX - r.left,
|
|
48
|
-
y: e.clientY - r.top
|
|
49
|
-
},
|
|
50
|
-
page: {
|
|
51
|
-
pon: page.pon,
|
|
52
|
-
point: page.toContentPoint(e.clientX, e.clientY),
|
|
53
|
-
scale: page.transform.viewScale,
|
|
54
|
-
rotation: page.transform.rotation,
|
|
55
|
-
zoom: page.transform.zoom
|
|
56
|
-
},
|
|
57
|
-
project: (pon) => pon === page.pon ? page.toContentPoint(e.clientX, e.clientY) : null,
|
|
58
|
-
modifiers: mods(e),
|
|
59
|
-
clickCount,
|
|
60
|
-
pointerType: e.pointerType || "mouse"
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
|
-
let dragging = false;
|
|
64
|
-
const down = (e) => {
|
|
65
|
-
if (e.button !== 0) return;
|
|
66
|
-
dragging = true;
|
|
67
|
-
interaction.dispatch(sample("down", e, clicks(Date.now(), e.clientX, e.clientY)));
|
|
68
|
-
};
|
|
69
|
-
const hover = (e) => {
|
|
70
|
-
if (dragging) return;
|
|
71
|
-
interaction.dispatch(sample("move", e));
|
|
72
|
-
};
|
|
73
|
-
const drag = (e) => {
|
|
74
|
-
if (!dragging) return;
|
|
75
|
-
interaction.dispatch(sample("move", e));
|
|
76
|
-
};
|
|
77
|
-
const up = (e) => {
|
|
78
|
-
if (!dragging) return;
|
|
79
|
-
dragging = false;
|
|
80
|
-
interaction.dispatch(sample("up", e));
|
|
81
|
-
};
|
|
82
|
-
el.addEventListener("pointerdown", down);
|
|
83
|
-
el.addEventListener("pointermove", hover);
|
|
84
|
-
window.addEventListener("pointermove", drag);
|
|
85
|
-
window.addEventListener("pointerup", up);
|
|
86
|
-
return () => {
|
|
87
|
-
el.removeEventListener("pointerdown", down);
|
|
88
|
-
el.removeEventListener("pointermove", hover);
|
|
89
|
-
window.removeEventListener("pointermove", drag);
|
|
90
|
-
window.removeEventListener("pointerup", up);
|
|
91
|
-
};
|
|
16
|
+
const element = ref.current;
|
|
17
|
+
if (!element) return;
|
|
18
|
+
return attachPagePointer(element, interaction, () => page);
|
|
92
19
|
}, [interaction, page]);
|
|
93
20
|
return /* @__PURE__ */ jsx("div", {
|
|
94
21
|
ref,
|
|
@@ -100,42 +27,46 @@ function PagePointerSource() {
|
|
|
100
27
|
}
|
|
101
28
|
});
|
|
102
29
|
}
|
|
103
|
-
/**
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
activeToolId: useSelector(InteractionToken, (c) => c.activeToolId()),
|
|
108
|
-
activate: interaction.activateTool,
|
|
109
|
-
tools: interaction.tools()
|
|
110
|
-
};
|
|
30
|
+
/** The interaction capability: switch tools, add your own, set their cursors. */
|
|
31
|
+
function useInteraction() {
|
|
32
|
+
return useCapability(InteractionToken$1);
|
|
111
33
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
34
|
+
/** Subscribe to one interaction event for the mounted lifetime: `useInteractionEvent((interaction) => interaction.onGestureStarted, handler)`. */
|
|
35
|
+
function useInteractionEvent(select, handler) {
|
|
36
|
+
useCapabilityEvent(InteractionToken$1, select, handler);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The tools' state: the active tool's id and every tool you can switch to
|
|
40
|
+
* (the page's State table, declared once in `interactionState`). Takes a
|
|
41
|
+
* selector, and re-renders only when what it returns changes. Without a
|
|
42
|
+
* document there is no active tool (`null`) and no tool.
|
|
43
|
+
*/
|
|
44
|
+
const useInteractionState = stateHook(interactionState);
|
|
45
|
+
/** The interaction settings (`defaultTool`, `tools`), with or without a document. Takes a selector. */
|
|
46
|
+
const useInteractionSettings = settingsHook(InteractionToken$1);
|
|
116
47
|
/**
|
|
117
|
-
* Give a tool
|
|
48
|
+
* Give a tool image cursors — the armed-tool indicator. The cursor is the only
|
|
118
49
|
* zero-latency pointer-locked pixel the platform has, and the hub already
|
|
119
50
|
* arbitrates it: unmapped hover claims beat it over annotations/text, page
|
|
120
51
|
* gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill
|
|
121
52
|
* controls) carries its own CSS cursor — so nothing chases the pointer in
|
|
122
53
|
* DOM. `null` installs nothing. A re-render with new content rebuilds the
|
|
123
54
|
* cursor (live recolor from tool defaults); unmount restores the tool's
|
|
124
|
-
* declared cursors.
|
|
55
|
+
* declared cursors. Without a document it installs nothing until one is ready.
|
|
125
56
|
*/
|
|
126
57
|
function useToolCursor(spec) {
|
|
127
|
-
const interaction =
|
|
58
|
+
const interaction = useOptionalCapability(InteractionToken);
|
|
128
59
|
const key = spec && JSON.stringify(spec);
|
|
129
60
|
const ref = useRef(spec);
|
|
130
61
|
ref.current = spec;
|
|
131
62
|
useEffect(() => {
|
|
132
|
-
const
|
|
133
|
-
if (!
|
|
134
|
-
interaction.setToolCursor(
|
|
135
|
-
return () => interaction.setToolCursor(
|
|
63
|
+
const cursorSpec = ref.current;
|
|
64
|
+
if (!cursorSpec || !interaction) return;
|
|
65
|
+
interaction.setToolCursor(cursorSpec.toolId, toolCursorsOf(cursorSpec.cursors));
|
|
66
|
+
return () => interaction.setToolCursor(cursorSpec.toolId, null);
|
|
136
67
|
}, [interaction, key]);
|
|
137
68
|
}
|
|
138
69
|
//#endregion
|
|
139
|
-
export { PagePointerSource, createClickCounter,
|
|
70
|
+
export { PagePointerSource, createClickCounter, svgCursor, useInteraction, useInteractionEvent, useInteractionSettings, useInteractionState, useToolCursor, vibrationFeedback, wkFeedback };
|
|
140
71
|
|
|
141
72
|
//# sourceMappingURL=interaction.js.map
|
package/dist/interaction.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interaction.js","names":[],"sources":["../src/interaction.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-interaction.\n *\n * <PagePointerSource> is the ONE pointer listener per page: it converts events to\n * page space via PageContext.toContentPoint and forwards normalized samples to the\n * hub. It binds only to the page context, so it works identically inside a\n * virtualized <Stage> page and a standalone <PageView>. Features never attach\n * their own pointer listeners — they register handlers with the hub.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-interaction';\n// The browser feedback providers live in @embedpdf/web (the plugin is\n// DOM-free); re-exported here so app code has one import for the feature.\nexport { vibrationFeedback, wkFeedback } from '@embedpdf/web';\nimport * as React from 'react';\nimport { useEffect, useRef } from 'react';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport type { Modifiers, PointerSample } from '@embedpdf/plugin-interaction';\nimport { svgCursor } from '@embedpdf/web';\nimport type { SvgCursorOptions } from '@embedpdf/web';\nimport { useCapability, usePage, useSelector } from './runtime';\n\nconst mods = (e: PointerEvent): Modifiers => ({\n shift: e.shiftKey,\n alt: e.altKey,\n ctrl: e.ctrlKey,\n meta: e.metaKey,\n});\n\n/**\n * Robust multi-click counter. `pointerdown.detail` is 0/1 in several browsers, so\n * we count clicks ourselves from timing + proximity — the standard double/triple\n * detection. Input normalization belongs in the adapter; the hub/handlers stay pure.\n */\nexport function createClickCounter(maxGapMs = 400, maxDistPx = 6) {\n let last = 0;\n let lx = 0;\n let ly = 0;\n let count = 0;\n return (now: number, x: number, y: number): number => {\n count = now - last <= maxGapMs && Math.hypot(x - lx, y - ly) <= maxDistPx ? count + 1 : 1;\n last = now;\n lx = x;\n ly = y;\n return count;\n };\n}\n\nexport function PagePointerSource() {\n const page = usePage();\n const interaction = useCapability(InteractionToken);\n const cursor = useSelector(InteractionToken, (c) => c.cursor());\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const clicks = createClickCounter();\n const sample = (\n phase: PointerSample['phase'],\n e: PointerEvent,\n clickCount = 1,\n ): PointerSample => {\n const r = el.getBoundingClientRect();\n return {\n phase,\n viewport: { x: e.clientX - r.left, y: e.clientY - r.top },\n // `scale`/`rotation`/`zoom` carry the same per-page environmental\n // context the Stage source resolves via `pageAt` — read off the page\n // transform so a standalone <PageView> drives handlers identically.\n page: {\n pon: page.pon,\n point: page.toContentPoint(e.clientX, e.clientY),\n scale: page.transform.viewScale,\n rotation: page.transform.rotation,\n zoom: page.transform.zoom,\n },\n // A per-page source can only project onto its OWN page — toContentPoint is\n // already unclamped (the drag listener lives on window), so a gesture\n // anchored here keeps tracking past the page bounds.\n project: (pon) => (pon === page.pon ? page.toContentPoint(e.clientX, e.clientY) : null),\n modifiers: mods(e),\n clickCount,\n pointerType: (e.pointerType || 'mouse') as PointerSample['pointerType'],\n };\n };\n let dragging = false;\n\n const down = (e: PointerEvent) => {\n if (e.button !== 0) return;\n dragging = true;\n interaction.dispatch(sample('down', e, clicks(Date.now(), e.clientX, e.clientY)));\n };\n // hover (no gesture): drive cursor feedback only — fires from the element\n const hover = (e: PointerEvent) => {\n if (dragging) return;\n interaction.dispatch(sample('move', e));\n };\n // active drag: track on window so it survives leaving the page bounds\n const drag = (e: PointerEvent) => {\n if (!dragging) return;\n interaction.dispatch(sample('move', e));\n };\n const up = (e: PointerEvent) => {\n if (!dragging) return;\n dragging = false;\n interaction.dispatch(sample('up', e));\n };\n\n el.addEventListener('pointerdown', down);\n el.addEventListener('pointermove', hover);\n window.addEventListener('pointermove', drag);\n window.addEventListener('pointerup', up);\n return () => {\n el.removeEventListener('pointerdown', down);\n el.removeEventListener('pointermove', hover);\n window.removeEventListener('pointermove', drag);\n window.removeEventListener('pointerup', up);\n };\n }, [interaction, page]);\n\n // Sits on top as the page's event surface; visual layers below use pointerEvents:none.\n return <div ref={ref} style={{ position: 'absolute', inset: 0, cursor, touchAction: 'none' }} />;\n}\n\n/** Read + switch the active tool (for a toolbar). */\nexport function useTool() {\n const interaction = useCapability(InteractionToken);\n const activeToolId = useSelector(InteractionToken, (c) => c.activeToolId());\n return {\n activeToolId,\n activate: interaction.activateTool,\n tools: interaction.tools(),\n };\n}\n\n/** One cursor slot: an SVG image ({@link SvgCursorOptions}) or a plain CSS\n * cursor string ('crosshair', 'url(…) 4 4, copy'). */\nexport type ToolCursorImage = SvgCursorOptions | string;\n\n/** What {@link useToolCursor} installs: the tool to reskin + its keyword map.\n * Apps build `svg` from the SAME icon their toolbar renders, so the cursor\n * and the button can never drift apart. */\nexport interface ToolCursorSpec {\n toolId: string;\n /** keyword → cursor: restyle the keywords this tool can show — its declared\n * base ('crosshair', 'copy') and hover claims ('text' over text) — in the\n * tool's identity. Keywords the map omits render as-is: a markup tool's\n * 'default' base stays the bare arrow, a foreign 'move' claim drops the\n * icon. An SVG value defaults its keyword `fallback` to the keyword it\n * replaces. */\n cursors: Record<string, ToolCursorImage>;\n}\n\nconst toCursor = (img: ToolCursorImage, fallback?: string): string =>\n typeof img === 'string'\n ? img\n : svgCursor(img.fallback === undefined && fallback ? { ...img, fallback } : img);\n\n/**\n * Give a tool IMAGE cursors — the armed-tool indicator. The cursor is the only\n * zero-latency pointer-locked pixel the platform has, and the hub already\n * arbitrates it: unmapped hover claims beat it over annotations/text, page\n * gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill\n * controls) carries its own CSS cursor — so nothing chases the pointer in\n * DOM. `null` installs nothing. A re-render with new content rebuilds the\n * cursor (live recolor from tool defaults); unmount restores the tool's\n * declared cursors.\n */\nexport function useToolCursor(spec: ToolCursorSpec | null): void {\n const interaction = useCapability(InteractionToken);\n // Key the effect by VALUE: specs are built inline in render, and a\n // fresh-but-identical object must not thrash the skin.\n const key = spec && JSON.stringify(spec);\n const ref = useRef(spec);\n ref.current = spec;\n useEffect(() => {\n const s = ref.current;\n if (!s) return;\n interaction.setToolCursor(\n s.toolId,\n Object.fromEntries(Object.entries(s.cursors).map(([k, v]) => [k, toCursor(v, k)])),\n );\n return () => interaction.setToolCursor(s.toolId, null);\n }, [interaction, key]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"interaction.js","names":["InteractionHostToken","InteractionToken"],"sources":["../src/interaction.tsx"],"sourcesContent":["/**\n * The React surface for @embedpdf/plugin-interaction.\n *\n * <PagePointerSource> is the one pointer listener per page: it converts events to\n * page space via PageContext.toPagePoint and forwards normalized samples to the\n * hub. It binds only to the page context, so it works identically inside a\n * virtualized <Stage> page and a standalone <PageView>. Features never attach\n * their own pointer listeners: they register handlers with the hub, and a tool\n * of your own brings its pointer methods (`registerTool`).\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-interaction';\n// The browser helpers live in @embedpdf/web (the plugin is DOM-free); they are\n// re-exported here so app code has one import for the feature.\nexport { createClickCounter, svgCursor, vibrationFeedback, wkFeedback } from '@embedpdf/web';\nexport type { SvgCursorOptions, ToolCursorImage, ToolCursorSpec } from '@embedpdf/web';\nimport * as React from 'react';\nimport { useEffect, useRef } from 'react';\nimport { InteractionToken as InteractionHostToken } from '@embedpdf/plugin-interaction/contract/host';\nimport { InteractionToken, interactionState } from '@embedpdf/plugin-interaction';\nimport type { InteractionCapability } from '@embedpdf/plugin-interaction';\nimport type { EventHook } from '@embedpdf/core';\nimport { attachPagePointer, toolCursorsOf } from '@embedpdf/web';\nimport type { ToolCursorSpec } from '@embedpdf/web';\nimport {\n useCapability,\n useCapabilityEvent,\n useOptionalCapability,\n usePage,\n useSelector,\n} from './runtime';\nimport { settingsHook, stateHook } from './state';\n\nexport function PagePointerSource() {\n const page = usePage();\n // The pointer source is a host of the hub (it dispatches samples).\n const interaction = useCapability(InteractionHostToken);\n const cursor = useSelector(InteractionHostToken, (interaction) => interaction.getCursor());\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const element = ref.current;\n if (!element) return;\n // The listener is `@embedpdf/web`'s, shared with every framework.\n return attachPagePointer(element, interaction, () => page);\n }, [interaction, page]);\n\n // Sits on top as the page's event surface; visual layers below use pointerEvents:none.\n return <div ref={ref} style={{ position: 'absolute', inset: 0, cursor, touchAction: 'none' }} />;\n}\n\n/** The interaction capability: switch tools, add your own, set their cursors. */\nexport function useInteraction(): InteractionCapability {\n return useCapability(InteractionToken);\n}\n\n/** Subscribe to one interaction event for the mounted lifetime: `useInteractionEvent((interaction) => interaction.onGestureStarted, handler)`. */\nexport function useInteractionEvent<T>(\n select: (interaction: InteractionCapability) => EventHook<T>,\n handler: (event: T) => void,\n): void {\n useCapabilityEvent(InteractionToken, select, handler);\n}\n\n/**\n * The tools' state: the active tool's id and every tool you can switch to\n * (the page's State table, declared once in `interactionState`). Takes a\n * selector, and re-renders only when what it returns changes. Without a\n * document there is no active tool (`null`) and no tool.\n */\nexport const useInteractionState = stateHook(interactionState);\n\n/** The interaction settings (`defaultTool`, `tools`), with or without a document. Takes a selector. */\nexport const useInteractionSettings = settingsHook(InteractionToken);\n\n/**\n * Give a tool image cursors — the armed-tool indicator. The cursor is the only\n * zero-latency pointer-locked pixel the platform has, and the hub already\n * arbitrates it: unmapped hover claims beat it over annotations/text, page\n * gaps fall back to the tool's `gapCursor`, and other UI (menus, form fill\n * controls) carries its own CSS cursor — so nothing chases the pointer in\n * DOM. `null` installs nothing. A re-render with new content rebuilds the\n * cursor (live recolor from tool defaults); unmount restores the tool's\n * declared cursors. Without a document it installs nothing until one is ready.\n */\nexport function useToolCursor(spec: ToolCursorSpec | null): void {\n const interaction = useOptionalCapability(InteractionHostToken);\n // Key the effect by value: specs are built inline in render, and a\n // fresh-but-identical object must not thrash the skin.\n const key = spec && JSON.stringify(spec);\n const ref = useRef(spec);\n ref.current = spec;\n useEffect(() => {\n const cursorSpec = ref.current;\n if (!cursorSpec || !interaction) return;\n interaction.setToolCursor(cursorSpec.toolId, toolCursorsOf(cursorSpec.cursors));\n return () => interaction.setToolCursor(cursorSpec.toolId, null);\n }, [interaction, key]);\n}\n"],"mappings":";;;;;;;;;AAkCA,SAAgB,oBAAoB;CAClC,MAAM,OAAO,QAAQ;CAErB,MAAM,cAAc,cAAcA,gBAAoB;CACtD,MAAM,SAAS,YAAYA,mBAAuB,gBAAgB,YAAY,UAAU,CAAC;CACzF,MAAM,MAAM,OAAuB,IAAI;CAEvC,gBAAgB;EACd,MAAM,UAAU,IAAI;EACpB,IAAI,CAAC,SAAS;EAEd,OAAO,kBAAkB,SAAS,mBAAmB,IAAI;CAC3D,GAAG,CAAC,aAAa,IAAI,CAAC;CAGtB,OAAO,oBAAC,OAAD;EAAU;EAAK,OAAO;GAAE,UAAU;GAAY,OAAO;GAAG;GAAQ,aAAa;EAAO;CAAI,CAAA;AACjG;;AAGA,SAAgB,iBAAwC;CACtD,OAAO,cAAcC,kBAAgB;AACvC;;AAGA,SAAgB,oBACd,QACA,SACM;CACN,mBAAmBA,oBAAkB,QAAQ,OAAO;AACtD;;;;;;;AAQA,MAAa,sBAAsB,UAAU,gBAAgB;;AAG7D,MAAa,yBAAyB,aAAaA,kBAAgB;;;;;;;;;;;AAYnE,SAAgB,cAAc,MAAmC;CAC/D,MAAM,cAAc,sBAAsBD,gBAAoB;CAG9D,MAAM,MAAM,QAAQ,KAAK,UAAU,IAAI;CACvC,MAAM,MAAM,OAAO,IAAI;CACvB,IAAI,UAAU;CACd,gBAAgB;EACd,MAAM,aAAa,IAAI;EACvB,IAAI,CAAC,cAAc,CAAC,aAAa;EACjC,YAAY,cAAc,WAAW,QAAQ,cAAc,WAAW,OAAO,CAAC;EAC9E,aAAa,YAAY,cAAc,WAAW,QAAQ,IAAI;CAChE,GAAG,CAAC,aAAa,GAAG,CAAC;AACvB"}
|
package/dist/link.cjs
CHANGED
|
@@ -1,71 +1,56 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rolldown_runtime = require("./rolldown-runtime-DDXJien1.cjs");
|
|
3
|
-
const require_runtime = require("./runtime.cjs");
|
|
3
|
+
const require_runtime = require("./runtime-fzpGv78v.cjs");
|
|
4
|
+
const require_stage_scope = require("./stage-scope-CQgCuf1w.cjs");
|
|
4
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
let _embedpdf_web = require("@embedpdf/web");
|
|
5
7
|
let react = require("react");
|
|
6
8
|
react = require_rolldown_runtime.__toESM(react, 1);
|
|
7
|
-
let
|
|
8
|
-
let _embedpdf_plugin_interaction_contract = require("@embedpdf/plugin-interaction/contract");
|
|
9
|
+
let _embedpdf_plugin_interaction_contract_host = require("@embedpdf/plugin-interaction/contract/host");
|
|
9
10
|
let _embedpdf_plugin_link = require("@embedpdf/plugin-link");
|
|
10
11
|
let _embedpdf_plugin_actions_contract = require("@embedpdf/plugin-actions/contract");
|
|
12
|
+
let _embedpdf_plugin_link_contract_host = require("@embedpdf/plugin-link/contract/host");
|
|
11
13
|
//#region src/link.tsx
|
|
12
14
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* anchors below, the selection menu's "Open link", the style editor's "Go to
|
|
18
|
-
* link" — goes through here, so none of them can drop the uri outcome again.
|
|
15
|
+
* A link's anchor. A press inside it must not reach the Stage's native listener (it would start
|
|
16
|
+
* the active tool's gesture, or end an edit as a click outside), so it stops with a native
|
|
17
|
+
* listener on the anchor itself: React's own `onPointerDown` runs from the root, after the Stage
|
|
18
|
+
* has already seen the press. The same listener sends the link's "mouse down" action.
|
|
19
19
|
*/
|
|
20
|
-
function
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
/** Content rect → view px (the page wrapper's own space) — the same idiom as
|
|
29
|
-
* the annotation and form layers: never re-derive `x * scale`. */
|
|
30
|
-
function boxOf(item, page) {
|
|
31
|
-
const tl = page.transform.toPixels({
|
|
32
|
-
x: item.rect.x,
|
|
33
|
-
y: item.rect.y
|
|
20
|
+
function LinkAnchor({ onPress, ...props }) {
|
|
21
|
+
const ref = (0, react.useRef)(null);
|
|
22
|
+
const press = (0, react.useRef)(onPress);
|
|
23
|
+
press.current = onPress;
|
|
24
|
+
(0, react.useEffect)(() => ref.current ? (0, _embedpdf_web.isolatePointerDown)(ref.current, () => press.current()) : void 0, []);
|
|
25
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
26
|
+
ref,
|
|
27
|
+
...props
|
|
34
28
|
});
|
|
35
|
-
const br = page.transform.toPixels({
|
|
36
|
-
x: item.rect.x + item.rect.width,
|
|
37
|
-
y: item.rect.y + item.rect.height
|
|
38
|
-
});
|
|
39
|
-
return {
|
|
40
|
-
left: tl.x,
|
|
41
|
-
top: tl.y,
|
|
42
|
-
width: br.x - tl.x,
|
|
43
|
-
height: br.y - tl.y
|
|
44
|
-
};
|
|
45
29
|
}
|
|
46
|
-
/**
|
|
47
|
-
*
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
30
|
+
/**
|
|
31
|
+
* Hand the link plugin this binding's website opener (`openExternalUri`)
|
|
32
|
+
* while the component is mounted: an allowed address opens in a new tab,
|
|
33
|
+
* anything else is refused and the plugin reports it.
|
|
34
|
+
*/
|
|
35
|
+
function useUriOpener() {
|
|
36
|
+
const host = require_runtime.useOptionalCapability(_embedpdf_plugin_link_contract_host.LinkToken);
|
|
37
|
+
(0, react.useEffect)(() => host?.registerUriOpener(_embedpdf_web.openExternalUri), [host]);
|
|
55
38
|
}
|
|
56
39
|
function LinkLayer({ renderLink } = {}) {
|
|
57
40
|
const page = require_runtime.usePage();
|
|
58
41
|
const link = require_runtime.useCapability(_embedpdf_plugin_link.LinkToken);
|
|
42
|
+
useUriOpener();
|
|
43
|
+
const stage = require_runtime.useOptionalCapability(require_stage_scope.useStageToken());
|
|
59
44
|
const actions = require_runtime.useOptionalCapability(_embedpdf_plugin_actions_contract.ActionsToken);
|
|
60
45
|
const linkPump = (0, react.useMemo)(() => actions ? (0, _embedpdf_plugin_actions_contract.createHoverPump)(actions.dispatch) : null, [actions]);
|
|
61
|
-
const items = require_runtime.useSelector(_embedpdf_plugin_link.LinkToken, (
|
|
62
|
-
const engaged = require_runtime.useSelector(
|
|
63
|
-
const editEnabled = require_runtime.useOptionalSelector(
|
|
46
|
+
const items = require_runtime.useSelector(_embedpdf_plugin_link.LinkToken, (link) => link.listLinks(page.ref), require_runtime.shallowArray);
|
|
47
|
+
const engaged = require_runtime.useSelector(_embedpdf_plugin_link_contract_host.LinkToken, (link) => link.isNavigationEngaged());
|
|
48
|
+
const editEnabled = require_runtime.useOptionalSelector(_embedpdf_plugin_interaction_contract_host.InteractionToken, (interaction) => interaction.activeToolEnables("annotation-edit"), false);
|
|
64
49
|
(0, react.useEffect)(() => {
|
|
65
|
-
link.
|
|
66
|
-
}, [link, page.
|
|
50
|
+
link.ensureLoaded(page.ref);
|
|
51
|
+
}, [link, page.ref]);
|
|
67
52
|
if (!engaged || !items.length) return null;
|
|
68
|
-
const visible =
|
|
53
|
+
const visible = (0, _embedpdf_web.navigableLinksOf)(items, editEnabled);
|
|
69
54
|
if (!visible.length) return null;
|
|
70
55
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
71
56
|
style: {
|
|
@@ -74,66 +59,33 @@ function LinkLayer({ renderLink } = {}) {
|
|
|
74
59
|
pointerEvents: "none"
|
|
75
60
|
},
|
|
76
61
|
children: visible.map((item) => {
|
|
77
|
-
const box =
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
activate: item.activate,
|
|
82
|
-
ref: item.ref,
|
|
83
|
-
pon: page.pon
|
|
84
|
-
};
|
|
85
|
-
const linkSource = item.ref ? {
|
|
86
|
-
kind: "link",
|
|
87
|
-
annotation: item.ref,
|
|
88
|
-
pon: page.pon
|
|
89
|
-
} : null;
|
|
90
|
-
const notify = (event) => {
|
|
91
|
-
if (!actions || !item.ref || !linkSource) return;
|
|
92
|
-
actions.dispatch({
|
|
93
|
-
scope: "annotation",
|
|
94
|
-
event,
|
|
95
|
-
ref: item.ref,
|
|
96
|
-
pon: page.pon,
|
|
97
|
-
source: linkSource
|
|
98
|
-
});
|
|
99
|
-
};
|
|
100
|
-
const native = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
62
|
+
const { box, href, label } = (0, _embedpdf_web.linkAnchorOf)(item, page.transform, (each) => link.getLabel(each));
|
|
63
|
+
const context = (0, _embedpdf_web.linkActivateContextOf)(item, page.ref, stage);
|
|
64
|
+
const native = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkAnchor, {
|
|
65
|
+
onPress: () => (0, _embedpdf_web.sendLinkEvent)(actions, item, page.ref, "mouseDown"),
|
|
101
66
|
href: href ?? void 0,
|
|
102
67
|
target: "_blank",
|
|
103
68
|
rel: "noopener noreferrer",
|
|
104
69
|
role: "link",
|
|
105
70
|
tabIndex: 0,
|
|
106
|
-
title:
|
|
107
|
-
"aria-label":
|
|
108
|
-
onClick: (
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
openLinkTarget(link, item.target, context);
|
|
71
|
+
title: label,
|
|
72
|
+
"aria-label": label,
|
|
73
|
+
onClick: (event) => {
|
|
74
|
+
if (href && (0, _embedpdf_web.isModifiedClick)(event)) return;
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
link.activate(item, context);
|
|
113
77
|
},
|
|
114
|
-
onKeyDown: (
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
78
|
+
onKeyDown: (event) => {
|
|
79
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
link.activate(item, context);
|
|
118
82
|
}
|
|
119
83
|
},
|
|
120
|
-
onPointerEnter: () =>
|
|
121
|
-
if (!linkPump || !item.ref || !item.hoverEvents) return;
|
|
122
|
-
linkPump.hover({
|
|
123
|
-
ref: item.ref,
|
|
124
|
-
pon: page.pon,
|
|
125
|
-
...linkSource ? { source: linkSource } : {},
|
|
126
|
-
events: item.hoverEvents
|
|
127
|
-
});
|
|
128
|
-
},
|
|
84
|
+
onPointerEnter: () => (0, _embedpdf_web.hoverLink)(linkPump, item, page.ref),
|
|
129
85
|
onPointerLeave: () => linkPump?.hover(null),
|
|
130
|
-
onPointerUp: () =>
|
|
131
|
-
onFocus: () =>
|
|
132
|
-
onBlur: () =>
|
|
133
|
-
onPointerDown: (e) => {
|
|
134
|
-
e.stopPropagation();
|
|
135
|
-
notify("mouseDown");
|
|
136
|
-
},
|
|
86
|
+
onPointerUp: () => (0, _embedpdf_web.sendLinkEvent)(actions, item, page.ref, "mouseUp"),
|
|
87
|
+
onFocus: () => (0, _embedpdf_web.sendLinkEvent)(actions, item, page.ref, "focus"),
|
|
88
|
+
onBlur: () => (0, _embedpdf_web.sendLinkEvent)(actions, item, page.ref, "blur"),
|
|
137
89
|
style: {
|
|
138
90
|
position: "absolute",
|
|
139
91
|
left: box.left,
|
|
@@ -144,21 +96,30 @@ function LinkLayer({ renderLink } = {}) {
|
|
|
144
96
|
pointerEvents: "auto"
|
|
145
97
|
}
|
|
146
98
|
});
|
|
147
|
-
const
|
|
148
|
-
item,
|
|
149
|
-
|
|
99
|
+
const drawn = renderLink?.({
|
|
100
|
+
link: item,
|
|
101
|
+
native
|
|
150
102
|
}) ?? native;
|
|
151
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children:
|
|
103
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: drawn }, item.id);
|
|
152
104
|
})
|
|
153
105
|
});
|
|
154
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The link capability (`listLinks`, `activate`, …) for app code. While a
|
|
109
|
+
* component uses it, `activate()` opens a website in a new tab.
|
|
110
|
+
*/
|
|
155
111
|
function useLink() {
|
|
112
|
+
useUriOpener();
|
|
156
113
|
return require_runtime.useCapability(_embedpdf_plugin_link.LinkToken);
|
|
157
114
|
}
|
|
115
|
+
/** Subscribe to one link event for the mounted lifetime: `useLinkEvent((link) => link.onActivated, handler)`. */
|
|
116
|
+
function useLinkEvent(select, handler) {
|
|
117
|
+
require_runtime.useCapabilityEvent(_embedpdf_plugin_link.LinkToken, select, handler);
|
|
118
|
+
}
|
|
158
119
|
//#endregion
|
|
159
120
|
exports.LinkLayer = LinkLayer;
|
|
160
|
-
exports.openLinkTarget = openLinkTarget;
|
|
161
121
|
exports.useLink = useLink;
|
|
122
|
+
exports.useLinkEvent = useLinkEvent;
|
|
162
123
|
Object.keys(_embedpdf_plugin_link).forEach(function(k) {
|
|
163
124
|
if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
164
125
|
enumerable: true,
|
package/dist/link.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link.cjs","names":["usePage","useCapability","LinkToken","useOptionalCapability","ActionsToken","useSelector","shallowArray","useOptionalSelector","InteractionToken","React"],"sources":["../src/link.tsx"],"sourcesContent":["/**\n * The React view of @embedpdf/plugin-link — the navigation plane's paint.\n *\n * One absolutely-positioned <a> per clickable link area, real anchors on\n * purpose: URIs get an `href` (middle-click, copy-link, status-bar preview\n * and keyboard focus for free), internal destinations activate through the\n * plugin (stage reveal). The layer stands down entirely while the active\n * tool doesn't enable `link-nav` — the annotation plane owns links then\n * (select / move / resize / retarget through the selection editor).\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-link';\nimport * as React from 'react';\nimport { useEffect, useMemo } from 'react';\nimport { ActionsToken, createHoverPump } from '@embedpdf/plugin-actions/contract';\nimport type {\n ActionSource,\n PdfAnnotationEventKind,\n} from '@embedpdf/plugin-actions/contract';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport {\n LinkToken,\n type LinkActivateContext,\n type LinkActivation,\n type LinkCapability,\n type LinkNavItem,\n type PdfLinkTarget,\n} from '@embedpdf/plugin-link';\nimport { sanitizeExternalUri } from '@embedpdf/web';\n\nimport {\n shallowArray,\n useCapability,\n useOptionalCapability,\n useOptionalSelector,\n usePage,\n useSelector,\n} from './runtime';\nimport type { PageContextValue } from './runtime';\n\n/**\n * Resolve a target through the plugin and PERFORM the `uri` outcome — the ONE\n * place in the codebase that turns a link target into a browser tab. The\n * plugin owns resolution (goto → stage reveal, policy, analytics) and stays\n * DOM-free; opening is this framework layer's job. Every click path — the nav\n * anchors below, the selection menu's \"Open link\", the style editor's \"Go to\n * link\" — goes through here, so none of them can drop the uri outcome again.\n */\nexport function openLinkTarget(\n link: LinkCapability,\n target: PdfLinkTarget,\n context?: LinkActivateContext,\n): LinkActivation {\n const activation = link.activate(target, context);\n if (activation.outcome === 'uri') {\n const href = sanitizeExternalUri(activation.uri);\n if (href && typeof window !== 'undefined') window.open(href, '_blank', 'noopener,noreferrer');\n }\n // 'dispatched': the action engine took it — the actions UI adapter owns any\n // URI open (the no-double-open rule); this opener must do NOTHING.\n return activation;\n}\n\n/** Content rect → view px (the page wrapper's own space) — the same idiom as\n * the annotation and form layers: never re-derive `x * scale`. */\nfunction boxOf(item: LinkNavItem, page: PageContextValue) {\n const tl = page.transform.toPixels({ x: item.rect.x, y: item.rect.y });\n const br = page.transform.toPixels({\n x: item.rect.x + item.rect.width,\n y: item.rect.y + item.rect.height,\n });\n return { left: tl.x, top: tl.y, width: br.x - tl.x, height: br.y - tl.y };\n}\n\n/** A human label for the hover tooltip. Apps with i18n render their own via\n * `renderLink`; this is the sensible default. */\nfunction labelOf(item: LinkNavItem): string {\n switch (item.target.kind) {\n case 'uri':\n return item.target.uri;\n case 'goto':\n return 'Go to destination';\n case 'named':\n return item.target.name;\n default:\n return 'Link';\n }\n}\n\nexport interface LinkLayerProps {\n /** Wrap or replace a link's native anchor (badging, custom tooltips). */\n renderLink?: (args: {\n item: LinkNavItem;\n nativeComponent: React.ReactNode;\n }) => React.ReactNode | undefined;\n}\n\nexport function LinkLayer({ renderLink }: LinkLayerProps = {}) {\n const page = usePage();\n const link = useCapability(LinkToken);\n // The link plane's /AA event feed: links are behavior-inert to the\n // annotation plane's hover feed while navigable (their pixels are THESE\n // anchors), so E/X/D/U/Fo/Bl can only fire from here. One shared pump per\n // layer — crossing layers still orders Exit before Enter because both\n // sides submit synchronously in DOM event order.\n const actions = useOptionalCapability(ActionsToken);\n const linkPump = useMemo(() => (actions ? createHoverPump(actions.dispatch) : null), [actions]);\n const items = useSelector(LinkToken, (c) => c.linksOn(page.pon), shallowArray);\n const engaged = useSelector(LinkToken, (c) => c.engaged());\n // One owner per pixel: an ATTACHED link is a property of its parent — while\n // the active tool can edit annotations, the parent owns those pixels and\n // the anchor stands down (select/move/resize work; no tooltip, no swallowed\n // pointer). Standalone document links navigate under any link-nav tool.\n const editEnabled = useOptionalSelector(\n InteractionToken,\n (c) => c.activeTool()?.enables.has('annotation-edit') ?? false,\n false,\n );\n\n useEffect(() => {\n link.ensurePage(page.pon);\n }, [link, page.pon]);\n\n // An authoring tool is active → the annotation plane owns links (they're\n // plain editable rects there); no nav anchors, no swallowed pointer events.\n if (!engaged || !items.length) return null;\n const visible = editEnabled ? items.filter((i) => !i.attached) : items;\n if (!visible.length) return null;\n\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {visible.map((item) => {\n const box = boxOf(item, page);\n // Real href ONLY for a chain-free sanitized external URI; blocked\n // schemes, internal targets, and chain-bearing trees (/Next after the\n // URI) activate through the plugin instead — a native navigation\n // would perform the first action and silently drop the rest.\n const chained = (item.activate?.root?.next.length ?? 0) > 0;\n const href =\n item.target.kind === 'uri' && !chained ? sanitizeExternalUri(item.target.uri) : null;\n const context: LinkActivateContext = { activate: item.activate, ref: item.ref, pon: page.pon };\n const linkSource: ActionSource | null = item.ref\n ? { kind: 'link', annotation: item.ref, pon: page.pon }\n : null;\n const notify = (event: Exclude<PdfAnnotationEventKind, 'cursorEnter' | 'cursorExit'>) => {\n if (!actions || !item.ref || !linkSource) return;\n void actions.dispatch({\n scope: 'annotation',\n event,\n ref: item.ref,\n pon: page.pon,\n source: linkSource,\n });\n };\n const native = (\n <a\n href={href ?? undefined}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n role=\"link\"\n tabIndex={0}\n title={labelOf(item)}\n aria-label={labelOf(item)}\n onClick={(e) => {\n // Modified clicks and middle-clicks on a real href keep their\n // native browser behaviour (new tab / copy link).\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n // A plain left-click on a real href: let the anchor be an\n // anchor — native navigation, target=_blank, status bar. Only\n // non-href targets (goto / named / blocked schemes) route\n // through the plugin, whose uri outcome the opener PERFORMS\n // (the old code preventDefault-ed AND dropped the outcome, so\n // clicking a URL link did nothing at all).\n if (href) return;\n e.preventDefault();\n openLinkTarget(link, item.target, context);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openLinkTarget(link, item.target, context);\n }\n }}\n onPointerEnter={() => {\n if (!linkPump || !item.ref || !item.hoverEvents) return;\n linkPump.hover({\n ref: item.ref,\n pon: page.pon,\n ...(linkSource ? { source: linkSource } : {}),\n events: item.hoverEvents,\n });\n }}\n onPointerLeave={() => linkPump?.hover(null)}\n onPointerUp={() => notify('mouseUp')}\n onFocus={() => notify('focus')}\n onBlur={() => notify('blur')}\n // Keep the hub out of it: a down inside the anchor must not reach\n // the Stage's native listener (same isolation idiom as FreeText).\n onPointerDown={(e) => {\n e.stopPropagation();\n notify('mouseDown');\n }}\n style={{\n position: 'absolute',\n left: box.left,\n top: box.top,\n width: box.width,\n height: box.height,\n cursor: 'pointer',\n pointerEvents: 'auto',\n }}\n />\n );\n const out = renderLink?.({ item, nativeComponent: native }) ?? native;\n return <React.Fragment key={item.id}>{out}</React.Fragment>;\n })}\n </div>\n );\n}\n\nexport function useLink() {\n return useCapability(LinkToken);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"link.cjs","names":["useOptionalCapability","LinkHostToken","openExternalUri","usePage","useCapability","LinkToken","useStageToken","ActionsToken","useSelector","shallowArray","useOptionalSelector","InteractionToken","React"],"sources":["../src/link.tsx"],"sourcesContent":["/**\n * The React view of @embedpdf/plugin-link — the navigation plane's paint.\n *\n * One absolutely-positioned <a> per clickable link area, real anchors on\n * purpose: URIs get an `href` (middle-click, copy-link, status-bar preview\n * and keyboard focus for free). A click or a key follows the link through\n * the plugin's `activate()`, so every way of following one fires\n * `onActivated`; the plugin opens websites through the opener this binding\n * registers. The layer stands down entirely while the active tool doesn't\n * enable `link-nav` — the annotation plane owns links then (select / move /\n * resize / retarget through the selection editor).\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-link';\nimport * as React from 'react';\nimport { useEffect, useMemo, useRef } from 'react';\nimport { ActionsToken, createHoverPump } from '@embedpdf/plugin-actions/contract';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract/host';\nimport { LinkToken, type Link, type LinkCapability } from '@embedpdf/plugin-link';\n// The layer paints anchors only while a navigation tool is active, and opens\n// websites for the plugin: both host facts.\nimport { LinkToken as LinkHostToken } from '@embedpdf/plugin-link/contract/host';\nimport {\n hoverLink,\n isolatePointerDown,\n isModifiedClick,\n linkActivateContextOf,\n linkAnchorOf,\n navigableLinksOf,\n openExternalUri,\n sendLinkEvent,\n} from '@embedpdf/web';\nimport type { EventHook } from '@embedpdf/core';\n\nimport {\n shallowArray,\n useCapability,\n useCapabilityEvent,\n useOptionalCapability,\n useOptionalSelector,\n usePage,\n useSelector,\n} from './runtime';\nimport { useStageToken } from './stage-scope';\n\n/**\n * A link's anchor. A press inside it must not reach the Stage's native listener (it would start\n * the active tool's gesture, or end an edit as a click outside), so it stops with a native\n * listener on the anchor itself: React's own `onPointerDown` runs from the root, after the Stage\n * has already seen the press. The same listener sends the link's \"mouse down\" action.\n */\nfunction LinkAnchor({\n onPress,\n ...props\n}: { onPress: () => void } & React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n const ref = useRef<HTMLAnchorElement>(null);\n const press = useRef(onPress);\n press.current = onPress;\n useEffect(\n () => (ref.current ? isolatePointerDown(ref.current, () => press.current()) : undefined),\n [],\n );\n return <a ref={ref} {...props} />;\n}\n\n/**\n * Hand the link plugin this binding's website opener (`openExternalUri`)\n * while the component is mounted: an allowed address opens in a new tab,\n * anything else is refused and the plugin reports it.\n */\nfunction useUriOpener(): void {\n const host = useOptionalCapability(LinkHostToken);\n useEffect(() => host?.registerUriOpener(openExternalUri), [host]);\n}\n\n/** What `renderLink` receives for each link. */\nexport interface LinkRenderProps {\n /** The link: its `id`, its `bounds` on the page and its `target`. */\n link: Link;\n /** The layer's own clickable area: wrap it to keep what a click does. */\n native: React.ReactNode;\n}\n\nexport interface LinkLayerProps {\n /**\n * Draw a link yourself, usually around `native`. Return `undefined` for a\n * link you don't want to change.\n */\n renderLink?: (props: LinkRenderProps) => React.ReactNode | undefined;\n}\n\nexport function LinkLayer({ renderLink }: LinkLayerProps = {}) {\n const page = usePage();\n const link = useCapability(LinkToken);\n useUriOpener();\n // A page destination moves the view this page is shown in.\n const stage = useOptionalCapability(useStageToken());\n // The link plane's /AA event feed: links are behavior-inert to the\n // annotation plane's hover feed while navigable (their pixels are these\n // anchors), so E/X/D/U/Fo/Bl can only fire from here. One shared pump per\n // layer — crossing layers still orders Exit before Enter because both\n // sides submit synchronously in DOM event order.\n const actions = useOptionalCapability(ActionsToken);\n const linkPump = useMemo(() => (actions ? createHoverPump(actions.dispatch) : null), [actions]);\n const items = useSelector(LinkToken, (link) => link.listLinks(page.ref), shallowArray);\n const engaged = useSelector(LinkHostToken, (link) => link.isNavigationEngaged());\n // One owner per pixel: an attached link is a property of its parent — while\n // the active tool can edit annotations, the parent owns those pixels and\n // the anchor stands down (select/move/resize work; no tooltip, no swallowed\n // pointer). Standalone document links navigate under any link-nav tool.\n const editEnabled = useOptionalSelector(\n InteractionToken,\n (interaction) => interaction.activeToolEnables('annotation-edit'),\n false,\n );\n\n useEffect(() => {\n void link.ensureLoaded(page.ref);\n }, [link, page.ref]);\n\n // An authoring tool is active → the annotation plane owns links (they're\n // plain editable rects there); no nav anchors, no swallowed pointer events.\n if (!engaged || !items.length) return null;\n const visible = navigableLinksOf(items, editEnabled);\n if (!visible.length) return null;\n\n return (\n <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>\n {visible.map((item) => {\n // Its box, its label, and a native href only for a website with nothing chained after it.\n const { box, href, label } = linkAnchorOf(item, page.transform, (each) =>\n link.getLabel(each),\n );\n const context = linkActivateContextOf(item, page.ref, stage);\n const native = (\n <LinkAnchor\n onPress={() => sendLinkEvent(actions, item, page.ref, 'mouseDown')}\n href={href ?? undefined}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n role=\"link\"\n tabIndex={0}\n title={label}\n aria-label={label}\n onClick={(event) => {\n // A modified click on a real href keeps the browser's own\n // behaviour (a background tab, a new window).\n if (href && isModifiedClick(event)) return;\n // Every other click follows the link through the plugin, which\n // opens a website through this binding's opener.\n event.preventDefault();\n link.activate(item, context);\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n link.activate(item, context);\n }\n }}\n onPointerEnter={() => hoverLink(linkPump, item, page.ref)}\n onPointerLeave={() => linkPump?.hover(null)}\n onPointerUp={() => sendLinkEvent(actions, item, page.ref, 'mouseUp')}\n onFocus={() => sendLinkEvent(actions, item, page.ref, 'focus')}\n onBlur={() => sendLinkEvent(actions, item, page.ref, 'blur')}\n style={{\n position: 'absolute',\n left: box.left,\n top: box.top,\n width: box.width,\n height: box.height,\n cursor: 'pointer',\n pointerEvents: 'auto',\n }}\n />\n );\n const drawn = renderLink?.({ link: item, native }) ?? native;\n return <React.Fragment key={item.id}>{drawn}</React.Fragment>;\n })}\n </div>\n );\n}\n\n/**\n * The link capability (`listLinks`, `activate`, …) for app code. While a\n * component uses it, `activate()` opens a website in a new tab.\n */\nexport function useLink(): LinkCapability {\n useUriOpener();\n return useCapability(LinkToken);\n}\n\n/** Subscribe to one link event for the mounted lifetime: `useLinkEvent((link) => link.onActivated, handler)`. */\nexport function useLinkEvent<T>(\n select: (link: LinkCapability) => EventHook<T>,\n handler: (event: T) => void,\n): void {\n useCapabilityEvent(LinkToken, select, handler);\n}\n"],"mappings":"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"}
|
package/dist/link.d.cts
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
|
+
import { EventHook } from "@embedpdf/core";
|
|
1
2
|
import * as React from "react";
|
|
2
|
-
import {
|
|
3
|
+
import { Link, LinkCapability } from "@embedpdf/plugin-link";
|
|
3
4
|
export * from "@embedpdf/plugin-link";
|
|
4
5
|
//#region src/link.d.ts
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
*/
|
|
13
|
-
declare function openLinkTarget(link: LinkCapability, target: PdfLinkTarget, context?: LinkActivateContext): LinkActivation;
|
|
6
|
+
/** What `renderLink` receives for each link. */
|
|
7
|
+
interface LinkRenderProps {
|
|
8
|
+
/** The link: its `id`, its `bounds` on the page and its `target`. */
|
|
9
|
+
link: Link;
|
|
10
|
+
/** The layer's own clickable area: wrap it to keep what a click does. */
|
|
11
|
+
native: React.ReactNode;
|
|
12
|
+
}
|
|
14
13
|
interface LinkLayerProps {
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Draw a link yourself, usually around `native`. Return `undefined` for a
|
|
16
|
+
* link you don't want to change.
|
|
17
|
+
*/
|
|
18
|
+
renderLink?: (props: LinkRenderProps) => React.ReactNode | undefined;
|
|
20
19
|
}
|
|
21
20
|
declare function LinkLayer({ renderLink }?: LinkLayerProps): import("react/jsx-runtime").JSX.Element | null;
|
|
21
|
+
/**
|
|
22
|
+
* The link capability (`listLinks`, `activate`, …) for app code. While a
|
|
23
|
+
* component uses it, `activate()` opens a website in a new tab.
|
|
24
|
+
*/
|
|
22
25
|
declare function useLink(): LinkCapability;
|
|
26
|
+
/** Subscribe to one link event for the mounted lifetime: `useLinkEvent((link) => link.onActivated, handler)`. */
|
|
27
|
+
declare function useLinkEvent<T>(select: (link: LinkCapability) => EventHook<T>, handler: (event: T) => void): void;
|
|
23
28
|
//#endregion
|
|
24
|
-
export { LinkLayer, LinkLayerProps,
|
|
29
|
+
export { LinkLayer, LinkLayerProps, LinkRenderProps, useLink, useLinkEvent };
|
|
25
30
|
//# sourceMappingURL=link.d.cts.map
|