@embedpdf/plugin-link 3.0.0-next.14 → 3.0.0-next.16
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/contract/host.cjs +10 -0
- package/dist/contract/host.cjs.map +1 -0
- package/dist/contract/host.d.cts +3 -0
- package/dist/contract/host.d.ts +3 -0
- package/dist/contract/host.js +9 -0
- package/dist/contract/host.js.map +1 -0
- package/dist/contract-B4x0DFCT.d.cts +143 -0
- package/dist/contract-CJ9mhj7i.js +46 -0
- package/dist/contract-CJ9mhj7i.js.map +1 -0
- package/dist/contract-DXB_v4as.cjs +56 -0
- package/dist/contract-DXB_v4as.cjs.map +1 -0
- package/dist/contract-DyTXDEwb.d.ts +143 -0
- package/dist/contract.cjs +2 -1
- package/dist/contract.d.cts +2 -2
- package/dist/contract.d.ts +2 -2
- package/dist/contract.js +2 -2
- package/dist/host-contract-DNwp7dlr.d.ts +23 -0
- package/dist/host-contract-c0QOjhO6.d.cts +23 -0
- package/dist/index.cjs +213 -175
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -10
- package/dist/index.d.ts +7 -10
- package/dist/index.js +214 -177
- package/dist/index.js.map +1 -1
- package/package.json +13 -9
- package/dist/contract-B8bEMeH8.d.ts +0 -92
- package/dist/contract-DGKEPl11.cjs +0 -11
- package/dist/contract-DGKEPl11.cjs.map +0 -1
- package/dist/contract-DO3vDsAV.d.cts +0 -92
- package/dist/contract-HRggERzU.js +0 -7
- package/dist/contract-HRggERzU.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,197 +1,246 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_contract = require("./contract-
|
|
2
|
+
const require_contract = require("./contract-DXB_v4as.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
|
|
10
|
-
let
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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/
|
|
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
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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) => require_contract.resolveLinkTarget(target, (page) => ctx.getPage(page) !== null);
|
|
110
|
+
const perform = (target, context) => {
|
|
111
|
+
const actions = ctx.tryGet(_embedpdf_plugin_actions_contract.ActionsToken);
|
|
112
|
+
if (actions && context?.activate) return {
|
|
113
|
+
outcome: "dispatched",
|
|
114
|
+
dispatch: actions.execute(context.activate, {
|
|
115
|
+
origin: "user",
|
|
116
|
+
source: {
|
|
117
|
+
kind: "link",
|
|
118
|
+
annotation: context.ref,
|
|
119
|
+
page: context.page
|
|
120
|
+
},
|
|
121
|
+
event: { scope: "activate" }
|
|
122
|
+
})
|
|
123
|
+
};
|
|
124
|
+
const resolution = resolve(target);
|
|
125
|
+
switch (resolution.kind) {
|
|
126
|
+
case "destination": {
|
|
127
|
+
const stage = context?.stage ?? ctx.tryGet(_embedpdf_plugin_stage_contract.StageToken);
|
|
128
|
+
if (!stage) return {
|
|
129
|
+
outcome: "destination",
|
|
130
|
+
destination: resolution.destination
|
|
86
131
|
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
132
|
+
stage.goToDestination(resolution.destination);
|
|
133
|
+
return { outcome: "revealed" };
|
|
134
|
+
}
|
|
135
|
+
case "uri": {
|
|
136
|
+
const open = uriOpeners[0];
|
|
137
|
+
if (!open || open(resolution.uri)) return {
|
|
138
|
+
outcome: "uri",
|
|
139
|
+
uri: resolution.uri
|
|
90
140
|
};
|
|
91
|
-
|
|
141
|
+
return {
|
|
92
142
|
outcome: "reported",
|
|
93
143
|
target
|
|
94
144
|
};
|
|
95
145
|
}
|
|
96
|
-
|
|
97
|
-
|
|
146
|
+
case "named": return {
|
|
147
|
+
outcome: "named",
|
|
148
|
+
name: resolution.name
|
|
149
|
+
};
|
|
150
|
+
default: return {
|
|
151
|
+
outcome: "reported",
|
|
152
|
+
target
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const activate = (input, context) => {
|
|
157
|
+
const target = isLink(input) ? input.target : input;
|
|
158
|
+
const linkContext = isLink(input) ? {
|
|
159
|
+
activate: input.activate,
|
|
160
|
+
ref: input.ref,
|
|
161
|
+
...context
|
|
162
|
+
} : context;
|
|
163
|
+
const activation = perform(target, linkContext);
|
|
164
|
+
activated.emit({
|
|
98
165
|
target,
|
|
99
166
|
activation
|
|
100
167
|
});
|
|
101
168
|
return activation;
|
|
102
169
|
};
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
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);
|
|
170
|
+
const getLinkAt = (page, point) => {
|
|
171
|
+
let best = null;
|
|
172
|
+
for (const link of listLinks(page)) {
|
|
173
|
+
if (!contains(link.bounds, point)) continue;
|
|
174
|
+
if (!best || link.bounds.width * link.bounds.height < best.bounds.width * best.bounds.height) best = link;
|
|
175
|
+
}
|
|
176
|
+
return best;
|
|
136
177
|
};
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
case "
|
|
143
|
-
|
|
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;
|
|
178
|
+
const getLabel = (input) => {
|
|
179
|
+
const target = isLink(input) ? input.target : input;
|
|
180
|
+
switch (target.kind) {
|
|
181
|
+
case "uri": return target.uri;
|
|
182
|
+
case "goto": return "Go to destination";
|
|
183
|
+
case "named": return target.name;
|
|
184
|
+
default: return "Link";
|
|
152
185
|
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
186
|
+
};
|
|
187
|
+
return {
|
|
188
|
+
api: {
|
|
189
|
+
listLinks,
|
|
190
|
+
getLink: (page, linkId) => listLinks(page).find((link) => link.id === linkId) ?? null,
|
|
191
|
+
getLinkAt,
|
|
192
|
+
listAllLinks: async (options) => {
|
|
193
|
+
const layouts = ctx.document()?.pages ?? [];
|
|
194
|
+
await ctx.cancellable(options?.signal, Promise.all(layouts.map((layout) => ensureLoaded(layout.ref, options))));
|
|
195
|
+
return layouts.flatMap((layout) => listLinks(layout.ref));
|
|
196
|
+
},
|
|
197
|
+
ensureLoaded,
|
|
198
|
+
isLoaded: (page) => {
|
|
199
|
+
const ref = ctx.getPage(page)?.ref;
|
|
200
|
+
return !!ref && (annotationHost() !== null || pages.getStatus(ref) === "ready");
|
|
201
|
+
},
|
|
202
|
+
getStatus: (page) => {
|
|
203
|
+
const ref = ctx.getPage(page)?.ref;
|
|
204
|
+
if (!ref) return "idle";
|
|
205
|
+
return annotationHost() ? "ready" : pages.getStatus(ref);
|
|
206
|
+
},
|
|
207
|
+
resolve,
|
|
208
|
+
activate,
|
|
209
|
+
activateAt: (pageArgument, point, context) => {
|
|
210
|
+
const page = ctx.pageOf(pageArgument).ref;
|
|
211
|
+
const link = getLinkAt(page, point);
|
|
212
|
+
return link ? activate(link, {
|
|
213
|
+
page,
|
|
214
|
+
...context
|
|
215
|
+
}) : null;
|
|
216
|
+
},
|
|
217
|
+
getLabel,
|
|
218
|
+
onActivated: activated.on,
|
|
219
|
+
onLoaded: loaded.on,
|
|
220
|
+
registerUriOpener: (opener) => {
|
|
221
|
+
uriOpeners.unshift(opener);
|
|
222
|
+
return () => {
|
|
223
|
+
const index = uriOpeners.indexOf(opener);
|
|
224
|
+
if (index >= 0) uriOpeners.splice(index, 1);
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
isNavigationEngaged: () => ctx.tryGet(_embedpdf_plugin_interaction_contract_host.InteractionToken)?.activeToolEnables("link-nav") ?? false
|
|
228
|
+
},
|
|
229
|
+
connect() {
|
|
230
|
+
connectLink(ctx);
|
|
178
231
|
}
|
|
179
|
-
}
|
|
232
|
+
};
|
|
180
233
|
}
|
|
181
234
|
//#endregion
|
|
182
235
|
//#region src/link.plugin.ts
|
|
183
236
|
/**
|
|
184
|
-
*
|
|
185
|
-
* interaction hub
|
|
186
|
-
*
|
|
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.
|
|
237
|
+
* Clickable link regions and their activation. Document-scoped; needs the
|
|
238
|
+
* interaction hub (the navigation tool tag), optionally the stage (reveal),
|
|
239
|
+
* the annotation plugin (the folded link model) and actions (`/A` trees).
|
|
191
240
|
*/
|
|
192
|
-
const linkPlugin = (
|
|
241
|
+
const linkPlugin = () => (0, _embedpdf_core.definePlugin)({
|
|
193
242
|
id: "link",
|
|
194
|
-
token:
|
|
243
|
+
token: require_contract_host.LinkToken,
|
|
195
244
|
scope: "document",
|
|
196
245
|
requires: [_embedpdf_plugin_interaction_contract.InteractionToken],
|
|
197
246
|
optional: [
|
|
@@ -199,22 +248,11 @@ const linkPlugin = (config) => (0, _embedpdf_core.definePlugin)({
|
|
|
199
248
|
_embedpdf_plugin_annotation_contract.AnnotationToken,
|
|
200
249
|
_embedpdf_plugin_actions_contract.ActionsToken
|
|
201
250
|
],
|
|
202
|
-
|
|
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
|
-
}
|
|
251
|
+
create: createLinkController
|
|
215
252
|
});
|
|
216
253
|
//#endregion
|
|
217
254
|
exports.LinkToken = require_contract.LinkToken;
|
|
218
255
|
exports.linkPlugin = linkPlugin;
|
|
256
|
+
exports.resolveLinkTarget = require_contract.resolveLinkTarget;
|
|
219
257
|
|
|
220
258
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -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","resolveLinkTarget","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 {\n resolveLinkTarget,\n type Link,\n type LinkActivateContext,\n type LinkActivatedEvent,\n type LinkActivation,\n type LinkLoadedEvent,\n type 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 // A page the document doesn't have can't be shown.\n const resolve = (target: PdfLinkTarget): LinkResolution =>\n resolveLinkTarget(target, (page) => ctx.getPage(page) !== null);\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
|
|
1
|
+
import { a as LinkCapability, c as PageDestination, d as LinkToken, i as LinkActivation, l as PdfLinkTarget, n as LinkActivateContext, o as LinkLoadedEvent, r as LinkActivatedEvent, s as LinkResolution, t as Link, u as resolveLinkTarget } from "./contract-B4x0DFCT.cjs";
|
|
2
|
+
import { t as LinkHostCapability } from "./host-contract-c0QOjhO6.cjs";
|
|
2
3
|
//#region src/link.plugin.d.ts
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
* interaction hub
|
|
6
|
-
*
|
|
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: (
|
|
9
|
+
declare const linkPlugin: () => import("@embedpdf/core").PluginDef<void, LinkHostCapability, import("@embedpdf/core").NoSettings>;
|
|
13
10
|
//#endregion
|
|
14
|
-
export {
|
|
11
|
+
export { Link, LinkActivateContext, LinkActivatedEvent, LinkActivation, LinkCapability, LinkLoadedEvent, LinkResolution, LinkToken, type PageDestination, type PdfLinkTarget, linkPlugin, resolveLinkTarget };
|
|
15
12
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import { a as LinkCapability, c as
|
|
1
|
+
import { a as LinkCapability, c as PageDestination, d as LinkToken, i as LinkActivation, l as PdfLinkTarget, n as LinkActivateContext, o as LinkLoadedEvent, r as LinkActivatedEvent, s as LinkResolution, t as Link, u as resolveLinkTarget } from "./contract-DyTXDEwb.js";
|
|
2
|
+
import { t as LinkHostCapability } from "./host-contract-DNwp7dlr.js";
|
|
2
3
|
//#region src/link.plugin.d.ts
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
* interaction hub
|
|
6
|
-
*
|
|
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: (
|
|
9
|
+
declare const linkPlugin: () => import("@embedpdf/core").PluginDef<void, LinkHostCapability, import("@embedpdf/core").NoSettings>;
|
|
13
10
|
//#endregion
|
|
14
|
-
export {
|
|
11
|
+
export { Link, LinkActivateContext, LinkActivatedEvent, LinkActivation, LinkCapability, LinkLoadedEvent, LinkResolution, LinkToken, type PageDestination, type PdfLinkTarget, linkPlugin, resolveLinkTarget };
|
|
15
12
|
//# sourceMappingURL=index.d.ts.map
|