@embedpdf/plugin-link 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/index.cjs CHANGED
@@ -1,197 +1,268 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_contract = require("./contract-DGKEPl11.cjs");
2
+ const require_contract = require("./contract-CaaMtYBR.cjs");
3
+ const require_contract_host = require("./contract/host.cjs");
3
4
  let _embedpdf_core = require("@embedpdf/core");
4
5
  let _embedpdf_plugin_actions_contract = require("@embedpdf/plugin-actions/contract");
5
6
  let _embedpdf_plugin_annotation_contract = require("@embedpdf/plugin-annotation/contract");
6
- let _embedpdf_plugin_annotation_contract_host = require("@embedpdf/plugin-annotation/contract/host");
7
7
  let _embedpdf_plugin_interaction_contract = require("@embedpdf/plugin-interaction/contract");
8
8
  let _embedpdf_plugin_stage_contract = require("@embedpdf/plugin-stage/contract");
9
- let _embedpdf_plugin_stage_destination = require("@embedpdf/plugin-stage/destination");
10
- let _embedpdf_core_annotation = require("@embedpdf/core-annotation");
9
+ let _embedpdf_plugin_annotation_contract_host = require("@embedpdf/plugin-annotation/contract/host");
10
+ let _embedpdf_plugin_interaction_contract_host = require("@embedpdf/plugin-interaction/contract/host");
11
+ let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
12
+ //#region src/connect.ts
13
+ /**
14
+ * Links are annotations: while a navigation tool is active the link layer
15
+ * owns their input (its anchors take the clicks), so the annotation plane
16
+ * stands down for them. A link still shows as the file draws it.
17
+ */
18
+ function connectLink(ctx) {
19
+ const annotation = ctx.tryGet(_embedpdf_plugin_annotation_contract_host.AnnotationToken);
20
+ if (!annotation) return;
21
+ const interaction = ctx.get(_embedpdf_plugin_interaction_contract_host.InteractionToken);
22
+ ctx.cleanup(annotation.registerBehavior({
23
+ id: "link-nav",
24
+ matches: (target) => target.subtype === "link",
25
+ engaged: () => interaction.activeToolEnables("link-nav")
26
+ }));
27
+ }
28
+ //#endregion
11
29
  //#region src/source.ts
12
- function loadLinksPage(io, pon) {
13
- const doc = io.doc;
14
- const crop = io.document()?.pages.find((p) => p.pageObjectNumber === pon)?.boxes.crop;
15
- if (!doc || !crop) return;
16
- doc.page(pon).annotations.list().then((snap) => {
17
- const items = [];
18
- for (const dto of snap.annotations) {
19
- if (dto.subtype !== "link" || dto.target == null) continue;
20
- if (dto.flags.hidden || dto.flags.noView) continue;
21
- items.push({
22
- id: dto.ref.kind === "objectNumber" ? `obj:${dto.ref.annotObjectNumber}` : `idx:${pon}:${dto.index}`,
23
- rect: (0, _embedpdf_core_annotation.pdfToContentRect)(dto.rect, crop),
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,
31
- attached: dto.replyType === "group" && dto.inReplyTo != null
32
- });
33
- }
34
- io.dispatch({
35
- type: "SET_PAGE",
36
- pon,
37
- items
30
+ /**
31
+ * The stand-alone source, used when no annotation plugin is installed: the
32
+ * clickable areas among one page's annotations, in page space.
33
+ */
34
+ /** The visible link annotations of a page that have a target. */
35
+ function linksOf(annotations) {
36
+ const links = [];
37
+ for (const dto of annotations) {
38
+ if (dto.subtype !== "link" || dto.target == null) continue;
39
+ if (dto.hidden || dto.noView) continue;
40
+ links.push({
41
+ id: (0, _embedpdf_engine_core_runtime.annotationKey)(dto.ref),
42
+ bounds: dto.rect,
43
+ target: dto.target,
44
+ ...dto.actions?.activate ? { activate: dto.actions.activate } : {},
45
+ ...dto.actions?.cursorEnter?.root || dto.actions?.cursorExit?.root ? { hoverEvents: {
46
+ enter: Boolean(dto.actions?.cursorEnter?.root),
47
+ exit: Boolean(dto.actions?.cursorExit?.root)
48
+ } } : {},
49
+ ref: dto.ref,
50
+ attached: dto.reply?.type === "group"
38
51
  });
39
- }, () => {});
52
+ }
53
+ return links;
40
54
  }
41
55
  //#endregion
42
- //#region src/capability.ts
43
- const EMPTY = [];
56
+ //#region src/controller.ts
57
+ const EMPTY = Object.freeze([]);
58
+ const isLink = (value) => "target" in value && "bounds" in value;
59
+ const contains = (bounds, point) => point.x >= bounds.x && point.x <= bounds.x + bounds.width && point.y >= bounds.y && point.y <= bounds.y + bounds.height;
60
+ /** The pages whose annotation lists an event changed, each once. */
61
+ function pagesChangedBy(event) {
62
+ switch (event.type) {
63
+ case "annotations.created": return [event.annotation.page];
64
+ case "annotations.updated": return [event.annotation.page];
65
+ case "annotations.deleted":
66
+ case "annotations.reordered": return [event.page];
67
+ case "redaction.applied":
68
+ case "pages.flattened": return event.results.filter((result) => result.status === "applied").map((result) => result.page);
69
+ case "annotations.flattened": return event.results.some((result) => result.status === "applied") ? [event.page] : null;
70
+ default: return null;
71
+ }
72
+ }
44
73
  /**
45
- * The navigation plane's surface. Data source is decided PER CALL (lazily,
46
- * so plugin registration order can't matter): the annotation plugin's
47
- * folded model when present — its `linkItemsOn` lens is memoized by model
48
- * identity and already excludes hidden links — else this plugin's own
49
- * engine-backed page cache (see source.ts).
74
+ * The link controller. Two sources for the clickable areas: the annotation
75
+ * plugin's folded model when it is installed (always current), else a page
76
+ * mirror of this plugin's own per-page reads, re-read when a confirmed
77
+ * change (an annotation write, a redaction, a flatten) touches a loaded page. Activation is pure resolution
78
+ * (`resolve`) plus the one side effect this plugin owns, a stage reveal;
79
+ * everything else is reported to the host, which keeps the user gesture.
50
80
  */
51
- function createLinkCapability(ctx, config = {}) {
52
- const anno = () => ctx.tryGet(_embedpdf_plugin_annotation_contract_host.AnnotationToken);
53
- const activate = (target, context) => {
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
- })
81
+ function createLinkController(ctx) {
82
+ const annotationHost = () => ctx.tryGet(_embedpdf_plugin_annotation_contract_host.AnnotationToken);
83
+ const activated = ctx.events.source();
84
+ const loaded = ctx.events.source();
85
+ const pages = ctx.pageMirror({
86
+ name: "links",
87
+ load: async (doc, page) => {
88
+ if (!ctx.getPage(page)) throw new _embedpdf_core.PluginError("not-found", "link", `page ${page.objectNumber} is not in this document`);
89
+ return linksOf((await doc.page(page).annotations.list()).annotations);
90
+ },
91
+ affected: pagesChangedBy,
92
+ changed: ({ page, cause }) => {
93
+ if (cause === "load") loaded.emit({ page });
94
+ }
95
+ });
96
+ const listLinks = (pageArgument) => {
97
+ const page = ctx.getPage(pageArgument)?.ref;
98
+ if (!page) return EMPTY;
99
+ const host = annotationHost();
100
+ if (host) return host.listLinkItems(page);
101
+ return pages.get(page) ?? EMPTY;
102
+ };
103
+ const ensureLoaded = async (pageArgument, options) => {
104
+ const { ref } = ctx.pageOf(pageArgument);
105
+ if (annotationHost()) return;
106
+ await ctx.cancellable(options?.signal, pages.ensureLoaded(ref));
107
+ };
108
+ const uriOpeners = [];
109
+ const resolve = (target) => {
110
+ switch (target.kind) {
111
+ case "goto": return ctx.getPage(target.destination.page) ? {
112
+ kind: "destination",
113
+ destination: target.destination
114
+ } : {
115
+ kind: "reported",
116
+ target
67
117
  };
68
- switch (target.kind) {
69
- case "goto": {
70
- const stage = ctx.tryGet(_embedpdf_plugin_stage_contract.StageToken);
71
- const layout = ctx.document()?.pages.find((p) => p.pageObjectNumber === target.destination.pageObjectNumber);
72
- if (!stage || !layout) return {
73
- outcome: "destination",
74
- destination: target.destination
75
- };
76
- const { pageIndex, options } = (0, _embedpdf_plugin_stage_destination.destinationToReveal)(target.destination, layout);
77
- stage.reveal(pageIndex, {
78
- ...options,
79
- behavior: "smooth"
80
- });
81
- return { outcome: "revealed" };
82
- }
83
- case "uri": return {
84
- outcome: "uri",
85
- uri: target.uri
118
+ case "uri": return {
119
+ kind: "uri",
120
+ uri: target.uri
121
+ };
122
+ case "named": return {
123
+ kind: "named",
124
+ name: target.name
125
+ };
126
+ default: return {
127
+ kind: "reported",
128
+ target
129
+ };
130
+ }
131
+ };
132
+ const perform = (target, context) => {
133
+ const actions = ctx.tryGet(_embedpdf_plugin_actions_contract.ActionsToken);
134
+ if (actions && context?.activate) return {
135
+ outcome: "dispatched",
136
+ dispatch: actions.execute(context.activate, {
137
+ origin: "user",
138
+ source: {
139
+ kind: "link",
140
+ annotation: context.ref,
141
+ page: context.page
142
+ },
143
+ event: { scope: "activate" }
144
+ })
145
+ };
146
+ const resolution = resolve(target);
147
+ switch (resolution.kind) {
148
+ case "destination": {
149
+ const stage = context?.stage ?? ctx.tryGet(_embedpdf_plugin_stage_contract.StageToken);
150
+ if (!stage) return {
151
+ outcome: "destination",
152
+ destination: resolution.destination
86
153
  };
87
- case "named": return {
88
- outcome: "named",
89
- name: target.name
154
+ stage.goToDestination(resolution.destination);
155
+ return { outcome: "revealed" };
156
+ }
157
+ case "uri": {
158
+ const open = uriOpeners[0];
159
+ if (!open || open(resolution.uri)) return {
160
+ outcome: "uri",
161
+ uri: resolution.uri
90
162
  };
91
- default: return {
163
+ return {
92
164
  outcome: "reported",
93
165
  target
94
166
  };
95
167
  }
96
- })();
97
- config.onActivate?.({
168
+ case "named": return {
169
+ outcome: "named",
170
+ name: resolution.name
171
+ };
172
+ default: return {
173
+ outcome: "reported",
174
+ target
175
+ };
176
+ }
177
+ };
178
+ const activate = (input, context) => {
179
+ const target = isLink(input) ? input.target : input;
180
+ const linkContext = isLink(input) ? {
181
+ activate: input.activate,
182
+ ref: input.ref,
183
+ ...context
184
+ } : context;
185
+ const activation = perform(target, linkContext);
186
+ activated.emit({
98
187
  target,
99
188
  activation
100
189
  });
101
190
  return activation;
102
191
  };
103
- return {
104
- linksOn: (pon) => {
105
- const host = anno();
106
- if (host) return host.linkItemsOn(pon);
107
- return ctx.getState().pages[pon] ?? EMPTY;
108
- },
109
- ensurePage: (pon) => {
110
- const host = anno();
111
- if (host) {
112
- host.ensurePage(pon);
113
- return;
114
- }
115
- if (!(pon in ctx.getState().pages)) loadLinksPage(ctx, pon);
116
- },
117
- engaged: () => ctx.tryGet(_embedpdf_plugin_interaction_contract.InteractionToken)?.activeTool()?.enables.has("link-nav") ?? false,
118
- activate
119
- };
120
- }
121
- //#endregion
122
- //#region src/effects.ts
123
- /**
124
- * Keeps the ENGINE-BACKED page cache honest in viewer-only deployments:
125
- * any annotation mutation on a cached page (own or remote — the cache has
126
- * no other refresh path, unlike the annotation plugin's optimistic model)
127
- * refetches that page's links. With the annotation plugin present the
128
- * folded model is the source and this effect never fires a fetch.
129
- */
130
- function registerLinkEffects(ctx) {
131
- const doc = ctx.doc;
132
- if (!doc) return;
133
- const refetch = (pon) => {
134
- if (ctx.tryGet(_embedpdf_plugin_annotation_contract_host.AnnotationToken)) return;
135
- if (pon in ctx.getState().pages) loadLinksPage(ctx, pon);
192
+ const getLinkAt = (page, point) => {
193
+ let best = null;
194
+ for (const link of listLinks(page)) {
195
+ if (!contains(link.bounds, point)) continue;
196
+ if (!best || link.bounds.width * link.bounds.height < best.bounds.width * best.bounds.height) best = link;
197
+ }
198
+ return best;
136
199
  };
137
- const unsubscribe = doc.events.subscribe((event) => {
138
- switch (event.type) {
139
- case "annotation.created":
140
- refetch(event.created.pageObjectNumber);
141
- break;
142
- case "annotation.updated":
143
- refetch(event.updated.pageObjectNumber);
144
- break;
145
- case "annotation.moved":
146
- if (event.moved.length) refetch(event.moved[0].pageObjectNumber);
147
- break;
148
- case "annotation.deleted":
149
- refetch(event.pageObjectNumber);
150
- break;
151
- default: break;
200
+ const getLabel = (input) => {
201
+ const target = isLink(input) ? input.target : input;
202
+ switch (target.kind) {
203
+ case "uri": return target.uri;
204
+ case "goto": return "Go to destination";
205
+ case "named": return target.name;
206
+ default: return "Link";
152
207
  }
153
- });
154
- ctx.cleanup(unsubscribe);
155
- }
156
- //#endregion
157
- //#region src/reducer.ts
158
- const initialLinkState = () => ({ pages: {} });
159
- /** Pure. Engine-backed page cache only — the annotation-backed mode reads the
160
- * annotation plugin's model and never touches this state. */
161
- function linkReducer(state, a) {
162
- switch (a.type) {
163
- case "SET_PAGE": return {
164
- ...state,
165
- pages: {
166
- ...state.pages,
167
- [a.pon]: a.items
168
- }
169
- };
170
- case "DROP_PAGE": {
171
- if (!(a.pon in state.pages)) return state;
172
- const pages = { ...state.pages };
173
- delete pages[a.pon];
174
- return {
175
- ...state,
176
- pages
177
- };
208
+ };
209
+ return {
210
+ api: {
211
+ listLinks,
212
+ getLink: (page, linkId) => listLinks(page).find((link) => link.id === linkId) ?? null,
213
+ getLinkAt,
214
+ listAllLinks: async (options) => {
215
+ const layouts = ctx.document()?.pages ?? [];
216
+ await ctx.cancellable(options?.signal, Promise.all(layouts.map((layout) => ensureLoaded(layout.ref, options))));
217
+ return layouts.flatMap((layout) => listLinks(layout.ref));
218
+ },
219
+ ensureLoaded,
220
+ isLoaded: (page) => {
221
+ const ref = ctx.getPage(page)?.ref;
222
+ return !!ref && (annotationHost() !== null || pages.getStatus(ref) === "ready");
223
+ },
224
+ getStatus: (page) => {
225
+ const ref = ctx.getPage(page)?.ref;
226
+ if (!ref) return "idle";
227
+ return annotationHost() ? "ready" : pages.getStatus(ref);
228
+ },
229
+ resolve,
230
+ activate,
231
+ activateAt: (pageArgument, point, context) => {
232
+ const page = ctx.pageOf(pageArgument).ref;
233
+ const link = getLinkAt(page, point);
234
+ return link ? activate(link, {
235
+ page,
236
+ ...context
237
+ }) : null;
238
+ },
239
+ getLabel,
240
+ onActivated: activated.on,
241
+ onLoaded: loaded.on,
242
+ registerUriOpener: (opener) => {
243
+ uriOpeners.unshift(opener);
244
+ return () => {
245
+ const index = uriOpeners.indexOf(opener);
246
+ if (index >= 0) uriOpeners.splice(index, 1);
247
+ };
248
+ },
249
+ isNavigationEngaged: () => ctx.tryGet(_embedpdf_plugin_interaction_contract_host.InteractionToken)?.activeToolEnables("link-nav") ?? false
250
+ },
251
+ connect() {
252
+ connectLink(ctx);
178
253
  }
179
- }
254
+ };
180
255
  }
181
256
  //#endregion
182
257
  //#region src/link.plugin.ts
183
258
  /**
184
- * The link plugin: the NAVIGATION plane. Document-scoped; requires only the
185
- * interaction hub. Navigation works with no annotation plugin at all —
186
- * links are read from the engine's link DTOs. When the annotation plugin IS
187
- * present, its folded model becomes the data source (no double fetch) and a
188
- * Behavior keeps links navigation-owned while a `link-nav` tool is active
189
- * (pointer/pan/form-fill by default) — the single-active-tool hub IS the
190
- * mode switch: grab the link tool and every link becomes an editable rect.
259
+ * Clickable link regions and their activation. Document-scoped; needs the
260
+ * interaction hub (the navigation tool tag), optionally the stage (reveal),
261
+ * the annotation plugin (the folded link model) and actions (`/A` trees).
191
262
  */
192
- const linkPlugin = (config) => (0, _embedpdf_core.definePlugin)({
263
+ const linkPlugin = () => (0, _embedpdf_core.definePlugin)({
193
264
  id: "link",
194
- token: require_contract.LinkToken,
265
+ token: require_contract_host.LinkToken,
195
266
  scope: "document",
196
267
  requires: [_embedpdf_plugin_interaction_contract.InteractionToken],
197
268
  optional: [
@@ -199,19 +270,7 @@ const linkPlugin = (config) => (0, _embedpdf_core.definePlugin)({
199
270
  _embedpdf_plugin_annotation_contract.AnnotationToken,
200
271
  _embedpdf_plugin_actions_contract.ActionsToken
201
272
  ],
202
- initialState: initialLinkState,
203
- reduce: linkReducer,
204
- capability: (ctx) => createLinkCapability(ctx, config),
205
- effects: registerLinkEffects,
206
- init: (ctx) => {
207
- const interaction = ctx.get(_embedpdf_plugin_interaction_contract.InteractionToken);
208
- const annotation = ctx.tryGet(_embedpdf_plugin_annotation_contract_host.AnnotationToken);
209
- if (annotation) annotation.registerBehavior({
210
- id: "link-nav",
211
- matches: (a) => a.subtype === "link",
212
- engaged: () => interaction.activeTool()?.enables.has("link-nav") ?? false
213
- });
214
- }
273
+ create: createLinkController
215
274
  });
216
275
  //#endregion
217
276
  exports.LinkToken = require_contract.LinkToken;
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"index.cjs","names":["AnnotationToken","InteractionToken","AnnotationHostToken","PluginError","ActionsToken","StageToken","InteractionToken","LinkToken","InteractionToken","StageToken","AnnotationToken","ActionsToken"],"sources":["../src/connect.ts","../src/source.ts","../src/controller.ts","../src/link.plugin.ts"],"sourcesContent":["/** The link plugin's wiring to its siblings, run from the controller's `connect`. */\nimport type { PluginContext } from '@embedpdf/core';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract/host';\n\n/**\n * Links are annotations: while a navigation tool is active the link layer\n * owns their input (its anchors take the clicks), so the annotation plane\n * stands down for them. A link still shows as the file draws it.\n */\nexport function connectLink(ctx: PluginContext<void>): void {\n const annotation = ctx.tryGet(AnnotationToken);\n if (!annotation) return;\n const interaction = ctx.get(InteractionToken);\n ctx.cleanup(\n annotation.registerBehavior({\n id: 'link-nav',\n matches: (target) => target.subtype === 'link',\n engaged: () => interaction.activeToolEnables('link-nav'),\n }),\n );\n}\n","/**\n * The stand-alone source, used when no annotation plugin is installed: the\n * clickable areas among one page's annotations, in page space.\n */\nimport { annotationKey, type Annotation } from '@embedpdf/engine-core/runtime';\n\nimport type { Link } from './contract';\n\n/** The visible link annotations of a page that have a target. */\nexport function linksOf(annotations: readonly Annotation[]): readonly Link[] {\n const links: Link[] = [];\n for (const dto of annotations) {\n if (dto.subtype !== 'link' || dto.target == null) continue;\n if (dto.hidden || dto.noView) continue;\n links.push({\n id: annotationKey(dto.ref),\n bounds: dto.rect,\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 attached: dto.reply?.type === 'group',\n });\n }\n return links;\n}\n","import {\n PluginError,\n type PluginContext,\n type DocumentEvent,\n type OperationOptions,\n type PageRef,\n} from '@embedpdf/core';\nimport type { Point } from '@embedpdf/core-geometry';\nimport type { PdfLinkTarget } from '@embedpdf/engine-core/runtime';\nimport { ActionsToken } from '@embedpdf/plugin-actions/contract';\nimport { AnnotationToken as AnnotationHostToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract/host';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\n\nimport { connectLink } from './connect';\nimport type {\n Link,\n LinkActivateContext,\n LinkActivatedEvent,\n LinkActivation,\n LinkLoadedEvent,\n LinkResolution,\n} from './contract';\nimport type { LinkHostCapability, UriOpener } from './host-contract';\nimport { linksOf } from './source';\n\nconst EMPTY: readonly Link[] = Object.freeze([]);\n\nconst isLink = (value: PdfLinkTarget | Link): value is Link =>\n 'target' in value && 'bounds' in value;\nconst contains = (\n bounds: { x: number; y: number; width: number; height: number },\n point: Point,\n): boolean =>\n point.x >= bounds.x &&\n point.x <= bounds.x + bounds.width &&\n point.y >= bounds.y &&\n point.y <= bounds.y + bounds.height;\n\n/** The pages whose annotation lists an event changed, each once. */\nfunction pagesChangedBy(event: DocumentEvent): readonly PageRef[] | null {\n switch (event.type) {\n case 'annotations.created':\n return [event.annotation.page];\n case 'annotations.updated':\n return [event.annotation.page];\n case 'annotations.deleted':\n case 'annotations.reordered':\n return [event.page];\n // These can remove annotations, links included, from the pages they applied to.\n case 'redaction.applied':\n case 'pages.flattened':\n return event.results\n .filter((result) => result.status === 'applied')\n .map((result) => result.page);\n case 'annotations.flattened':\n return event.results.some((result) => result.status === 'applied') ? [event.page] : null;\n default:\n return null;\n }\n}\n\n/**\n * The link controller. Two sources for the clickable areas: the annotation\n * plugin's folded model when it is installed (always current), else a page\n * mirror of this plugin's own per-page reads, re-read when a confirmed\n * change (an annotation write, a redaction, a flatten) touches a loaded page. Activation is pure resolution\n * (`resolve`) plus the one side effect this plugin owns, a stage reveal;\n * everything else is reported to the host, which keeps the user gesture.\n */\nexport function createLinkController(ctx: PluginContext<void>) {\n const annotationHost = () => ctx.tryGet(AnnotationHostToken);\n const activated = ctx.events.source<LinkActivatedEvent>();\n const loaded = ctx.events.source<LinkLoadedEvent>();\n\n const pages = ctx.pageMirror<readonly Link[]>({\n name: 'links',\n load: async (doc, page) => {\n if (!ctx.getPage(page)) {\n throw new PluginError(\n 'not-found',\n 'link',\n `page ${page.objectNumber} is not in this document`,\n );\n }\n const snapshot = await doc.page(page).annotations.list();\n return linksOf(snapshot.annotations);\n },\n affected: pagesChangedBy,\n changed: ({ page, cause }) => {\n if (cause === 'load') loaded.emit({ page });\n },\n });\n\n // Reads take a page's ref or its index; a page that isn't there has no links.\n const listLinks = (pageArgument: PageRef | number): readonly Link[] => {\n const page = ctx.getPage(pageArgument)?.ref;\n if (!page) return EMPTY;\n const host = annotationHost();\n if (host) return host.listLinkItems(page);\n return pages.get(page) ?? EMPTY;\n };\n\n const ensureLoaded = async (\n pageArgument: PageRef | number,\n options?: OperationOptions,\n ): Promise<void> => {\n const { ref } = ctx.pageOf(pageArgument);\n // With the annotation plugin installed, its model owns the data.\n if (annotationHost()) return;\n await ctx.cancellable(options?.signal, pages.ensureLoaded(ref));\n };\n\n // The framework's website openers, the latest first: the plugin never opens one itself.\n const uriOpeners: UriOpener[] = [];\n\n const resolve = (target: PdfLinkTarget): LinkResolution => {\n switch (target.kind) {\n case 'goto':\n // A page the document doesn't have can't be shown.\n return ctx.getPage(target.destination.page)\n ? { kind: 'destination', destination: target.destination }\n : { kind: 'reported', target };\n case 'uri':\n return { kind: 'uri', uri: target.uri };\n case 'named':\n return { kind: 'named', name: target.name };\n default:\n return { kind: 'reported', target };\n }\n };\n\n const perform = (target: PdfLinkTarget, context?: LinkActivateContext): LinkActivation => {\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, page: context.page },\n event: { scope: 'activate' },\n });\n return { outcome: 'dispatched', dispatch };\n }\n const resolution = resolve(target);\n switch (resolution.kind) {\n case 'destination': {\n // The view the link was followed in, by its own scroll behavior.\n const stage = context?.stage ?? ctx.tryGet(StageToken);\n if (!stage) return { outcome: 'destination', destination: resolution.destination };\n stage.goToDestination(resolution.destination);\n return { outcome: 'revealed' };\n }\n case 'uri': {\n const open = uriOpeners[0];\n if (!open || open(resolution.uri)) return { outcome: 'uri', uri: resolution.uri };\n return { outcome: 'reported', target };\n }\n case 'named':\n return { outcome: 'named', name: resolution.name };\n default:\n return { outcome: 'reported', target };\n }\n };\n\n const activate = (input: PdfLinkTarget | Link, context?: LinkActivateContext): LinkActivation => {\n const target = isLink(input) ? input.target : input;\n const linkContext: LinkActivateContext | undefined = isLink(input)\n ? { activate: input.activate, ref: input.ref, ...context }\n : context;\n const activation = perform(target, linkContext);\n activated.emit({ target, activation });\n return activation;\n };\n\n const getLinkAt = (page: PageRef | number, point: Point): Link | null => {\n let best: Link | null = null;\n for (const link of listLinks(page)) {\n if (!contains(link.bounds, point)) continue;\n if (\n !best ||\n link.bounds.width * link.bounds.height < best.bounds.width * best.bounds.height\n ) {\n best = link;\n }\n }\n return best;\n };\n\n const getLabel = (input: Link | PdfLinkTarget): string => {\n const target = isLink(input) ? input.target : input;\n switch (target.kind) {\n case 'uri':\n return target.uri;\n case 'goto':\n return 'Go to destination';\n case 'named':\n return target.name;\n default:\n return 'Link';\n }\n };\n\n const api: LinkHostCapability = {\n listLinks,\n getLink: (page, linkId) => listLinks(page).find((link) => link.id === linkId) ?? null,\n getLinkAt,\n listAllLinks: async (options) => {\n const layouts = ctx.document()?.pages ?? [];\n await ctx.cancellable(\n options?.signal,\n Promise.all(layouts.map((layout) => ensureLoaded(layout.ref, options))),\n );\n return layouts.flatMap((layout) => listLinks(layout.ref));\n },\n ensureLoaded,\n isLoaded: (page) => {\n const ref = ctx.getPage(page)?.ref;\n return !!ref && (annotationHost() !== null || pages.getStatus(ref) === 'ready');\n },\n getStatus: (page) => {\n const ref = ctx.getPage(page)?.ref;\n if (!ref) return 'idle';\n return annotationHost() ? 'ready' : pages.getStatus(ref);\n },\n resolve,\n activate,\n activateAt: (pageArgument, point, context) => {\n const page = ctx.pageOf(pageArgument).ref;\n const link = getLinkAt(page, point);\n return link ? activate(link, { page, ...context }) : null;\n },\n getLabel,\n onActivated: activated.on,\n onLoaded: loaded.on,\n registerUriOpener: (opener) => {\n uriOpeners.unshift(opener);\n return () => {\n const index = uriOpeners.indexOf(opener);\n if (index >= 0) uriOpeners.splice(index, 1);\n };\n },\n isNavigationEngaged: () => ctx.tryGet(InteractionToken)?.activeToolEnables('link-nav') ?? false,\n };\n\n return {\n api,\n connect() {\n connectLink(ctx);\n },\n };\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { ActionsToken } from '@embedpdf/plugin-actions/contract';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/contract';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\n\nimport { createLinkController } from './controller';\nimport { LinkToken } from './host-contract';\n\n/**\n * Clickable link regions and their activation. Document-scoped; needs the\n * interaction hub (the navigation tool tag), optionally the stage (reveal),\n * the annotation plugin (the folded link model) and actions (`/A` trees).\n */\nexport const linkPlugin = () =>\n definePlugin({\n id: 'link',\n token: LinkToken,\n scope: 'document',\n requires: [InteractionToken],\n optional: [StageToken, AnnotationToken, ActionsToken],\n create: createLinkController,\n });\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,15 +1,12 @@
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";
1
+ import { a as LinkCapability, c as PageDestination, i as LinkActivation, l as PdfLinkTarget, n as LinkActivateContext, o as LinkLoadedEvent, r as LinkActivatedEvent, s as LinkResolution, t as Link, u as LinkToken } from "./contract-C8by3cQG.cjs";
2
+ import { t as LinkHostCapability } from "./host-contract-WJZHqcPp.cjs";
2
3
  //#region src/link.plugin.d.ts
3
4
  /**
4
- * The link plugin: the NAVIGATION plane. Document-scoped; requires only the
5
- * interaction hub. Navigation works with no annotation plugin at all —
6
- * links are read from the engine's link DTOs. When the annotation plugin IS
7
- * present, its folded model becomes the data source (no double fetch) and a
8
- * Behavior keeps links navigation-owned while a `link-nav` tool is active
9
- * (pointer/pan/form-fill by default) — the single-active-tool hub IS the
10
- * mode switch: grab the link tool and every link becomes an editable rect.
5
+ * Clickable link regions and their activation. Document-scoped; needs the
6
+ * interaction hub (the navigation tool tag), optionally the stage (reveal),
7
+ * the annotation plugin (the folded link model) and actions (`/A` trees).
11
8
  */
12
- declare const linkPlugin: (config?: LinkPluginConfig) => import("@embedpdf/core").PluginDef<LinkState, LinkAction, LinkCapability>;
9
+ declare const linkPlugin: () => import("@embedpdf/core").PluginDef<void, LinkHostCapability, import("@embedpdf/core").NoSettings>;
13
10
  //#endregion
14
- export { type LinkActivateContext, type LinkActivateEvent, type LinkActivation, type LinkCapability, type LinkNavItem, type LinkPluginConfig, type LinkState, LinkToken, type PdfDestination, type PdfLinkTarget, linkPlugin };
11
+ export { Link, LinkActivateContext, LinkActivatedEvent, LinkActivation, LinkCapability, LinkLoadedEvent, LinkResolution, LinkToken, type PageDestination, type PdfLinkTarget, linkPlugin };
15
12
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,15 +1,12 @@
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";
1
+ import { a as LinkCapability, c as PageDestination, i as LinkActivation, l as PdfLinkTarget, n as LinkActivateContext, o as LinkLoadedEvent, r as LinkActivatedEvent, s as LinkResolution, t as Link, u as LinkToken } from "./contract-BL4jWtGT.js";
2
+ import { t as LinkHostCapability } from "./host-contract-BEEpcMUN.js";
2
3
  //#region src/link.plugin.d.ts
3
4
  /**
4
- * The link plugin: the NAVIGATION plane. Document-scoped; requires only the
5
- * interaction hub. Navigation works with no annotation plugin at all —
6
- * links are read from the engine's link DTOs. When the annotation plugin IS
7
- * present, its folded model becomes the data source (no double fetch) and a
8
- * Behavior keeps links navigation-owned while a `link-nav` tool is active
9
- * (pointer/pan/form-fill by default) — the single-active-tool hub IS the
10
- * mode switch: grab the link tool and every link becomes an editable rect.
5
+ * Clickable link regions and their activation. Document-scoped; needs the
6
+ * interaction hub (the navigation tool tag), optionally the stage (reveal),
7
+ * the annotation plugin (the folded link model) and actions (`/A` trees).
11
8
  */
12
- declare const linkPlugin: (config?: LinkPluginConfig) => import("@embedpdf/core").PluginDef<LinkState, LinkAction, LinkCapability>;
9
+ declare const linkPlugin: () => import("@embedpdf/core").PluginDef<void, LinkHostCapability, import("@embedpdf/core").NoSettings>;
13
10
  //#endregion
14
- export { type LinkActivateContext, type LinkActivateEvent, type LinkActivation, type LinkCapability, type LinkNavItem, type LinkPluginConfig, type LinkState, LinkToken, type PdfDestination, type PdfLinkTarget, linkPlugin };
11
+ export { Link, LinkActivateContext, LinkActivatedEvent, LinkActivation, LinkCapability, LinkLoadedEvent, LinkResolution, LinkToken, type PageDestination, type PdfLinkTarget, linkPlugin };
15
12
  //# sourceMappingURL=index.d.ts.map