@embedpdf/plugin-link 3.0.0-next.10 → 3.0.0-next.11

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.
@@ -0,0 +1,92 @@
1
+ import { PageObjectNumber } from "@embedpdf/core";
2
+ import { ActionDispatchResult } from "@embedpdf/plugin-actions/contract";
3
+ import { LinkNavItem } from "@embedpdf/plugin-annotation/contract";
4
+ import { AnnotationRef, PdfActionTree, PdfDestination, PdfLinkTarget } from "@embedpdf/engine-core/runtime";
5
+ //#region src/types.d.ts
6
+ /**
7
+ * What one activation did. `revealed` = the stage moved the camera;
8
+ * `destination` = no stage plugin — the embedder gets the explicit
9
+ * destination to handle; `uri` = opening is the FRAMEWORK layer's job (the
10
+ * plugin is DOM-free); `named` = a viewer verb (NextPage…) reported until
11
+ * stage verbs land; `reported` = read-only/executable-shaped targets
12
+ * (javascript / goto-remote / launch / unsupported) — surfaced, NEVER
13
+ * executed here (javascript belongs to the scripting orchestrator).
14
+ */
15
+ type LinkActivation = {
16
+ outcome: 'revealed';
17
+ } | {
18
+ outcome: 'destination';
19
+ destination: PdfDestination;
20
+ } | {
21
+ outcome: 'uri';
22
+ uri: string;
23
+ } | {
24
+ outcome: 'named';
25
+ name: string;
26
+ } | {
27
+ outcome: 'reported';
28
+ target: PdfLinkTarget;
29
+ } |
30
+ /** The action engine took the activation (full payload tree, mixed /Next
31
+ * chains included). `dispatch` is the in-flight result; the framework
32
+ * opener must do NOTHING for this outcome — the actions UI adapter owns
33
+ * any URI open (the no-double-open rule). */
34
+ {
35
+ outcome: 'dispatched';
36
+ dispatch: Promise<ActionDispatchResult>;
37
+ } | {
38
+ outcome: 'none';
39
+ };
40
+ /** Optional activation context: the payload-carrying /A tree (and its
41
+ * annotation identity) from the LinkNavItem, enabling action-engine
42
+ * delegation. Without it — or without the actions plugin — activation
43
+ * follows the classic root-projection path. */
44
+ interface LinkActivateContext {
45
+ activate?: PdfActionTree;
46
+ ref?: AnnotationRef;
47
+ pon?: PageObjectNumber;
48
+ }
49
+ interface LinkActivateEvent {
50
+ target: PdfLinkTarget;
51
+ activation: LinkActivation;
52
+ }
53
+ interface LinkPluginConfig {
54
+ /** Every activation (navigations AND reported targets) — analytics /
55
+ * custom handling. Called after the built-in handling ran. */
56
+ onActivate?: (event: LinkActivateEvent) => void;
57
+ }
58
+ /**
59
+ * The navigation plane of link annotations. Data comes from ONE of two
60
+ * sources, picked at init: the annotation plugin's folded model when that
61
+ * plugin is present (zero extra engine reads — it already lists every
62
+ * page), or the plugin's own engine reads in viewer-only deployments.
63
+ */
64
+ interface LinkCapability {
65
+ /** The clickable link areas of a page (content space, y-down). */
66
+ linksOn(pon: PageObjectNumber): LinkNavItem[];
67
+ /** Lazy-load a page's links (no-op when the annotation plugin owns the data). */
68
+ ensurePage(pon: PageObjectNumber): void;
69
+ /** Whether the navigation plane currently owns links: the active tool
70
+ * enables `link-nav` (pointer/pan/form-fill by default). While false —
71
+ * the link tool or any authoring tool is active — the annotation plane
72
+ * owns them and the LinkLayer stands down. */
73
+ engaged(): boolean;
74
+ /** THE one activation entry point (framework layers call it on click). */
75
+ activate(target: PdfLinkTarget, context?: LinkActivateContext): LinkActivation;
76
+ }
77
+ /** Engine-backed page cache (viewer-only deployments). */
78
+ interface LinkState {
79
+ pages: Record<number, LinkNavItem[]>;
80
+ }
81
+ type LinkAction = {
82
+ type: 'SET_PAGE';
83
+ pon: number;
84
+ items: LinkNavItem[];
85
+ } | {
86
+ type: 'DROP_PAGE';
87
+ pon: number;
88
+ };
89
+ declare const LinkToken: import("@embedpdf/core").CapabilityToken<LinkCapability>;
90
+ //#endregion
91
+ export { LinkCapability as a, LinkState as c, PdfLinkTarget as d, LinkActivation as i, LinkToken as l, LinkActivateContext as n, LinkNavItem as o, LinkActivateEvent as r, LinkPluginConfig as s, LinkAction as t, PdfDestination as u };
92
+ //# sourceMappingURL=contract-B8bEMeH8.d.ts.map
@@ -0,0 +1,11 @@
1
+ //#region src/types.ts
2
+ const LinkToken = (0, require("@embedpdf/core").createCapabilityToken)("link");
3
+ //#endregion
4
+ Object.defineProperty(exports, "LinkToken", {
5
+ enumerable: true,
6
+ get: function() {
7
+ return LinkToken;
8
+ }
9
+ });
10
+
11
+ //# sourceMappingURL=contract-DGKEPl11.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-DGKEPl11.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken, type PageObjectNumber } from '@embedpdf/core';\nimport type {\n AnnotationRef,\n PdfActionTree,\n PdfDestination,\n PdfLinkTarget,\n} from '@embedpdf/engine-core/runtime';\nimport type { ActionDispatchResult } from '@embedpdf/plugin-actions/contract';\nimport type { LinkNavItem } from '@embedpdf/plugin-annotation/contract';\n\nexport type { LinkNavItem };\n// The wire target vocabulary, re-exported so app code building link editors\n// needs only this package (the annotation-props precedent).\nexport type { PdfDestination, PdfLinkTarget };\n\n/**\n * What one activation did. `revealed` = the stage moved the camera;\n * `destination` = no stage plugin — the embedder gets the explicit\n * destination to handle; `uri` = opening is the FRAMEWORK layer's job (the\n * plugin is DOM-free); `named` = a viewer verb (NextPage…) reported until\n * stage verbs land; `reported` = read-only/executable-shaped targets\n * (javascript / goto-remote / launch / unsupported) — surfaced, NEVER\n * executed here (javascript belongs to the scripting orchestrator).\n */\nexport type LinkActivation =\n | { outcome: 'revealed' }\n | { outcome: 'destination'; destination: PdfDestination }\n | { outcome: 'uri'; uri: string }\n | { outcome: 'named'; name: string }\n | { outcome: 'reported'; target: PdfLinkTarget }\n /** The action engine took the activation (full payload tree, mixed /Next\n * chains included). `dispatch` is the in-flight result; the framework\n * opener must do NOTHING for this outcome — the actions UI adapter owns\n * any URI open (the no-double-open rule). */\n | { outcome: 'dispatched'; dispatch: Promise<ActionDispatchResult> }\n | { outcome: 'none' };\n\n/** Optional activation context: the payload-carrying /A tree (and its\n * annotation identity) from the LinkNavItem, enabling action-engine\n * delegation. Without it — or without the actions plugin — activation\n * follows the classic root-projection path. */\nexport interface LinkActivateContext {\n activate?: PdfActionTree;\n ref?: AnnotationRef;\n pon?: PageObjectNumber;\n}\n\nexport interface LinkActivateEvent {\n target: PdfLinkTarget;\n activation: LinkActivation;\n}\n\nexport interface LinkPluginConfig {\n /** Every activation (navigations AND reported targets) — analytics /\n * custom handling. Called after the built-in handling ran. */\n onActivate?: (event: LinkActivateEvent) => void;\n}\n\n/**\n * The navigation plane of link annotations. Data comes from ONE of two\n * sources, picked at init: the annotation plugin's folded model when that\n * plugin is present (zero extra engine reads — it already lists every\n * page), or the plugin's own engine reads in viewer-only deployments.\n */\nexport interface LinkCapability {\n /** The clickable link areas of a page (content space, y-down). */\n linksOn(pon: PageObjectNumber): LinkNavItem[];\n /** Lazy-load a page's links (no-op when the annotation plugin owns the data). */\n ensurePage(pon: PageObjectNumber): void;\n /** Whether the navigation plane currently owns links: the active tool\n * enables `link-nav` (pointer/pan/form-fill by default). While false —\n * the link tool or any authoring tool is active — the annotation plane\n * owns them and the LinkLayer stands down. */\n engaged(): boolean;\n /** THE one activation entry point (framework layers call it on click). */\n activate(target: PdfLinkTarget, context?: LinkActivateContext): LinkActivation;\n}\n\n/** Engine-backed page cache (viewer-only deployments). */\nexport interface LinkState {\n pages: Record<number, LinkNavItem[]>;\n}\n\nexport type LinkAction =\n | { type: 'SET_PAGE'; pon: number; items: LinkNavItem[] }\n | { type: 'DROP_PAGE'; pon: number };\n\nexport const LinkToken = createCapabilityToken<LinkCapability>('link');\n"],"mappings":";AAuFA,MAAa,aAAA,2BAAA,CAAA,CAAA,sBAAA,CAAkD,MAAM"}
@@ -0,0 +1,92 @@
1
+ import { PageObjectNumber } from "@embedpdf/core";
2
+ import { AnnotationRef, PdfActionTree, PdfDestination, PdfLinkTarget } from "@embedpdf/engine-core/runtime";
3
+ import { ActionDispatchResult } from "@embedpdf/plugin-actions/contract";
4
+ import { LinkNavItem } from "@embedpdf/plugin-annotation/contract";
5
+ //#region src/types.d.ts
6
+ /**
7
+ * What one activation did. `revealed` = the stage moved the camera;
8
+ * `destination` = no stage plugin — the embedder gets the explicit
9
+ * destination to handle; `uri` = opening is the FRAMEWORK layer's job (the
10
+ * plugin is DOM-free); `named` = a viewer verb (NextPage…) reported until
11
+ * stage verbs land; `reported` = read-only/executable-shaped targets
12
+ * (javascript / goto-remote / launch / unsupported) — surfaced, NEVER
13
+ * executed here (javascript belongs to the scripting orchestrator).
14
+ */
15
+ type LinkActivation = {
16
+ outcome: 'revealed';
17
+ } | {
18
+ outcome: 'destination';
19
+ destination: PdfDestination;
20
+ } | {
21
+ outcome: 'uri';
22
+ uri: string;
23
+ } | {
24
+ outcome: 'named';
25
+ name: string;
26
+ } | {
27
+ outcome: 'reported';
28
+ target: PdfLinkTarget;
29
+ } |
30
+ /** The action engine took the activation (full payload tree, mixed /Next
31
+ * chains included). `dispatch` is the in-flight result; the framework
32
+ * opener must do NOTHING for this outcome — the actions UI adapter owns
33
+ * any URI open (the no-double-open rule). */
34
+ {
35
+ outcome: 'dispatched';
36
+ dispatch: Promise<ActionDispatchResult>;
37
+ } | {
38
+ outcome: 'none';
39
+ };
40
+ /** Optional activation context: the payload-carrying /A tree (and its
41
+ * annotation identity) from the LinkNavItem, enabling action-engine
42
+ * delegation. Without it — or without the actions plugin — activation
43
+ * follows the classic root-projection path. */
44
+ interface LinkActivateContext {
45
+ activate?: PdfActionTree;
46
+ ref?: AnnotationRef;
47
+ pon?: PageObjectNumber;
48
+ }
49
+ interface LinkActivateEvent {
50
+ target: PdfLinkTarget;
51
+ activation: LinkActivation;
52
+ }
53
+ interface LinkPluginConfig {
54
+ /** Every activation (navigations AND reported targets) — analytics /
55
+ * custom handling. Called after the built-in handling ran. */
56
+ onActivate?: (event: LinkActivateEvent) => void;
57
+ }
58
+ /**
59
+ * The navigation plane of link annotations. Data comes from ONE of two
60
+ * sources, picked at init: the annotation plugin's folded model when that
61
+ * plugin is present (zero extra engine reads — it already lists every
62
+ * page), or the plugin's own engine reads in viewer-only deployments.
63
+ */
64
+ interface LinkCapability {
65
+ /** The clickable link areas of a page (content space, y-down). */
66
+ linksOn(pon: PageObjectNumber): LinkNavItem[];
67
+ /** Lazy-load a page's links (no-op when the annotation plugin owns the data). */
68
+ ensurePage(pon: PageObjectNumber): void;
69
+ /** Whether the navigation plane currently owns links: the active tool
70
+ * enables `link-nav` (pointer/pan/form-fill by default). While false —
71
+ * the link tool or any authoring tool is active — the annotation plane
72
+ * owns them and the LinkLayer stands down. */
73
+ engaged(): boolean;
74
+ /** THE one activation entry point (framework layers call it on click). */
75
+ activate(target: PdfLinkTarget, context?: LinkActivateContext): LinkActivation;
76
+ }
77
+ /** Engine-backed page cache (viewer-only deployments). */
78
+ interface LinkState {
79
+ pages: Record<number, LinkNavItem[]>;
80
+ }
81
+ type LinkAction = {
82
+ type: 'SET_PAGE';
83
+ pon: number;
84
+ items: LinkNavItem[];
85
+ } | {
86
+ type: 'DROP_PAGE';
87
+ pon: number;
88
+ };
89
+ declare const LinkToken: import("@embedpdf/core").CapabilityToken<LinkCapability>;
90
+ //#endregion
91
+ export { LinkCapability as a, LinkState as c, PdfLinkTarget as d, LinkActivation as i, LinkToken as l, LinkActivateContext as n, LinkNavItem as o, LinkActivateEvent as r, LinkPluginConfig as s, LinkAction as t, PdfDestination as u };
92
+ //# sourceMappingURL=contract-DO3vDsAV.d.cts.map
@@ -0,0 +1,7 @@
1
+ import { createCapabilityToken } from "@embedpdf/core";
2
+ //#region src/types.ts
3
+ const LinkToken = createCapabilityToken("link");
4
+ //#endregion
5
+ export { LinkToken as t };
6
+
7
+ //# sourceMappingURL=contract-HRggERzU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-HRggERzU.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken, type PageObjectNumber } from '@embedpdf/core';\nimport type {\n AnnotationRef,\n PdfActionTree,\n PdfDestination,\n PdfLinkTarget,\n} from '@embedpdf/engine-core/runtime';\nimport type { ActionDispatchResult } from '@embedpdf/plugin-actions/contract';\nimport type { LinkNavItem } from '@embedpdf/plugin-annotation/contract';\n\nexport type { LinkNavItem };\n// The wire target vocabulary, re-exported so app code building link editors\n// needs only this package (the annotation-props precedent).\nexport type { PdfDestination, PdfLinkTarget };\n\n/**\n * What one activation did. `revealed` = the stage moved the camera;\n * `destination` = no stage plugin — the embedder gets the explicit\n * destination to handle; `uri` = opening is the FRAMEWORK layer's job (the\n * plugin is DOM-free); `named` = a viewer verb (NextPage…) reported until\n * stage verbs land; `reported` = read-only/executable-shaped targets\n * (javascript / goto-remote / launch / unsupported) — surfaced, NEVER\n * executed here (javascript belongs to the scripting orchestrator).\n */\nexport type LinkActivation =\n | { outcome: 'revealed' }\n | { outcome: 'destination'; destination: PdfDestination }\n | { outcome: 'uri'; uri: string }\n | { outcome: 'named'; name: string }\n | { outcome: 'reported'; target: PdfLinkTarget }\n /** The action engine took the activation (full payload tree, mixed /Next\n * chains included). `dispatch` is the in-flight result; the framework\n * opener must do NOTHING for this outcome — the actions UI adapter owns\n * any URI open (the no-double-open rule). */\n | { outcome: 'dispatched'; dispatch: Promise<ActionDispatchResult> }\n | { outcome: 'none' };\n\n/** Optional activation context: the payload-carrying /A tree (and its\n * annotation identity) from the LinkNavItem, enabling action-engine\n * delegation. Without it — or without the actions plugin — activation\n * follows the classic root-projection path. */\nexport interface LinkActivateContext {\n activate?: PdfActionTree;\n ref?: AnnotationRef;\n pon?: PageObjectNumber;\n}\n\nexport interface LinkActivateEvent {\n target: PdfLinkTarget;\n activation: LinkActivation;\n}\n\nexport interface LinkPluginConfig {\n /** Every activation (navigations AND reported targets) — analytics /\n * custom handling. Called after the built-in handling ran. */\n onActivate?: (event: LinkActivateEvent) => void;\n}\n\n/**\n * The navigation plane of link annotations. Data comes from ONE of two\n * sources, picked at init: the annotation plugin's folded model when that\n * plugin is present (zero extra engine reads — it already lists every\n * page), or the plugin's own engine reads in viewer-only deployments.\n */\nexport interface LinkCapability {\n /** The clickable link areas of a page (content space, y-down). */\n linksOn(pon: PageObjectNumber): LinkNavItem[];\n /** Lazy-load a page's links (no-op when the annotation plugin owns the data). */\n ensurePage(pon: PageObjectNumber): void;\n /** Whether the navigation plane currently owns links: the active tool\n * enables `link-nav` (pointer/pan/form-fill by default). While false —\n * the link tool or any authoring tool is active — the annotation plane\n * owns them and the LinkLayer stands down. */\n engaged(): boolean;\n /** THE one activation entry point (framework layers call it on click). */\n activate(target: PdfLinkTarget, context?: LinkActivateContext): LinkActivation;\n}\n\n/** Engine-backed page cache (viewer-only deployments). */\nexport interface LinkState {\n pages: Record<number, LinkNavItem[]>;\n}\n\nexport type LinkAction =\n | { type: 'SET_PAGE'; pon: number; items: LinkNavItem[] }\n | { type: 'DROP_PAGE'; pon: number };\n\nexport const LinkToken = createCapabilityToken<LinkCapability>('link');\n"],"mappings":";;AAuFA,MAAa,YAAY,sBAAsC,MAAM"}
@@ -0,0 +1,3 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_contract = require("./contract-DGKEPl11.cjs");
3
+ exports.LinkToken = require_contract.LinkToken;
@@ -0,0 +1,2 @@
1
+ import { a as LinkCapability, c as LinkState, d as PdfLinkTarget, i as LinkActivation, l as LinkToken, n as LinkActivateContext, o as LinkNavItem, r as LinkActivateEvent, s as LinkPluginConfig, u as PdfDestination } from "./contract-DO3vDsAV.cjs";
2
+ export { type LinkActivateContext, type LinkActivateEvent, type LinkActivation, type LinkCapability, type LinkNavItem, type LinkPluginConfig, type LinkState, LinkToken, type PdfDestination, type PdfLinkTarget };
@@ -0,0 +1,2 @@
1
+ import { a as LinkCapability, c as LinkState, d as PdfLinkTarget, i as LinkActivation, l as LinkToken, n as LinkActivateContext, o as LinkNavItem, r as LinkActivateEvent, s as LinkPluginConfig, u as PdfDestination } from "./contract-B8bEMeH8.js";
2
+ export { type LinkActivateContext, type LinkActivateEvent, type LinkActivation, type LinkCapability, type LinkNavItem, type LinkPluginConfig, type LinkState, LinkToken, type PdfDestination, type PdfLinkTarget };
@@ -0,0 +1,2 @@
1
+ import { t as LinkToken } from "./contract-HRggERzU.js";
2
+ export { LinkToken };
package/dist/index.cjs CHANGED
@@ -1,9 +1,12 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_contract = require("./contract-DGKEPl11.cjs");
2
3
  let _embedpdf_core = require("@embedpdf/core");
3
- let _embedpdf_plugin_interaction = require("@embedpdf/plugin-interaction");
4
- let _embedpdf_plugin_stage = require("@embedpdf/plugin-stage");
5
- let _embedpdf_plugin_annotation = require("@embedpdf/plugin-annotation");
6
- let _embedpdf_plugin_annotation_internal = require("@embedpdf/plugin-annotation/internal");
4
+ let _embedpdf_plugin_actions_contract = require("@embedpdf/plugin-actions/contract");
5
+ let _embedpdf_plugin_annotation_contract = require("@embedpdf/plugin-annotation/contract");
6
+ let _embedpdf_plugin_annotation_contract_host = require("@embedpdf/plugin-annotation/contract/host");
7
+ let _embedpdf_plugin_interaction_contract = require("@embedpdf/plugin-interaction/contract");
8
+ let _embedpdf_plugin_stage_contract = require("@embedpdf/plugin-stage/contract");
9
+ let _embedpdf_plugin_stage_destination = require("@embedpdf/plugin-stage/destination");
7
10
  let _embedpdf_core_annotation = require("@embedpdf/core-annotation");
8
11
  //#region src/source.ts
9
12
  function loadLinksPage(io, pon) {
@@ -19,6 +22,12 @@ function loadLinksPage(io, pon) {
19
22
  id: dto.ref.kind === "objectNumber" ? `obj:${dto.ref.annotObjectNumber}` : `idx:${pon}:${dto.index}`,
20
23
  rect: (0, _embedpdf_core_annotation.pdfToContentRect)(dto.rect, crop),
21
24
  target: dto.target,
25
+ ...dto.actions?.activate ? { activate: dto.actions.activate } : {},
26
+ ...dto.actions?.cursorEnter?.root || dto.actions?.cursorExit?.root ? { hoverEvents: {
27
+ enter: Boolean(dto.actions?.cursorEnter?.root),
28
+ exit: Boolean(dto.actions?.cursorExit?.root)
29
+ } } : {},
30
+ ref: dto.ref,
22
31
  attached: dto.replyType === "group" && dto.inReplyTo != null
23
32
  });
24
33
  }
@@ -40,18 +49,31 @@ const EMPTY = [];
40
49
  * engine-backed page cache (see source.ts).
41
50
  */
42
51
  function createLinkCapability(ctx, config = {}) {
43
- const anno = () => ctx.tryGet(_embedpdf_plugin_annotation_internal.AnnotationToken);
44
- const activate = (target) => {
52
+ const anno = () => ctx.tryGet(_embedpdf_plugin_annotation_contract_host.AnnotationToken);
53
+ const activate = (target, context) => {
45
54
  const activation = (() => {
55
+ const actions = ctx.tryGet(_embedpdf_plugin_actions_contract.ActionsToken);
56
+ if (actions && context?.activate) return {
57
+ outcome: "dispatched",
58
+ dispatch: actions.execute(context.activate, {
59
+ origin: "user",
60
+ source: {
61
+ kind: "link",
62
+ annotation: context.ref,
63
+ pon: context.pon
64
+ },
65
+ event: { scope: "activate" }
66
+ })
67
+ };
46
68
  switch (target.kind) {
47
69
  case "goto": {
48
- const stage = ctx.tryGet(_embedpdf_plugin_stage.StageToken);
70
+ const stage = ctx.tryGet(_embedpdf_plugin_stage_contract.StageToken);
49
71
  const layout = ctx.document()?.pages.find((p) => p.pageObjectNumber === target.destination.pageObjectNumber);
50
72
  if (!stage || !layout) return {
51
73
  outcome: "destination",
52
74
  destination: target.destination
53
75
  };
54
- const { pageIndex, options } = (0, _embedpdf_plugin_stage.destinationToReveal)(target.destination, layout);
76
+ const { pageIndex, options } = (0, _embedpdf_plugin_stage_destination.destinationToReveal)(target.destination, layout);
55
77
  stage.reveal(pageIndex, {
56
78
  ...options,
57
79
  behavior: "smooth"
@@ -92,7 +114,7 @@ function createLinkCapability(ctx, config = {}) {
92
114
  }
93
115
  if (!(pon in ctx.getState().pages)) loadLinksPage(ctx, pon);
94
116
  },
95
- engaged: () => ctx.tryGet(_embedpdf_plugin_interaction.InteractionToken)?.activeTool()?.enables.has("link-nav") ?? false,
117
+ engaged: () => ctx.tryGet(_embedpdf_plugin_interaction_contract.InteractionToken)?.activeTool()?.enables.has("link-nav") ?? false,
96
118
  activate
97
119
  };
98
120
  }
@@ -109,7 +131,7 @@ function registerLinkEffects(ctx) {
109
131
  const doc = ctx.doc;
110
132
  if (!doc) return;
111
133
  const refetch = (pon) => {
112
- if (ctx.tryGet(_embedpdf_plugin_annotation_internal.AnnotationToken)) return;
134
+ if (ctx.tryGet(_embedpdf_plugin_annotation_contract_host.AnnotationToken)) return;
113
135
  if (pon in ctx.getState().pages) loadLinksPage(ctx, pon);
114
136
  };
115
137
  const unsubscribe = doc.events.subscribe((event) => {
@@ -157,9 +179,6 @@ function linkReducer(state, a) {
157
179
  }
158
180
  }
159
181
  //#endregion
160
- //#region src/types.ts
161
- const LinkToken = (0, _embedpdf_core.createCapabilityToken)("link");
162
- //#endregion
163
182
  //#region src/link.plugin.ts
164
183
  /**
165
184
  * The link plugin: the NAVIGATION plane. Document-scoped; requires only the
@@ -172,17 +191,21 @@ const LinkToken = (0, _embedpdf_core.createCapabilityToken)("link");
172
191
  */
173
192
  const linkPlugin = (config) => (0, _embedpdf_core.definePlugin)({
174
193
  id: "link",
175
- token: LinkToken,
194
+ token: require_contract.LinkToken,
176
195
  scope: "document",
177
- requires: [_embedpdf_plugin_interaction.InteractionToken],
178
- optional: [_embedpdf_plugin_stage.StageToken, _embedpdf_plugin_annotation.AnnotationToken],
196
+ requires: [_embedpdf_plugin_interaction_contract.InteractionToken],
197
+ optional: [
198
+ _embedpdf_plugin_stage_contract.StageToken,
199
+ _embedpdf_plugin_annotation_contract.AnnotationToken,
200
+ _embedpdf_plugin_actions_contract.ActionsToken
201
+ ],
179
202
  initialState: initialLinkState,
180
203
  reduce: linkReducer,
181
204
  capability: (ctx) => createLinkCapability(ctx, config),
182
205
  effects: registerLinkEffects,
183
206
  init: (ctx) => {
184
- const interaction = ctx.get(_embedpdf_plugin_interaction.InteractionToken);
185
- const annotation = ctx.tryGet(_embedpdf_plugin_annotation_internal.AnnotationToken);
207
+ const interaction = ctx.get(_embedpdf_plugin_interaction_contract.InteractionToken);
208
+ const annotation = ctx.tryGet(_embedpdf_plugin_annotation_contract_host.AnnotationToken);
186
209
  if (annotation) annotation.registerBehavior({
187
210
  id: "link-nav",
188
211
  matches: (a) => a.subtype === "link",
@@ -191,7 +214,7 @@ const linkPlugin = (config) => (0, _embedpdf_core.definePlugin)({
191
214
  }
192
215
  });
193
216
  //#endregion
194
- exports.LinkToken = LinkToken;
217
+ exports.LinkToken = require_contract.LinkToken;
195
218
  exports.linkPlugin = linkPlugin;
196
219
 
197
220
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["AnnotationHostToken","StageToken","InteractionToken","AnnotationHostToken","InteractionToken","StageToken","AnnotationToken","AnnotationHostToken"],"sources":["../src/source.ts","../src/capability.ts","../src/effects.ts","../src/reducer.ts","../src/types.ts","../src/link.plugin.ts"],"sourcesContent":["import type { DocumentHandle, PageLayout } from '@embedpdf/core';\nimport { pdfToContentRect } from '@embedpdf/core-annotation';\nimport type { LinkAction, LinkNavItem } from './types';\n\n/**\n * The ENGINE-BACKED link source, for viewer-only deployments (no annotation\n * plugin): read a page's annotation list and keep just the navigable links —\n * standalone AND attached children alike (both are raw link DTOs here).\n * Shared by the capability's lazy `ensurePage` and the effects module's\n * event-driven refetch; both PluginContext and EffectContext satisfy the\n * structural `io` slice.\n */\nexport interface LinkSourceIO {\n doc: DocumentHandle | null;\n document(): { pages: readonly PageLayout[] } | null;\n dispatch(action: LinkAction): void;\n}\n\nexport function loadLinksPage(io: LinkSourceIO, pon: number): void {\n const doc = io.doc;\n const crop = io.document()?.pages.find((p) => p.pageObjectNumber === pon)?.boxes.crop;\n if (!doc || !crop) return;\n doc\n .page(pon)\n .annotations.list()\n .then(\n (snap) => {\n const items: LinkNavItem[] = [];\n for (const dto of snap.annotations) {\n if (dto.subtype !== 'link' || dto.target == null) continue;\n if (dto.flags.hidden || dto.flags.noView) continue;\n items.push({\n id:\n dto.ref.kind === 'objectNumber'\n ? `obj:${dto.ref.annotObjectNumber}`\n : `idx:${pon}:${dto.index}`,\n rect: pdfToContentRect(dto.rect, crop),\n target: dto.target,\n // A `/RT /Group` child riding another annotation — labeled so the\n // nav layer can defer to editing (moot in viewer-only deployments,\n // but the item contract stays truthful either way).\n attached: dto.replyType === 'group' && dto.inReplyTo != null,\n });\n }\n io.dispatch({ type: 'SET_PAGE', pon, items });\n },\n () => {},\n );\n}\n","import type { PluginContext } from '@embedpdf/core';\nimport type { PdfLinkTarget } from '@embedpdf/engine-core/runtime';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport { destinationToReveal, StageToken } from '@embedpdf/plugin-stage';\n// The host lens (linkItemsOn) — same runtime token as the public one.\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/internal';\nimport { loadLinksPage } from './source';\nimport type {\n LinkAction,\n LinkActivation,\n LinkCapability,\n LinkNavItem,\n LinkPluginConfig,\n LinkState,\n} from './types';\n\nconst EMPTY: LinkNavItem[] = [];\n\n/**\n * The navigation plane's surface. Data source is decided PER CALL (lazily,\n * so plugin registration order can't matter): the annotation plugin's\n * folded model when present — its `linkItemsOn` lens is memoized by model\n * identity and already excludes hidden links — else this plugin's own\n * engine-backed page cache (see source.ts).\n */\nexport function createLinkCapability(\n ctx: PluginContext<LinkState, LinkAction>,\n config: LinkPluginConfig = {},\n): LinkCapability {\n const anno = () => ctx.tryGet(AnnotationHostToken);\n\n const activate = (target: PdfLinkTarget): LinkActivation => {\n const activation = ((): LinkActivation => {\n switch (target.kind) {\n case 'goto': {\n const stage = ctx.tryGet(StageToken);\n const layout = ctx\n .document()\n ?.pages.find((p) => p.pageObjectNumber === target.destination.pageObjectNumber);\n if (!stage || !layout) {\n // No camera to drive — hand the embedder the explicit destination.\n return { outcome: 'destination', destination: target.destination };\n }\n const { pageIndex, options } = destinationToReveal(target.destination, layout);\n stage.reveal(pageIndex, { ...options, behavior: 'smooth' });\n return { outcome: 'revealed' };\n }\n case 'uri':\n // Opening is the FRAMEWORK layer's job (a real <a href> after\n // sanitizing) — this plugin never touches the DOM.\n return { outcome: 'uri', uri: target.uri };\n case 'named':\n return { outcome: 'named', name: target.name };\n default:\n // javascript / goto-remote / launch / unsupported: reported, never\n // executed here. JavaScript activation belongs to the scripting\n // orchestrator once it lands — the chain already rides the\n // annotation's base `actions`.\n return { outcome: 'reported', target };\n }\n })();\n config.onActivate?.({ target, activation });\n return activation;\n };\n\n return {\n linksOn: (pon) => {\n const host = anno();\n if (host) return host.linkItemsOn(pon);\n return ctx.getState().pages[pon] ?? EMPTY;\n },\n ensurePage: (pon) => {\n const host = anno();\n if (host) {\n host.ensurePage(pon); // the folded model is the source; it lazy-loads\n return;\n }\n if (!(pon in ctx.getState().pages)) loadLinksPage(ctx, pon);\n },\n engaged: () => ctx.tryGet(InteractionToken)?.activeTool()?.enables.has('link-nav') ?? false,\n activate,\n };\n}\n","import type { DocumentEvent, EffectContext } from '@embedpdf/core';\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/internal';\nimport { loadLinksPage } from './source';\nimport type { LinkAction, LinkState } from './types';\n\n/**\n * Keeps the ENGINE-BACKED page cache honest in viewer-only deployments:\n * any annotation mutation on a cached page (own or remote — the cache has\n * no other refresh path, unlike the annotation plugin's optimistic model)\n * refetches that page's links. With the annotation plugin present the\n * folded model is the source and this effect never fires a fetch.\n */\nexport function registerLinkEffects(ctx: EffectContext<LinkState, LinkAction>): void {\n const doc = ctx.doc;\n if (!doc) return;\n\n const refetch = (pon: number): void => {\n if (ctx.tryGet(AnnotationHostToken)) return; // annotation model owns the data\n if (pon in ctx.getState().pages) loadLinksPage(ctx, pon);\n };\n\n const unsubscribe = doc.events.subscribe((event: DocumentEvent) => {\n switch (event.type) {\n case 'annotation.created':\n refetch(event.created.pageObjectNumber);\n break;\n case 'annotation.updated':\n refetch(event.updated.pageObjectNumber);\n break;\n case 'annotation.moved':\n if (event.moved.length) refetch(event.moved[0].pageObjectNumber);\n break;\n case 'annotation.deleted':\n refetch(event.pageObjectNumber);\n break;\n default:\n break;\n }\n });\n ctx.cleanup(unsubscribe);\n}\n","import type { LinkAction, LinkState } from './types';\n\nexport const initialLinkState = (): LinkState => ({ pages: {} });\n\n/** Pure. Engine-backed page cache only — the annotation-backed mode reads the\n * annotation plugin's model and never touches this state. */\nexport function linkReducer(state: LinkState, a: LinkAction): LinkState {\n switch (a.type) {\n case 'SET_PAGE':\n return { ...state, pages: { ...state.pages, [a.pon]: a.items } };\n case 'DROP_PAGE': {\n if (!(a.pon in state.pages)) return state;\n const pages = { ...state.pages };\n delete pages[a.pon];\n return { ...state, pages };\n }\n }\n}\n","import { createCapabilityToken, type PageObjectNumber } from '@embedpdf/core';\nimport type { PdfDestination, PdfLinkTarget } from '@embedpdf/engine-core/runtime';\nimport type { LinkNavItem } from '@embedpdf/plugin-annotation';\n\nexport type { LinkNavItem };\n// The wire target vocabulary, re-exported so app code building link editors\n// needs only this package (the annotation-props precedent).\nexport type { PdfDestination, PdfLinkTarget };\n\n/**\n * What one activation did. `revealed` = the stage moved the camera;\n * `destination` = no stage plugin — the embedder gets the explicit\n * destination to handle; `uri` = opening is the FRAMEWORK layer's job (the\n * plugin is DOM-free); `named` = a viewer verb (NextPage…) reported until\n * stage verbs land; `reported` = read-only/executable-shaped targets\n * (javascript / goto-remote / launch / unsupported) — surfaced, NEVER\n * executed here (javascript belongs to the scripting orchestrator).\n */\nexport type LinkActivation =\n | { outcome: 'revealed' }\n | { outcome: 'destination'; destination: PdfDestination }\n | { outcome: 'uri'; uri: string }\n | { outcome: 'named'; name: string }\n | { outcome: 'reported'; target: PdfLinkTarget }\n | { outcome: 'none' };\n\nexport interface LinkActivateEvent {\n target: PdfLinkTarget;\n activation: LinkActivation;\n}\n\nexport interface LinkPluginConfig {\n /** Every activation (navigations AND reported targets) — analytics /\n * custom handling. Called after the built-in handling ran. */\n onActivate?: (event: LinkActivateEvent) => void;\n}\n\n/**\n * The navigation plane of link annotations. Data comes from ONE of two\n * sources, picked at init: the annotation plugin's folded model when that\n * plugin is present (zero extra engine reads — it already lists every\n * page), or the plugin's own engine reads in viewer-only deployments.\n */\nexport interface LinkCapability {\n /** The clickable link areas of a page (content space, y-down). */\n linksOn(pon: PageObjectNumber): LinkNavItem[];\n /** Lazy-load a page's links (no-op when the annotation plugin owns the data). */\n ensurePage(pon: PageObjectNumber): void;\n /** Whether the navigation plane currently owns links: the active tool\n * enables `link-nav` (pointer/pan/form-fill by default). While false —\n * the link tool or any authoring tool is active — the annotation plane\n * owns them and the LinkLayer stands down. */\n engaged(): boolean;\n /** THE one activation entry point (framework layers call it on click). */\n activate(target: PdfLinkTarget): LinkActivation;\n}\n\n/** Engine-backed page cache (viewer-only deployments). */\nexport interface LinkState {\n pages: Record<number, LinkNavItem[]>;\n}\n\nexport type LinkAction =\n | { type: 'SET_PAGE'; pon: number; items: LinkNavItem[] }\n | { type: 'DROP_PAGE'; pon: number };\n\nexport const LinkToken = createCapabilityToken<LinkCapability>('link');\n","import { definePlugin } from '@embedpdf/core';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport { StageToken } from '@embedpdf/plugin-stage';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation';\n// Behavior registration lives on the HOST capability (framework/plugin\n// surface) — same runtime token, wider type. The form plugin's precedent.\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/internal';\n\nimport { createLinkCapability } from './capability';\nimport { registerLinkEffects } from './effects';\nimport { initialLinkState, linkReducer } from './reducer';\nimport { LinkToken } from './types';\nimport type { LinkAction, LinkCapability, LinkPluginConfig, LinkState } from './types';\n\n/**\n * The link plugin: the NAVIGATION plane. Document-scoped; requires only the\n * interaction hub. Navigation works with no annotation plugin at all —\n * links are read from the engine's link DTOs. When the annotation plugin IS\n * present, its folded model becomes the data source (no double fetch) and a\n * Behavior keeps links navigation-owned while a `link-nav` tool is active\n * (pointer/pan/form-fill by default) — the single-active-tool hub IS the\n * mode switch: grab the link tool and every link becomes an editable rect.\n */\nexport const linkPlugin = (config?: LinkPluginConfig) =>\n definePlugin<LinkState, LinkAction, LinkCapability>({\n id: 'link',\n token: LinkToken,\n scope: 'document',\n requires: [InteractionToken],\n optional: [StageToken, AnnotationToken],\n initialState: initialLinkState,\n reduce: linkReducer,\n capability: (ctx) => createLinkCapability(ctx, config),\n effects: registerLinkEffects,\n init: (ctx) => {\n const interaction = ctx.get(InteractionToken);\n const annotation = ctx.tryGet(AnnotationHostToken);\n if (annotation) {\n annotation.registerBehavior({\n id: 'link-nav',\n matches: (a) => a.subtype === 'link',\n engaged: () => interaction.activeTool()?.enables.has('link-nav') ?? false,\n });\n }\n },\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["AnnotationHostToken","ActionsToken","StageToken","InteractionToken","AnnotationHostToken","LinkToken","InteractionToken","StageToken","AnnotationToken","ActionsToken","AnnotationHostToken"],"sources":["../src/source.ts","../src/capability.ts","../src/effects.ts","../src/reducer.ts","../src/link.plugin.ts"],"sourcesContent":["import type { DocumentHandle, PageLayout } from '@embedpdf/core';\nimport { pdfToContentRect } from '@embedpdf/core-annotation';\nimport type { LinkAction, LinkNavItem } from './types';\n\n/**\n * The ENGINE-BACKED link source, for viewer-only deployments (no annotation\n * plugin): read a page's annotation list and keep just the navigable links —\n * standalone AND attached children alike (both are raw link DTOs here).\n * Shared by the capability's lazy `ensurePage` and the effects module's\n * event-driven refetch; both PluginContext and EffectContext satisfy the\n * structural `io` slice.\n */\nexport interface LinkSourceIO {\n doc: DocumentHandle | null;\n document(): { pages: readonly PageLayout[] } | null;\n dispatch(action: LinkAction): void;\n}\n\nexport function loadLinksPage(io: LinkSourceIO, pon: number): void {\n const doc = io.doc;\n const crop = io.document()?.pages.find((p) => p.pageObjectNumber === pon)?.boxes.crop;\n if (!doc || !crop) return;\n doc\n .page(pon)\n .annotations.list()\n .then(\n (snap) => {\n const items: LinkNavItem[] = [];\n for (const dto of snap.annotations) {\n if (dto.subtype !== 'link' || dto.target == null) continue;\n if (dto.flags.hidden || dto.flags.noView) continue;\n items.push({\n id:\n dto.ref.kind === 'objectNumber'\n ? `obj:${dto.ref.annotObjectNumber}`\n : `idx:${pon}:${dto.index}`,\n rect: pdfToContentRect(dto.rect, crop),\n target: dto.target,\n ...(dto.actions?.activate ? { activate: dto.actions.activate } : {}),\n ...(dto.actions?.cursorEnter?.root || dto.actions?.cursorExit?.root\n ? {\n hoverEvents: {\n enter: Boolean(dto.actions?.cursorEnter?.root),\n exit: Boolean(dto.actions?.cursorExit?.root),\n },\n }\n : {}),\n ref: dto.ref,\n // A `/RT /Group` child riding another annotation — labeled so the\n // nav layer can defer to editing (moot in viewer-only deployments,\n // but the item contract stays truthful either way).\n attached: dto.replyType === 'group' && dto.inReplyTo != null,\n });\n }\n io.dispatch({ type: 'SET_PAGE', pon, items });\n },\n () => {},\n );\n}\n","import type { PluginContext } from '@embedpdf/core';\nimport type { PdfLinkTarget } from '@embedpdf/engine-core/runtime';\nimport { ActionsToken } from '@embedpdf/plugin-actions/contract';\n// The host lens (linkItemsOn) — same runtime token as the public one.\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\nimport { destinationToReveal } from '@embedpdf/plugin-stage/destination';\nimport { loadLinksPage } from './source';\nimport type {\n LinkAction,\n LinkActivateContext,\n LinkActivation,\n LinkCapability,\n LinkNavItem,\n LinkPluginConfig,\n LinkState,\n} from './types';\n\nconst EMPTY: LinkNavItem[] = [];\n\n/**\n * The navigation plane's surface. Data source is decided PER CALL (lazily,\n * so plugin registration order can't matter): the annotation plugin's\n * folded model when present — its `linkItemsOn` lens is memoized by model\n * identity and already excludes hidden links — else this plugin's own\n * engine-backed page cache (see source.ts).\n */\nexport function createLinkCapability(\n ctx: PluginContext<LinkState, LinkAction>,\n config: LinkPluginConfig = {},\n): LinkCapability {\n const anno = () => ctx.tryGet(AnnotationHostToken);\n\n const activate = (target: PdfLinkTarget, context?: LinkActivateContext): LinkActivation => {\n const activation = ((): LinkActivation => {\n // The action engine takes precedence when it is installed and the item\n // carries its payload tree: named verbs execute, mixed /Next chains\n // run, and the dispatcher enforces policy + the incomplete-tree law.\n // Without it, the classic root-projection path below is the fallback.\n const actions = ctx.tryGet(ActionsToken);\n if (actions && context?.activate) {\n const dispatch = actions.execute(context.activate, {\n origin: 'user',\n source: { kind: 'link', annotation: context.ref, pon: context.pon },\n event: { scope: 'activate' },\n });\n return { outcome: 'dispatched', dispatch };\n }\n switch (target.kind) {\n case 'goto': {\n const stage = ctx.tryGet(StageToken);\n const layout = ctx\n .document()\n ?.pages.find((p) => p.pageObjectNumber === target.destination.pageObjectNumber);\n if (!stage || !layout) {\n // No camera to drive — hand the embedder the explicit destination.\n return { outcome: 'destination', destination: target.destination };\n }\n const { pageIndex, options } = destinationToReveal(target.destination, layout);\n stage.reveal(pageIndex, { ...options, behavior: 'smooth' });\n return { outcome: 'revealed' };\n }\n case 'uri':\n // Opening is the FRAMEWORK layer's job (a real <a href> after\n // sanitizing) — this plugin never touches the DOM.\n return { outcome: 'uri', uri: target.uri };\n case 'named':\n return { outcome: 'named', name: target.name };\n default:\n // javascript / goto-remote / launch / unsupported: reported, never\n // executed here. JavaScript activation belongs to the scripting\n // orchestrator once it lands — the chain already rides the\n // annotation's base `actions`.\n return { outcome: 'reported', target };\n }\n })();\n config.onActivate?.({ target, activation });\n return activation;\n };\n\n return {\n linksOn: (pon) => {\n const host = anno();\n if (host) return host.linkItemsOn(pon);\n return ctx.getState().pages[pon] ?? EMPTY;\n },\n ensurePage: (pon) => {\n const host = anno();\n if (host) {\n host.ensurePage(pon); // the folded model is the source; it lazy-loads\n return;\n }\n if (!(pon in ctx.getState().pages)) loadLinksPage(ctx, pon);\n },\n engaged: () => ctx.tryGet(InteractionToken)?.activeTool()?.enables.has('link-nav') ?? false,\n activate,\n };\n}\n","import type { DocumentEvent, EffectContext } from '@embedpdf/core';\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { loadLinksPage } from './source';\nimport type { LinkAction, LinkState } from './types';\n\n/**\n * Keeps the ENGINE-BACKED page cache honest in viewer-only deployments:\n * any annotation mutation on a cached page (own or remote — the cache has\n * no other refresh path, unlike the annotation plugin's optimistic model)\n * refetches that page's links. With the annotation plugin present the\n * folded model is the source and this effect never fires a fetch.\n */\nexport function registerLinkEffects(ctx: EffectContext<LinkState, LinkAction>): void {\n const doc = ctx.doc;\n if (!doc) return;\n\n const refetch = (pon: number): void => {\n if (ctx.tryGet(AnnotationHostToken)) return; // annotation model owns the data\n if (pon in ctx.getState().pages) loadLinksPage(ctx, pon);\n };\n\n const unsubscribe = doc.events.subscribe((event: DocumentEvent) => {\n switch (event.type) {\n case 'annotation.created':\n refetch(event.created.pageObjectNumber);\n break;\n case 'annotation.updated':\n refetch(event.updated.pageObjectNumber);\n break;\n case 'annotation.moved':\n if (event.moved.length) refetch(event.moved[0].pageObjectNumber);\n break;\n case 'annotation.deleted':\n refetch(event.pageObjectNumber);\n break;\n default:\n break;\n }\n });\n ctx.cleanup(unsubscribe);\n}\n","import type { LinkAction, LinkState } from './types';\n\nexport const initialLinkState = (): LinkState => ({ pages: {} });\n\n/** Pure. Engine-backed page cache only — the annotation-backed mode reads the\n * annotation plugin's model and never touches this state. */\nexport function linkReducer(state: LinkState, a: LinkAction): LinkState {\n switch (a.type) {\n case 'SET_PAGE':\n return { ...state, pages: { ...state.pages, [a.pon]: a.items } };\n case 'DROP_PAGE': {\n if (!(a.pon in state.pages)) return state;\n const pages = { ...state.pages };\n delete pages[a.pon];\n return { ...state, pages };\n }\n }\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { ActionsToken } from '@embedpdf/plugin-actions/contract';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/contract';\n// Behavior registration lives on the HOST capability (framework/plugin\n// surface) — same runtime token, wider type. The form plugin's precedent.\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\n\nimport { createLinkCapability } from './capability';\nimport { registerLinkEffects } from './effects';\nimport { initialLinkState, linkReducer } from './reducer';\nimport { LinkToken } from './types';\nimport type { LinkAction, LinkCapability, LinkPluginConfig, LinkState } from './types';\n\n/**\n * The link plugin: the NAVIGATION plane. Document-scoped; requires only the\n * interaction hub. Navigation works with no annotation plugin at all —\n * links are read from the engine's link DTOs. When the annotation plugin IS\n * present, its folded model becomes the data source (no double fetch) and a\n * Behavior keeps links navigation-owned while a `link-nav` tool is active\n * (pointer/pan/form-fill by default) — the single-active-tool hub IS the\n * mode switch: grab the link tool and every link becomes an editable rect.\n */\nexport const linkPlugin = (config?: LinkPluginConfig) =>\n definePlugin<LinkState, LinkAction, LinkCapability>({\n id: 'link',\n token: LinkToken,\n scope: 'document',\n requires: [InteractionToken],\n optional: [StageToken, AnnotationToken, ActionsToken],\n initialState: initialLinkState,\n reduce: linkReducer,\n capability: (ctx) => createLinkCapability(ctx, config),\n effects: registerLinkEffects,\n init: (ctx) => {\n const interaction = ctx.get(InteractionToken);\n const annotation = ctx.tryGet(AnnotationHostToken);\n if (annotation) {\n annotation.registerBehavior({\n id: 'link-nav',\n matches: (a) => a.subtype === 'link',\n engaged: () => interaction.activeTool()?.enables.has('link-nav') ?? false,\n });\n }\n },\n });\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,75 +1,4 @@
1
- import { PageObjectNumber } from "@embedpdf/core";
2
- import { PdfDestination, PdfLinkTarget } from "@embedpdf/engine-core/runtime";
3
- import { LinkNavItem } from "@embedpdf/plugin-annotation";
4
- //#region src/types.d.ts
5
- /**
6
- * What one activation did. `revealed` = the stage moved the camera;
7
- * `destination` = no stage plugin — the embedder gets the explicit
8
- * destination to handle; `uri` = opening is the FRAMEWORK layer's job (the
9
- * plugin is DOM-free); `named` = a viewer verb (NextPage…) reported until
10
- * stage verbs land; `reported` = read-only/executable-shaped targets
11
- * (javascript / goto-remote / launch / unsupported) — surfaced, NEVER
12
- * executed here (javascript belongs to the scripting orchestrator).
13
- */
14
- type LinkActivation = {
15
- outcome: 'revealed';
16
- } | {
17
- outcome: 'destination';
18
- destination: PdfDestination;
19
- } | {
20
- outcome: 'uri';
21
- uri: string;
22
- } | {
23
- outcome: 'named';
24
- name: string;
25
- } | {
26
- outcome: 'reported';
27
- target: PdfLinkTarget;
28
- } | {
29
- outcome: 'none';
30
- };
31
- interface LinkActivateEvent {
32
- target: PdfLinkTarget;
33
- activation: LinkActivation;
34
- }
35
- interface LinkPluginConfig {
36
- /** Every activation (navigations AND reported targets) — analytics /
37
- * custom handling. Called after the built-in handling ran. */
38
- onActivate?: (event: LinkActivateEvent) => void;
39
- }
40
- /**
41
- * The navigation plane of link annotations. Data comes from ONE of two
42
- * sources, picked at init: the annotation plugin's folded model when that
43
- * plugin is present (zero extra engine reads — it already lists every
44
- * page), or the plugin's own engine reads in viewer-only deployments.
45
- */
46
- interface LinkCapability {
47
- /** The clickable link areas of a page (content space, y-down). */
48
- linksOn(pon: PageObjectNumber): LinkNavItem[];
49
- /** Lazy-load a page's links (no-op when the annotation plugin owns the data). */
50
- ensurePage(pon: PageObjectNumber): void;
51
- /** Whether the navigation plane currently owns links: the active tool
52
- * enables `link-nav` (pointer/pan/form-fill by default). While false —
53
- * the link tool or any authoring tool is active — the annotation plane
54
- * owns them and the LinkLayer stands down. */
55
- engaged(): boolean;
56
- /** THE one activation entry point (framework layers call it on click). */
57
- activate(target: PdfLinkTarget): LinkActivation;
58
- }
59
- /** Engine-backed page cache (viewer-only deployments). */
60
- interface LinkState {
61
- pages: Record<number, LinkNavItem[]>;
62
- }
63
- type LinkAction = {
64
- type: 'SET_PAGE';
65
- pon: number;
66
- items: LinkNavItem[];
67
- } | {
68
- type: 'DROP_PAGE';
69
- pon: number;
70
- };
71
- declare const LinkToken: import("@embedpdf/core").CapabilityToken<LinkCapability>;
72
- //#endregion
1
+ import { a as LinkCapability, c as LinkState, d as PdfLinkTarget, i as LinkActivation, l as LinkToken, n as LinkActivateContext, o as LinkNavItem, r as LinkActivateEvent, s as LinkPluginConfig, t as LinkAction, u as PdfDestination } from "./contract-DO3vDsAV.cjs";
73
2
  //#region src/link.plugin.d.ts
74
3
  /**
75
4
  * The link plugin: the NAVIGATION plane. Document-scoped; requires only the
@@ -82,5 +11,5 @@ declare const LinkToken: import("@embedpdf/core").CapabilityToken<LinkCapability
82
11
  */
83
12
  declare const linkPlugin: (config?: LinkPluginConfig) => import("@embedpdf/core").PluginDef<LinkState, LinkAction, LinkCapability>;
84
13
  //#endregion
85
- export { type LinkActivateEvent, type LinkActivation, type LinkCapability, type LinkNavItem, type LinkPluginConfig, type LinkState, LinkToken, type PdfDestination, type PdfLinkTarget, linkPlugin };
14
+ export { type LinkActivateContext, type LinkActivateEvent, type LinkActivation, type LinkCapability, type LinkNavItem, type LinkPluginConfig, type LinkState, LinkToken, type PdfDestination, type PdfLinkTarget, linkPlugin };
86
15
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,75 +1,4 @@
1
- import { PageObjectNumber } from "@embedpdf/core";
2
- import { LinkNavItem } from "@embedpdf/plugin-annotation";
3
- import { PdfDestination, PdfLinkTarget } from "@embedpdf/engine-core/runtime";
4
- //#region src/types.d.ts
5
- /**
6
- * What one activation did. `revealed` = the stage moved the camera;
7
- * `destination` = no stage plugin — the embedder gets the explicit
8
- * destination to handle; `uri` = opening is the FRAMEWORK layer's job (the
9
- * plugin is DOM-free); `named` = a viewer verb (NextPage…) reported until
10
- * stage verbs land; `reported` = read-only/executable-shaped targets
11
- * (javascript / goto-remote / launch / unsupported) — surfaced, NEVER
12
- * executed here (javascript belongs to the scripting orchestrator).
13
- */
14
- type LinkActivation = {
15
- outcome: 'revealed';
16
- } | {
17
- outcome: 'destination';
18
- destination: PdfDestination;
19
- } | {
20
- outcome: 'uri';
21
- uri: string;
22
- } | {
23
- outcome: 'named';
24
- name: string;
25
- } | {
26
- outcome: 'reported';
27
- target: PdfLinkTarget;
28
- } | {
29
- outcome: 'none';
30
- };
31
- interface LinkActivateEvent {
32
- target: PdfLinkTarget;
33
- activation: LinkActivation;
34
- }
35
- interface LinkPluginConfig {
36
- /** Every activation (navigations AND reported targets) — analytics /
37
- * custom handling. Called after the built-in handling ran. */
38
- onActivate?: (event: LinkActivateEvent) => void;
39
- }
40
- /**
41
- * The navigation plane of link annotations. Data comes from ONE of two
42
- * sources, picked at init: the annotation plugin's folded model when that
43
- * plugin is present (zero extra engine reads — it already lists every
44
- * page), or the plugin's own engine reads in viewer-only deployments.
45
- */
46
- interface LinkCapability {
47
- /** The clickable link areas of a page (content space, y-down). */
48
- linksOn(pon: PageObjectNumber): LinkNavItem[];
49
- /** Lazy-load a page's links (no-op when the annotation plugin owns the data). */
50
- ensurePage(pon: PageObjectNumber): void;
51
- /** Whether the navigation plane currently owns links: the active tool
52
- * enables `link-nav` (pointer/pan/form-fill by default). While false —
53
- * the link tool or any authoring tool is active — the annotation plane
54
- * owns them and the LinkLayer stands down. */
55
- engaged(): boolean;
56
- /** THE one activation entry point (framework layers call it on click). */
57
- activate(target: PdfLinkTarget): LinkActivation;
58
- }
59
- /** Engine-backed page cache (viewer-only deployments). */
60
- interface LinkState {
61
- pages: Record<number, LinkNavItem[]>;
62
- }
63
- type LinkAction = {
64
- type: 'SET_PAGE';
65
- pon: number;
66
- items: LinkNavItem[];
67
- } | {
68
- type: 'DROP_PAGE';
69
- pon: number;
70
- };
71
- declare const LinkToken: import("@embedpdf/core").CapabilityToken<LinkCapability>;
72
- //#endregion
1
+ import { a as LinkCapability, c as LinkState, d as PdfLinkTarget, i as LinkActivation, l as LinkToken, n as LinkActivateContext, o as LinkNavItem, r as LinkActivateEvent, s as LinkPluginConfig, t as LinkAction, u as PdfDestination } from "./contract-B8bEMeH8.js";
73
2
  //#region src/link.plugin.d.ts
74
3
  /**
75
4
  * The link plugin: the NAVIGATION plane. Document-scoped; requires only the
@@ -82,5 +11,5 @@ declare const LinkToken: import("@embedpdf/core").CapabilityToken<LinkCapability
82
11
  */
83
12
  declare const linkPlugin: (config?: LinkPluginConfig) => import("@embedpdf/core").PluginDef<LinkState, LinkAction, LinkCapability>;
84
13
  //#endregion
85
- export { type LinkActivateEvent, type LinkActivation, type LinkCapability, type LinkNavItem, type LinkPluginConfig, type LinkState, LinkToken, type PdfDestination, type PdfLinkTarget, linkPlugin };
14
+ export { type LinkActivateContext, type LinkActivateEvent, type LinkActivation, type LinkCapability, type LinkNavItem, type LinkPluginConfig, type LinkState, LinkToken, type PdfDestination, type PdfLinkTarget, linkPlugin };
86
15
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,8 +1,11 @@
1
- import { createCapabilityToken, definePlugin } from "@embedpdf/core";
2
- import { InteractionToken } from "@embedpdf/plugin-interaction";
3
- import { StageToken, destinationToReveal } from "@embedpdf/plugin-stage";
4
- import { AnnotationToken } from "@embedpdf/plugin-annotation";
5
- import { AnnotationToken as AnnotationToken$1 } from "@embedpdf/plugin-annotation/internal";
1
+ import { t as LinkToken } from "./contract-HRggERzU.js";
2
+ import { definePlugin } from "@embedpdf/core";
3
+ import { ActionsToken } from "@embedpdf/plugin-actions/contract";
4
+ import { AnnotationToken } from "@embedpdf/plugin-annotation/contract";
5
+ import { AnnotationToken as AnnotationToken$1 } from "@embedpdf/plugin-annotation/contract/host";
6
+ import { InteractionToken } from "@embedpdf/plugin-interaction/contract";
7
+ import { StageToken } from "@embedpdf/plugin-stage/contract";
8
+ import { destinationToReveal } from "@embedpdf/plugin-stage/destination";
6
9
  import { pdfToContentRect } from "@embedpdf/core-annotation";
7
10
  //#region src/source.ts
8
11
  function loadLinksPage(io, pon) {
@@ -18,6 +21,12 @@ function loadLinksPage(io, pon) {
18
21
  id: dto.ref.kind === "objectNumber" ? `obj:${dto.ref.annotObjectNumber}` : `idx:${pon}:${dto.index}`,
19
22
  rect: pdfToContentRect(dto.rect, crop),
20
23
  target: dto.target,
24
+ ...dto.actions?.activate ? { activate: dto.actions.activate } : {},
25
+ ...dto.actions?.cursorEnter?.root || dto.actions?.cursorExit?.root ? { hoverEvents: {
26
+ enter: Boolean(dto.actions?.cursorEnter?.root),
27
+ exit: Boolean(dto.actions?.cursorExit?.root)
28
+ } } : {},
29
+ ref: dto.ref,
21
30
  attached: dto.replyType === "group" && dto.inReplyTo != null
22
31
  });
23
32
  }
@@ -40,8 +49,21 @@ const EMPTY = [];
40
49
  */
41
50
  function createLinkCapability(ctx, config = {}) {
42
51
  const anno = () => ctx.tryGet(AnnotationToken$1);
43
- const activate = (target) => {
52
+ const activate = (target, context) => {
44
53
  const activation = (() => {
54
+ const actions = ctx.tryGet(ActionsToken);
55
+ if (actions && context?.activate) return {
56
+ outcome: "dispatched",
57
+ dispatch: actions.execute(context.activate, {
58
+ origin: "user",
59
+ source: {
60
+ kind: "link",
61
+ annotation: context.ref,
62
+ pon: context.pon
63
+ },
64
+ event: { scope: "activate" }
65
+ })
66
+ };
45
67
  switch (target.kind) {
46
68
  case "goto": {
47
69
  const stage = ctx.tryGet(StageToken);
@@ -156,9 +178,6 @@ function linkReducer(state, a) {
156
178
  }
157
179
  }
158
180
  //#endregion
159
- //#region src/types.ts
160
- const LinkToken = createCapabilityToken("link");
161
- //#endregion
162
181
  //#region src/link.plugin.ts
163
182
  /**
164
183
  * The link plugin: the NAVIGATION plane. Document-scoped; requires only the
@@ -174,7 +193,11 @@ const linkPlugin = (config) => definePlugin({
174
193
  token: LinkToken,
175
194
  scope: "document",
176
195
  requires: [InteractionToken],
177
- optional: [StageToken, AnnotationToken],
196
+ optional: [
197
+ StageToken,
198
+ AnnotationToken,
199
+ ActionsToken
200
+ ],
178
201
  initialState: initialLinkState,
179
202
  reduce: linkReducer,
180
203
  capability: (ctx) => createLinkCapability(ctx, config),
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["AnnotationHostToken","AnnotationHostToken","AnnotationHostToken"],"sources":["../src/source.ts","../src/capability.ts","../src/effects.ts","../src/reducer.ts","../src/types.ts","../src/link.plugin.ts"],"sourcesContent":["import type { DocumentHandle, PageLayout } from '@embedpdf/core';\nimport { pdfToContentRect } from '@embedpdf/core-annotation';\nimport type { LinkAction, LinkNavItem } from './types';\n\n/**\n * The ENGINE-BACKED link source, for viewer-only deployments (no annotation\n * plugin): read a page's annotation list and keep just the navigable links —\n * standalone AND attached children alike (both are raw link DTOs here).\n * Shared by the capability's lazy `ensurePage` and the effects module's\n * event-driven refetch; both PluginContext and EffectContext satisfy the\n * structural `io` slice.\n */\nexport interface LinkSourceIO {\n doc: DocumentHandle | null;\n document(): { pages: readonly PageLayout[] } | null;\n dispatch(action: LinkAction): void;\n}\n\nexport function loadLinksPage(io: LinkSourceIO, pon: number): void {\n const doc = io.doc;\n const crop = io.document()?.pages.find((p) => p.pageObjectNumber === pon)?.boxes.crop;\n if (!doc || !crop) return;\n doc\n .page(pon)\n .annotations.list()\n .then(\n (snap) => {\n const items: LinkNavItem[] = [];\n for (const dto of snap.annotations) {\n if (dto.subtype !== 'link' || dto.target == null) continue;\n if (dto.flags.hidden || dto.flags.noView) continue;\n items.push({\n id:\n dto.ref.kind === 'objectNumber'\n ? `obj:${dto.ref.annotObjectNumber}`\n : `idx:${pon}:${dto.index}`,\n rect: pdfToContentRect(dto.rect, crop),\n target: dto.target,\n // A `/RT /Group` child riding another annotation — labeled so the\n // nav layer can defer to editing (moot in viewer-only deployments,\n // but the item contract stays truthful either way).\n attached: dto.replyType === 'group' && dto.inReplyTo != null,\n });\n }\n io.dispatch({ type: 'SET_PAGE', pon, items });\n },\n () => {},\n );\n}\n","import type { PluginContext } from '@embedpdf/core';\nimport type { PdfLinkTarget } from '@embedpdf/engine-core/runtime';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport { destinationToReveal, StageToken } from '@embedpdf/plugin-stage';\n// The host lens (linkItemsOn) — same runtime token as the public one.\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/internal';\nimport { loadLinksPage } from './source';\nimport type {\n LinkAction,\n LinkActivation,\n LinkCapability,\n LinkNavItem,\n LinkPluginConfig,\n LinkState,\n} from './types';\n\nconst EMPTY: LinkNavItem[] = [];\n\n/**\n * The navigation plane's surface. Data source is decided PER CALL (lazily,\n * so plugin registration order can't matter): the annotation plugin's\n * folded model when present — its `linkItemsOn` lens is memoized by model\n * identity and already excludes hidden links — else this plugin's own\n * engine-backed page cache (see source.ts).\n */\nexport function createLinkCapability(\n ctx: PluginContext<LinkState, LinkAction>,\n config: LinkPluginConfig = {},\n): LinkCapability {\n const anno = () => ctx.tryGet(AnnotationHostToken);\n\n const activate = (target: PdfLinkTarget): LinkActivation => {\n const activation = ((): LinkActivation => {\n switch (target.kind) {\n case 'goto': {\n const stage = ctx.tryGet(StageToken);\n const layout = ctx\n .document()\n ?.pages.find((p) => p.pageObjectNumber === target.destination.pageObjectNumber);\n if (!stage || !layout) {\n // No camera to drive — hand the embedder the explicit destination.\n return { outcome: 'destination', destination: target.destination };\n }\n const { pageIndex, options } = destinationToReveal(target.destination, layout);\n stage.reveal(pageIndex, { ...options, behavior: 'smooth' });\n return { outcome: 'revealed' };\n }\n case 'uri':\n // Opening is the FRAMEWORK layer's job (a real <a href> after\n // sanitizing) — this plugin never touches the DOM.\n return { outcome: 'uri', uri: target.uri };\n case 'named':\n return { outcome: 'named', name: target.name };\n default:\n // javascript / goto-remote / launch / unsupported: reported, never\n // executed here. JavaScript activation belongs to the scripting\n // orchestrator once it lands — the chain already rides the\n // annotation's base `actions`.\n return { outcome: 'reported', target };\n }\n })();\n config.onActivate?.({ target, activation });\n return activation;\n };\n\n return {\n linksOn: (pon) => {\n const host = anno();\n if (host) return host.linkItemsOn(pon);\n return ctx.getState().pages[pon] ?? EMPTY;\n },\n ensurePage: (pon) => {\n const host = anno();\n if (host) {\n host.ensurePage(pon); // the folded model is the source; it lazy-loads\n return;\n }\n if (!(pon in ctx.getState().pages)) loadLinksPage(ctx, pon);\n },\n engaged: () => ctx.tryGet(InteractionToken)?.activeTool()?.enables.has('link-nav') ?? false,\n activate,\n };\n}\n","import type { DocumentEvent, EffectContext } from '@embedpdf/core';\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/internal';\nimport { loadLinksPage } from './source';\nimport type { LinkAction, LinkState } from './types';\n\n/**\n * Keeps the ENGINE-BACKED page cache honest in viewer-only deployments:\n * any annotation mutation on a cached page (own or remote — the cache has\n * no other refresh path, unlike the annotation plugin's optimistic model)\n * refetches that page's links. With the annotation plugin present the\n * folded model is the source and this effect never fires a fetch.\n */\nexport function registerLinkEffects(ctx: EffectContext<LinkState, LinkAction>): void {\n const doc = ctx.doc;\n if (!doc) return;\n\n const refetch = (pon: number): void => {\n if (ctx.tryGet(AnnotationHostToken)) return; // annotation model owns the data\n if (pon in ctx.getState().pages) loadLinksPage(ctx, pon);\n };\n\n const unsubscribe = doc.events.subscribe((event: DocumentEvent) => {\n switch (event.type) {\n case 'annotation.created':\n refetch(event.created.pageObjectNumber);\n break;\n case 'annotation.updated':\n refetch(event.updated.pageObjectNumber);\n break;\n case 'annotation.moved':\n if (event.moved.length) refetch(event.moved[0].pageObjectNumber);\n break;\n case 'annotation.deleted':\n refetch(event.pageObjectNumber);\n break;\n default:\n break;\n }\n });\n ctx.cleanup(unsubscribe);\n}\n","import type { LinkAction, LinkState } from './types';\n\nexport const initialLinkState = (): LinkState => ({ pages: {} });\n\n/** Pure. Engine-backed page cache only — the annotation-backed mode reads the\n * annotation plugin's model and never touches this state. */\nexport function linkReducer(state: LinkState, a: LinkAction): LinkState {\n switch (a.type) {\n case 'SET_PAGE':\n return { ...state, pages: { ...state.pages, [a.pon]: a.items } };\n case 'DROP_PAGE': {\n if (!(a.pon in state.pages)) return state;\n const pages = { ...state.pages };\n delete pages[a.pon];\n return { ...state, pages };\n }\n }\n}\n","import { createCapabilityToken, type PageObjectNumber } from '@embedpdf/core';\nimport type { PdfDestination, PdfLinkTarget } from '@embedpdf/engine-core/runtime';\nimport type { LinkNavItem } from '@embedpdf/plugin-annotation';\n\nexport type { LinkNavItem };\n// The wire target vocabulary, re-exported so app code building link editors\n// needs only this package (the annotation-props precedent).\nexport type { PdfDestination, PdfLinkTarget };\n\n/**\n * What one activation did. `revealed` = the stage moved the camera;\n * `destination` = no stage plugin — the embedder gets the explicit\n * destination to handle; `uri` = opening is the FRAMEWORK layer's job (the\n * plugin is DOM-free); `named` = a viewer verb (NextPage…) reported until\n * stage verbs land; `reported` = read-only/executable-shaped targets\n * (javascript / goto-remote / launch / unsupported) — surfaced, NEVER\n * executed here (javascript belongs to the scripting orchestrator).\n */\nexport type LinkActivation =\n | { outcome: 'revealed' }\n | { outcome: 'destination'; destination: PdfDestination }\n | { outcome: 'uri'; uri: string }\n | { outcome: 'named'; name: string }\n | { outcome: 'reported'; target: PdfLinkTarget }\n | { outcome: 'none' };\n\nexport interface LinkActivateEvent {\n target: PdfLinkTarget;\n activation: LinkActivation;\n}\n\nexport interface LinkPluginConfig {\n /** Every activation (navigations AND reported targets) — analytics /\n * custom handling. Called after the built-in handling ran. */\n onActivate?: (event: LinkActivateEvent) => void;\n}\n\n/**\n * The navigation plane of link annotations. Data comes from ONE of two\n * sources, picked at init: the annotation plugin's folded model when that\n * plugin is present (zero extra engine reads — it already lists every\n * page), or the plugin's own engine reads in viewer-only deployments.\n */\nexport interface LinkCapability {\n /** The clickable link areas of a page (content space, y-down). */\n linksOn(pon: PageObjectNumber): LinkNavItem[];\n /** Lazy-load a page's links (no-op when the annotation plugin owns the data). */\n ensurePage(pon: PageObjectNumber): void;\n /** Whether the navigation plane currently owns links: the active tool\n * enables `link-nav` (pointer/pan/form-fill by default). While false —\n * the link tool or any authoring tool is active — the annotation plane\n * owns them and the LinkLayer stands down. */\n engaged(): boolean;\n /** THE one activation entry point (framework layers call it on click). */\n activate(target: PdfLinkTarget): LinkActivation;\n}\n\n/** Engine-backed page cache (viewer-only deployments). */\nexport interface LinkState {\n pages: Record<number, LinkNavItem[]>;\n}\n\nexport type LinkAction =\n | { type: 'SET_PAGE'; pon: number; items: LinkNavItem[] }\n | { type: 'DROP_PAGE'; pon: number };\n\nexport const LinkToken = createCapabilityToken<LinkCapability>('link');\n","import { definePlugin } from '@embedpdf/core';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport { StageToken } from '@embedpdf/plugin-stage';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation';\n// Behavior registration lives on the HOST capability (framework/plugin\n// surface) — same runtime token, wider type. The form plugin's precedent.\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/internal';\n\nimport { createLinkCapability } from './capability';\nimport { registerLinkEffects } from './effects';\nimport { initialLinkState, linkReducer } from './reducer';\nimport { LinkToken } from './types';\nimport type { LinkAction, LinkCapability, LinkPluginConfig, LinkState } from './types';\n\n/**\n * The link plugin: the NAVIGATION plane. Document-scoped; requires only the\n * interaction hub. Navigation works with no annotation plugin at all —\n * links are read from the engine's link DTOs. When the annotation plugin IS\n * present, its folded model becomes the data source (no double fetch) and a\n * Behavior keeps links navigation-owned while a `link-nav` tool is active\n * (pointer/pan/form-fill by default) — the single-active-tool hub IS the\n * mode switch: grab the link tool and every link becomes an editable rect.\n */\nexport const linkPlugin = (config?: LinkPluginConfig) =>\n definePlugin<LinkState, LinkAction, LinkCapability>({\n id: 'link',\n token: LinkToken,\n scope: 'document',\n requires: [InteractionToken],\n optional: [StageToken, AnnotationToken],\n initialState: initialLinkState,\n reduce: linkReducer,\n capability: (ctx) => createLinkCapability(ctx, config),\n effects: registerLinkEffects,\n init: (ctx) => {\n const interaction = ctx.get(InteractionToken);\n const annotation = ctx.tryGet(AnnotationHostToken);\n if (annotation) {\n annotation.registerBehavior({\n id: 'link-nav',\n matches: (a) => a.subtype === 'link',\n engaged: () => interaction.activeTool()?.enables.has('link-nav') ?? false,\n });\n }\n },\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["AnnotationHostToken","AnnotationHostToken","AnnotationHostToken"],"sources":["../src/source.ts","../src/capability.ts","../src/effects.ts","../src/reducer.ts","../src/link.plugin.ts"],"sourcesContent":["import type { DocumentHandle, PageLayout } from '@embedpdf/core';\nimport { pdfToContentRect } from '@embedpdf/core-annotation';\nimport type { LinkAction, LinkNavItem } from './types';\n\n/**\n * The ENGINE-BACKED link source, for viewer-only deployments (no annotation\n * plugin): read a page's annotation list and keep just the navigable links —\n * standalone AND attached children alike (both are raw link DTOs here).\n * Shared by the capability's lazy `ensurePage` and the effects module's\n * event-driven refetch; both PluginContext and EffectContext satisfy the\n * structural `io` slice.\n */\nexport interface LinkSourceIO {\n doc: DocumentHandle | null;\n document(): { pages: readonly PageLayout[] } | null;\n dispatch(action: LinkAction): void;\n}\n\nexport function loadLinksPage(io: LinkSourceIO, pon: number): void {\n const doc = io.doc;\n const crop = io.document()?.pages.find((p) => p.pageObjectNumber === pon)?.boxes.crop;\n if (!doc || !crop) return;\n doc\n .page(pon)\n .annotations.list()\n .then(\n (snap) => {\n const items: LinkNavItem[] = [];\n for (const dto of snap.annotations) {\n if (dto.subtype !== 'link' || dto.target == null) continue;\n if (dto.flags.hidden || dto.flags.noView) continue;\n items.push({\n id:\n dto.ref.kind === 'objectNumber'\n ? `obj:${dto.ref.annotObjectNumber}`\n : `idx:${pon}:${dto.index}`,\n rect: pdfToContentRect(dto.rect, crop),\n target: dto.target,\n ...(dto.actions?.activate ? { activate: dto.actions.activate } : {}),\n ...(dto.actions?.cursorEnter?.root || dto.actions?.cursorExit?.root\n ? {\n hoverEvents: {\n enter: Boolean(dto.actions?.cursorEnter?.root),\n exit: Boolean(dto.actions?.cursorExit?.root),\n },\n }\n : {}),\n ref: dto.ref,\n // A `/RT /Group` child riding another annotation — labeled so the\n // nav layer can defer to editing (moot in viewer-only deployments,\n // but the item contract stays truthful either way).\n attached: dto.replyType === 'group' && dto.inReplyTo != null,\n });\n }\n io.dispatch({ type: 'SET_PAGE', pon, items });\n },\n () => {},\n );\n}\n","import type { PluginContext } from '@embedpdf/core';\nimport type { PdfLinkTarget } from '@embedpdf/engine-core/runtime';\nimport { ActionsToken } from '@embedpdf/plugin-actions/contract';\n// The host lens (linkItemsOn) — same runtime token as the public one.\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\nimport { destinationToReveal } from '@embedpdf/plugin-stage/destination';\nimport { loadLinksPage } from './source';\nimport type {\n LinkAction,\n LinkActivateContext,\n LinkActivation,\n LinkCapability,\n LinkNavItem,\n LinkPluginConfig,\n LinkState,\n} from './types';\n\nconst EMPTY: LinkNavItem[] = [];\n\n/**\n * The navigation plane's surface. Data source is decided PER CALL (lazily,\n * so plugin registration order can't matter): the annotation plugin's\n * folded model when present — its `linkItemsOn` lens is memoized by model\n * identity and already excludes hidden links — else this plugin's own\n * engine-backed page cache (see source.ts).\n */\nexport function createLinkCapability(\n ctx: PluginContext<LinkState, LinkAction>,\n config: LinkPluginConfig = {},\n): LinkCapability {\n const anno = () => ctx.tryGet(AnnotationHostToken);\n\n const activate = (target: PdfLinkTarget, context?: LinkActivateContext): LinkActivation => {\n const activation = ((): LinkActivation => {\n // The action engine takes precedence when it is installed and the item\n // carries its payload tree: named verbs execute, mixed /Next chains\n // run, and the dispatcher enforces policy + the incomplete-tree law.\n // Without it, the classic root-projection path below is the fallback.\n const actions = ctx.tryGet(ActionsToken);\n if (actions && context?.activate) {\n const dispatch = actions.execute(context.activate, {\n origin: 'user',\n source: { kind: 'link', annotation: context.ref, pon: context.pon },\n event: { scope: 'activate' },\n });\n return { outcome: 'dispatched', dispatch };\n }\n switch (target.kind) {\n case 'goto': {\n const stage = ctx.tryGet(StageToken);\n const layout = ctx\n .document()\n ?.pages.find((p) => p.pageObjectNumber === target.destination.pageObjectNumber);\n if (!stage || !layout) {\n // No camera to drive — hand the embedder the explicit destination.\n return { outcome: 'destination', destination: target.destination };\n }\n const { pageIndex, options } = destinationToReveal(target.destination, layout);\n stage.reveal(pageIndex, { ...options, behavior: 'smooth' });\n return { outcome: 'revealed' };\n }\n case 'uri':\n // Opening is the FRAMEWORK layer's job (a real <a href> after\n // sanitizing) — this plugin never touches the DOM.\n return { outcome: 'uri', uri: target.uri };\n case 'named':\n return { outcome: 'named', name: target.name };\n default:\n // javascript / goto-remote / launch / unsupported: reported, never\n // executed here. JavaScript activation belongs to the scripting\n // orchestrator once it lands — the chain already rides the\n // annotation's base `actions`.\n return { outcome: 'reported', target };\n }\n })();\n config.onActivate?.({ target, activation });\n return activation;\n };\n\n return {\n linksOn: (pon) => {\n const host = anno();\n if (host) return host.linkItemsOn(pon);\n return ctx.getState().pages[pon] ?? EMPTY;\n },\n ensurePage: (pon) => {\n const host = anno();\n if (host) {\n host.ensurePage(pon); // the folded model is the source; it lazy-loads\n return;\n }\n if (!(pon in ctx.getState().pages)) loadLinksPage(ctx, pon);\n },\n engaged: () => ctx.tryGet(InteractionToken)?.activeTool()?.enables.has('link-nav') ?? false,\n activate,\n };\n}\n","import type { DocumentEvent, EffectContext } from '@embedpdf/core';\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { loadLinksPage } from './source';\nimport type { LinkAction, LinkState } from './types';\n\n/**\n * Keeps the ENGINE-BACKED page cache honest in viewer-only deployments:\n * any annotation mutation on a cached page (own or remote — the cache has\n * no other refresh path, unlike the annotation plugin's optimistic model)\n * refetches that page's links. With the annotation plugin present the\n * folded model is the source and this effect never fires a fetch.\n */\nexport function registerLinkEffects(ctx: EffectContext<LinkState, LinkAction>): void {\n const doc = ctx.doc;\n if (!doc) return;\n\n const refetch = (pon: number): void => {\n if (ctx.tryGet(AnnotationHostToken)) return; // annotation model owns the data\n if (pon in ctx.getState().pages) loadLinksPage(ctx, pon);\n };\n\n const unsubscribe = doc.events.subscribe((event: DocumentEvent) => {\n switch (event.type) {\n case 'annotation.created':\n refetch(event.created.pageObjectNumber);\n break;\n case 'annotation.updated':\n refetch(event.updated.pageObjectNumber);\n break;\n case 'annotation.moved':\n if (event.moved.length) refetch(event.moved[0].pageObjectNumber);\n break;\n case 'annotation.deleted':\n refetch(event.pageObjectNumber);\n break;\n default:\n break;\n }\n });\n ctx.cleanup(unsubscribe);\n}\n","import type { LinkAction, LinkState } from './types';\n\nexport const initialLinkState = (): LinkState => ({ pages: {} });\n\n/** Pure. Engine-backed page cache only — the annotation-backed mode reads the\n * annotation plugin's model and never touches this state. */\nexport function linkReducer(state: LinkState, a: LinkAction): LinkState {\n switch (a.type) {\n case 'SET_PAGE':\n return { ...state, pages: { ...state.pages, [a.pon]: a.items } };\n case 'DROP_PAGE': {\n if (!(a.pon in state.pages)) return state;\n const pages = { ...state.pages };\n delete pages[a.pon];\n return { ...state, pages };\n }\n }\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { ActionsToken } from '@embedpdf/plugin-actions/contract';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/contract';\n// Behavior registration lives on the HOST capability (framework/plugin\n// surface) — same runtime token, wider type. The form plugin's precedent.\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\n\nimport { createLinkCapability } from './capability';\nimport { registerLinkEffects } from './effects';\nimport { initialLinkState, linkReducer } from './reducer';\nimport { LinkToken } from './types';\nimport type { LinkAction, LinkCapability, LinkPluginConfig, LinkState } from './types';\n\n/**\n * The link plugin: the NAVIGATION plane. Document-scoped; requires only the\n * interaction hub. Navigation works with no annotation plugin at all —\n * links are read from the engine's link DTOs. When the annotation plugin IS\n * present, its folded model becomes the data source (no double fetch) and a\n * Behavior keeps links navigation-owned while a `link-nav` tool is active\n * (pointer/pan/form-fill by default) — the single-active-tool hub IS the\n * mode switch: grab the link tool and every link becomes an editable rect.\n */\nexport const linkPlugin = (config?: LinkPluginConfig) =>\n definePlugin<LinkState, LinkAction, LinkCapability>({\n id: 'link',\n token: LinkToken,\n scope: 'document',\n requires: [InteractionToken],\n optional: [StageToken, AnnotationToken, ActionsToken],\n initialState: initialLinkState,\n reduce: linkReducer,\n capability: (ctx) => createLinkCapability(ctx, config),\n effects: registerLinkEffects,\n init: (ctx) => {\n const interaction = ctx.get(InteractionToken);\n const annotation = ctx.tryGet(AnnotationHostToken);\n if (annotation) {\n annotation.registerBehavior({\n id: 'link-nav',\n matches: (a) => a.subtype === 'link',\n engaged: () => interaction.activeTool()?.enables.has('link-nav') ?? false,\n });\n }\n },\n });\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embedpdf/plugin-link",
3
- "version": "3.0.0-next.10",
3
+ "version": "3.0.0-next.11",
4
4
  "private": false,
5
5
  "description": "The navigation plane for PDF link annotations: per-page clickable link areas + one activate() entry point (GoTo → stage reveal, URI → framework opener). Works standalone in viewer deployments; with the annotation plugin present it reads the folded model (no double fetch) and a Behavior hands links to the editing plane when the link tool is active.",
6
6
  "type": "module",
@@ -12,19 +12,24 @@
12
12
  "import": "./dist/index.js",
13
13
  "require": "./dist/index.cjs"
14
14
  },
15
+ "./contract": {
16
+ "import": "./dist/contract.js",
17
+ "require": "./dist/contract.cjs"
18
+ },
15
19
  "./package.json": "./package.json"
16
20
  },
17
21
  "files": [
18
22
  "dist"
19
23
  ],
20
24
  "dependencies": {
21
- "@embedpdf/core-annotation": "3.0.0-next.10",
22
- "@embedpdf/core": "3.0.0-next.10",
23
- "@embedpdf/plugin-interaction": "3.0.0-next.10",
24
- "@embedpdf/core-geometry": "3.0.0-next.10",
25
- "@embedpdf/plugin-stage": "3.0.0-next.10",
26
- "@embedpdf/engine-core": "3.0.0-next.10",
27
- "@embedpdf/plugin-annotation": "3.0.0-next.10"
25
+ "@embedpdf/core-annotation": "3.0.0-next.11",
26
+ "@embedpdf/core": "3.0.0-next.11",
27
+ "@embedpdf/plugin-annotation": "3.0.0-next.11",
28
+ "@embedpdf/core-geometry": "3.0.0-next.11",
29
+ "@embedpdf/plugin-interaction": "3.0.0-next.11",
30
+ "@embedpdf/engine-core": "3.0.0-next.11",
31
+ "@embedpdf/plugin-stage": "3.0.0-next.11",
32
+ "@embedpdf/plugin-actions": "3.0.0-next.11"
28
33
  },
29
34
  "devDependencies": {
30
35
  "typescript": "^5.0.0",