@embedpdf/plugin-page-edit 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/index.cjs CHANGED
@@ -1,119 +1,319 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_contract = require("./contract-UeGRCn-t.cjs");
2
+ const require_contract = require("./contract-B4DeVaKv.cjs");
3
3
  let _embedpdf_core = require("@embedpdf/core");
4
- //#region src/capability.ts
5
- /**
6
- * Authorization for structural page edits. Maps to PDF bit 11 (ASSEMBLE =
7
- * insert/rotate/delete pages). The engine ALSO enforces this — every
8
- * page-structure verb throws `PermissionDenied` without it, identically on
9
- * local and cloud — so `canEdit()` is the UI mirror of a real guard, not the
10
- * guard itself. Typed as `DocCapability` so a typo is a compile error.
11
- */
12
- const ASSEMBLE_CAPABILITY = "doc.pages.assemble";
4
+ //#region src/model.ts
5
+ const initialPageEditState = () => ({ selected: [] });
6
+ /** The selection becomes `refs`, each page once. */
7
+ function selected(state, refs) {
8
+ const numbers = refs.map((ref) => ref.objectNumber);
9
+ const unique = refs.filter((_ref, i) => numbers.indexOf(numbers[i]) === i);
10
+ return unique.length === state.selected.length && unique.every((ref, i) => ref.objectNumber === state.selected[i].objectNumber) ? state : {
11
+ ...state,
12
+ selected: unique
13
+ };
14
+ }
15
+ //#endregion
16
+ //#region src/controller.ts
13
17
  /**
14
- * Forwards structural edits to the document's engine handle, addressed by PON.
15
- * The handle (`ctx.doc`) and the page registry (`ctx.document()`, kept in sync
16
- * by the kernel's event→registry bridge) are both already on the plugin
17
- * context — this capability is the thin layer that turns the relative rotate
18
- * gesture into the engine's absolute wire.
18
+ * The page-edit controller: page edits that take refs or indexes, and the
19
+ * page selection. Turning a page relative to its own rotation, and fixing a
20
+ * position's page, live here once instead of in every app's click handler.
21
+ *
22
+ * A verb refuses before anything starts (permission, pages, position), then
23
+ * stages its edit as one change on the document's queue (`ctx.changes`): one
24
+ * step of the history, sent after everything staged before it. The page list
25
+ * shows it once the engine answers; nothing predicts pages yet.
19
26
  */
20
- function createPageEditCapability(ctx) {
21
- const requireDoc = () => {
22
- const doc = ctx.doc;
23
- if (!doc) throw new Error("[page-edit] no document bound");
24
- return doc;
27
+ /** Inserting, rotating and deleting pages (PDF permission bit 4 or 11). The engine enforces it too. */
28
+ const ASSEMBLE = "doc.pages.assemble";
29
+ /** Copying pages out of a document is a partial download. The engine enforces it too. */
30
+ const DOWNLOAD = "doc.download";
31
+ /** The size of a blank page when the document has no page to match: Letter, in points. */
32
+ const LETTER_SIZE = {
33
+ width: 612,
34
+ height: 792
35
+ };
36
+ const NO_PAGES = Object.freeze([]);
37
+ const NO_LAYOUTS = Object.freeze([]);
38
+ function createPageEditController(ctx) {
39
+ /** Engine calls outside the guarded `ctx.doc` (another document, a scratch document) map their errors here. */
40
+ const mapErrors = async (work) => {
41
+ try {
42
+ return await work();
43
+ } catch (error) {
44
+ throw (0, _embedpdf_core.toPluginError)("page-edit", error);
45
+ }
25
46
  };
26
- /** Current absolute rotation of a page from the registry; 0 if unknown. */
27
- const rotationOf = (pon) => {
28
- return (ctx.document()?.pages.find((p) => p.pageObjectNumber === pon))?.rotation ?? 0;
47
+ const registry = () => ctx.document()?.pages ?? NO_LAYOUTS;
48
+ const noPages = () => new _embedpdf_core.PluginError("invalid-input", "page-edit", "no pages given");
49
+ /**
50
+ * A verb's pages as refs, checked when it is called: an index names the
51
+ * page at that position now, which is the page the user clicked.
52
+ */
53
+ const refsOf = (pages) => {
54
+ if (pages.length === 0) throw noPages();
55
+ return pages.map((page) => ctx.pageOf(page).ref);
56
+ };
57
+ /** A position, its page checked when the verb is called, like the verb's own pages. Default `'end'`. */
58
+ const positionOf = (position) => {
59
+ if (!position || position === "start" || position === "end") return position ?? "end";
60
+ if ("after" in position) return { after: ctx.pageOf(position.after).ref };
61
+ return { before: ctx.pageOf(position.before).ref };
29
62
  };
30
63
  /**
31
- * Resolve a placement to the engine's index wire, from the registry at
32
- * call time — the PON anchors are why "add after this thumbnail" survives
33
- * a concurrent reorder. Returns the anchor page too, so `addBlank` can
34
- * default its size without a second lookup.
64
+ * Default blank-page size: the page the position names, else the page the
65
+ * new ones go next to (the first or the last), else Letter.
35
66
  */
36
- const resolvePlacement = (placement) => {
37
- if (!placement) return {
38
- destIndex: void 0,
39
- anchor: void 0
40
- };
41
- if ("index" in placement) return {
42
- destIndex: placement.index,
43
- anchor: void 0
44
- };
45
- const pon = "after" in placement ? placement.after : placement.before;
46
- const anchor = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);
47
- if (!anchor) throw new Error(`[page-edit] placement page not found: ${pon}`);
48
- return {
49
- destIndex: "after" in placement ? anchor.index + 1 : anchor.index,
50
- anchor
51
- };
67
+ const sizeAt = (position) => {
68
+ if (typeof position === "object") return ctx.pageOf("after" in position ? position.after : position.before).size;
69
+ const pages = registry();
70
+ if (pages.length === 0) return LETTER_SIZE;
71
+ return (position === "start" ? pages[0] : pages[pages.length - 1]).size;
72
+ };
73
+ const getSelected = (0, _embedpdf_core.memo)(() => [registry(), ctx.state.get().selected], (pages, refs) => {
74
+ if (refs.length === 0) return NO_PAGES;
75
+ const present = new Set(pages.map((page) => page.ref.objectNumber));
76
+ return refs.filter((ref) => present.has(ref.objectNumber));
77
+ });
78
+ const select = (pages) => {
79
+ const refs = pages.flatMap((page) => {
80
+ const found = ctx.getPage(page);
81
+ return found ? [found.ref] : [];
82
+ });
83
+ ctx.state.update(selected, refs);
84
+ };
85
+ /** Refuse before anything starts: without the right, or once the caller cancelled. */
86
+ const refuse = (permission, operation, options) => {
87
+ ctx.assertAllowed(permission, operation);
88
+ if (options?.signal?.aborted) throw new _embedpdf_core.PluginError("operation-cancelled", "page-edit", `${operation} was cancelled`);
52
89
  };
53
90
  /**
54
- * Default blank-page size: the insertion point's predecessor (the page
55
- * the new one will follow), else the first page, else — only reachable
56
- * with an empty registry, which an open document never has — US Letter.
91
+ * Stage one user action: one change, one step of the history. Its label
92
+ * counts the pages it acts on, when the verb knows how many.
57
93
  */
58
- const neighbourSize = (destIndex) => {
59
- const pages = ctx.document()?.pages ?? [];
60
- if (pages.length === 0) return {
61
- width: 612,
62
- height: 792
94
+ const stageChange = (verb, count, ops, undo) => {
95
+ const label = count === null ? { key: `pages.${verb}` } : {
96
+ key: `pages.${verb}`,
97
+ count
63
98
  };
64
- if (destIndex === void 0) return pages[pages.length - 1].size;
65
- return pages[Math.max(0, Math.min(destIndex - 1, pages.length - 1))].size;
99
+ return ctx.changes.stage({
100
+ label,
101
+ ops,
102
+ ...undo ? { undo } : {}
103
+ });
104
+ };
105
+ /** The engine's answer to a staged change: its first item. */
106
+ const answer = async (change, options) => {
107
+ try {
108
+ return (await ctx.cancellable(options?.signal, change.result)).items[0];
109
+ } catch (error) {
110
+ throw (0, _embedpdf_core.toPluginError)("page-edit", error);
111
+ }
112
+ };
113
+ /** What an insert's answer names: the new pages. */
114
+ const inserted = async (change, options) => {
115
+ const item = await answer(change, options);
116
+ return { pages: "insertedPages" in item ? item.insertedPages : NO_PAGES };
66
117
  };
67
- return {
68
- canEdit() {
69
- return ctx.doc?.security.allows(ASSEMBLE_CAPABILITY) ?? false;
118
+ /**
119
+ * A page's rotation once this session's changes on their way apply: the
120
+ * last rotation one of them gives it, else the page's own. What a turn
121
+ * starts from, so two quick quarter turns are a half turn.
122
+ */
123
+ const rotationOf = (ref) => {
124
+ let rotation = ctx.pageOf(ref).rotation;
125
+ for (const change of ctx.changes.pending()) for (const op of change.shows) {
126
+ if (op.type !== "pages.rotate") continue;
127
+ if (op.pages.some((page) => page.objectNumber === ref.objectNumber)) rotation = op.rotation;
128
+ }
129
+ return rotation;
130
+ };
131
+ /**
132
+ * Rotate pages as one change: one op per rotation they end up with. What
133
+ * undoing it looks like is each page turned back, so a turn right after an
134
+ * undo starts from where the undo leaves the page.
135
+ */
136
+ const rotate = (refs, rotationAfter) => {
137
+ const after = /* @__PURE__ */ new Map();
138
+ const before = /* @__PURE__ */ new Map();
139
+ for (const ref of refs) {
140
+ const add = (groups, rotation) => groups.set(rotation, [...groups.get(rotation) ?? [], ref]);
141
+ add(before, rotationOf(ref));
142
+ add(after, rotationAfter(ref));
143
+ }
144
+ const opsOf = (groups) => [...groups].map(([rotation, pages]) => ({
145
+ type: "pages.rotate",
146
+ pages,
147
+ rotation
148
+ }));
149
+ return stageChange("rotate", refs.length, opsOf(after), opsOf(before));
150
+ };
151
+ /**
152
+ * The pages as they show: every change on its way sent and answered first,
153
+ * so a copy holds the edits made before it.
154
+ */
155
+ const settled = async (options) => {
156
+ ctx.changes.sendHolds();
157
+ await ctx.cancellable(options?.signal, ctx.changes.whenSettled());
158
+ };
159
+ /** Copy pages of this document out, as they show. */
160
+ const extractShown = async (refs, options) => {
161
+ await settled(options);
162
+ return ctx.cancellable(options?.signal, ctx.doc.pages.extract([...refs]));
163
+ };
164
+ /** Stage the insert of a PDF's pages; resolves the new pages. */
165
+ const insertBytes = (verb, count, bytes, position, options) => {
166
+ const source = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
167
+ const change = stageChange(verb, count, [{
168
+ type: "pages.insert",
169
+ source,
170
+ position
171
+ }]);
172
+ return inserted(change, options);
173
+ };
174
+ /** Some pages of a PDF: open the bytes beside the document and extract them. */
175
+ const pagesOfBytes = async (bytes, pageIndexes, signal) => {
176
+ const source = await mapErrors(() => ctx.engine.open({
177
+ kind: "bytes",
178
+ id: `page-edit-source-${Date.now().toString(36)}`,
179
+ bytes: new Uint8Array(bytes)
180
+ }, { scope: ["*"] }));
181
+ try {
182
+ const layout = await mapErrors(() => ctx.cancellable(signal, source.pages.list()));
183
+ const refs = pageIndexes.map((index) => {
184
+ const page = layout.pages[index];
185
+ if (!page) throw new _embedpdf_core.PluginError("not-found", "page-edit", `the PDF has no page ${index}`);
186
+ return page.ref;
187
+ });
188
+ return await mapErrors(() => ctx.cancellable(signal, source.pages.extract(refs)));
189
+ } finally {
190
+ await source.close();
191
+ }
192
+ };
193
+ return { api: {
194
+ canEdit: () => ctx.allows(ASSEMBLE),
195
+ canExtract: () => ctx.allows(DOWNLOAD),
196
+ getSelected,
197
+ select,
198
+ rotateBy: async (pages, delta, options) => {
199
+ refuse(ASSEMBLE, "pageEdit.rotateBy", options);
200
+ const refs = refsOf(pages);
201
+ const change = rotate(refs, (ref) => ((rotationOf(ref) + delta) % 360 + 360) % 360);
202
+ await answer(change, options);
203
+ },
204
+ setRotation: async (pages, rotation, options) => {
205
+ refuse(ASSEMBLE, "pageEdit.setRotation", options);
206
+ const change = rotate(refsOf(pages), () => rotation);
207
+ await answer(change, options);
70
208
  },
71
- rotateBy(pon, delta) {
72
- const next = ((rotationOf(pon) + delta) % 360 + 360) % 360;
73
- return requireDoc().pages.rotate([pon], next);
209
+ reorder: async (pages, position, options) => {
210
+ refuse(ASSEMBLE, "pageEdit.reorder", options);
211
+ const refs = refsOf(pages);
212
+ const change = stageChange("reorder", refs.length, [{
213
+ type: "pages.reorder",
214
+ pages: refs,
215
+ position: positionOf(position)
216
+ }]);
217
+ await answer(change, options);
74
218
  },
75
- setRotation(pons, rotation) {
76
- return requireDoc().pages.rotate(pons, rotation);
219
+ delete: async (pages, options) => {
220
+ refuse(ASSEMBLE, "pageEdit.delete", options);
221
+ const refs = refsOf(pages);
222
+ const deleting = new Set(refs.map((ref) => ref.objectNumber));
223
+ if (registry().every((page) => deleting.has(page.ref.objectNumber))) throw new _embedpdf_core.PluginError("invalid-input", "page-edit", "a document keeps at least one page");
224
+ const change = stageChange("delete", refs.length, [{
225
+ type: "pages.delete",
226
+ pages: refs
227
+ }]);
228
+ await answer(change, options);
77
229
  },
78
- move(pons, destIndex) {
79
- return requireDoc().pages.move(pons, destIndex);
230
+ insertBlank: async (options = {}) => {
231
+ refuse(ASSEMBLE, "pageEdit.insertBlank", options);
232
+ const position = positionOf(options.position);
233
+ const count = options.count ?? 1;
234
+ const size = options.size ?? sizeAt(position);
235
+ const change = stageChange("insertBlank", count, [{
236
+ type: "pages.insertBlank",
237
+ spec: {
238
+ size,
239
+ count
240
+ },
241
+ position
242
+ }]);
243
+ return inserted(change, options);
80
244
  },
81
- delete(pons) {
82
- return requireDoc().pages.delete(pons);
245
+ insertFromBytes: async (bytes, options = {}) => {
246
+ refuse(ASSEMBLE, "pageEdit.insertFromBytes", options);
247
+ const position = positionOf(options.position);
248
+ const { pageIndexes, signal } = options;
249
+ if (pageIndexes?.length === 0) throw noPages();
250
+ const source = pageIndexes ? await pagesOfBytes(bytes, pageIndexes, signal) : bytes;
251
+ return insertBytes("insert", pageIndexes?.length ?? null, source, position, options);
83
252
  },
84
- addBlank(opts = {}) {
85
- const doc = requireDoc();
86
- const { destIndex, anchor } = resolvePlacement(opts.placement);
87
- const size = opts.size ?? anchor?.size ?? neighbourSize(destIndex);
88
- return doc.pages.insertBlank({
89
- size,
90
- count: opts.count
91
- }, destIndex);
253
+ insertFromDocument: async (documentId, pages, options = {}) => {
254
+ refuse(ASSEMBLE, "pageEdit.insertFromDocument", options);
255
+ if (pages.length === 0) throw noPages();
256
+ const other = ctx.documentHandle(documentId);
257
+ if (!other) throw new _embedpdf_core.PluginError("not-found", "page-edit", `document '${documentId}' is not open`);
258
+ const otherPages = ctx.get(_embedpdf_core.DocumentsToken).listPages(documentId);
259
+ const refs = pages.map((page) => {
260
+ const found = typeof page === "number" ? otherPages[page] : otherPages.find((candidate) => candidate.ref.objectNumber === page.objectNumber);
261
+ if (!found) throw new _embedpdf_core.PluginError("not-found", "page-edit", `document '${documentId}' has no such page`);
262
+ return found.ref;
263
+ });
264
+ const position = positionOf(options.position);
265
+ const bytes = await mapErrors(() => ctx.cancellable(options.signal, other.pages.extract(refs)));
266
+ return insertBytes("insert", refs.length, bytes, position, options);
92
267
  },
93
- insert(bytes, opts = {}) {
94
- const doc = requireDoc();
95
- const { destIndex } = resolvePlacement(opts.placement);
96
- return doc.pages.insert(bytes, destIndex);
268
+ duplicate: async (pages, options = {}) => {
269
+ refuse(ASSEMBLE, "pageEdit.duplicate", options);
270
+ ctx.assertAllowed(DOWNLOAD, "pageEdit.duplicate");
271
+ const refs = refsOf(pages);
272
+ const position = options.position ? positionOf(options.position) : { after: refs[refs.length - 1] };
273
+ const bytes = await extractShown(refs, options);
274
+ return insertBytes("duplicate", refs.length, bytes, position, options);
275
+ },
276
+ extract: async (pages, options) => {
277
+ refuse(DOWNLOAD, "pageEdit.extract", options);
278
+ return extractShown(refsOf(pages), options);
97
279
  }
98
- };
280
+ } };
99
281
  }
100
282
  //#endregion
101
283
  //#region src/page-edit.plugin.ts
102
284
  /**
103
- * Document-scoped, stateless: turns the engine handle's page service into a
104
- * PON-addressed edit capability. The relative→absolute rotation lives in the
105
- * capability so the four framework adapters never re-derive it, and this is the
106
- * home for client-side edit state (pending/optimistic/undo) when it lands —
107
- * adapters keep their thin hook over `PageEditToken` unchanged.
285
+ * Document-scoped: the document's page edits, each one change of its
286
+ * history, and which of its pages are selected. Turning a page relative to
287
+ * its own rotation lives in the controller, so no app re-derives it.
108
288
  */
109
289
  const pageEditPlugin = () => (0, _embedpdf_core.definePlugin)({
110
290
  id: "page-edit",
111
291
  scope: "document",
112
292
  token: require_contract.PageEditToken,
113
- capability: createPageEditCapability
293
+ state: initialPageEditState,
294
+ create: createPageEditController
295
+ });
296
+ //#endregion
297
+ //#region src/state.ts
298
+ /**
299
+ * The pages page's State table as code: what `usePageEditState()` returns,
300
+ * and the same fields in every other framework.
301
+ */
302
+ const pageEditState = (0, _embedpdf_core.defineState)(require_contract.PageEditToken, {
303
+ read: (pageEdit) => ({
304
+ selected: pageEdit.getSelected(),
305
+ canEdit: pageEdit.canEdit(),
306
+ canExtract: pageEdit.canExtract()
307
+ }),
308
+ empty: {
309
+ selected: [],
310
+ canEdit: false,
311
+ canExtract: false
312
+ }
114
313
  });
115
314
  //#endregion
116
315
  exports.PageEditToken = require_contract.PageEditToken;
117
316
  exports.pageEditPlugin = pageEditPlugin;
317
+ exports.pageEditState = pageEditState;
118
318
 
119
319
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["PageEditToken"],"sources":["../src/capability.ts","../src/page-edit.plugin.ts"],"sourcesContent":["import type {\n PluginContext,\n PageObjectNumber,\n PageRotation,\n DocCapability,\n PdfSize,\n} from '@embedpdf/core';\nimport type { PageEditCapability, PagePlacement } from './types';\n\n/**\n * Authorization for structural page edits. Maps to PDF bit 11 (ASSEMBLE =\n * insert/rotate/delete pages). The engine ALSO enforces this — every\n * page-structure verb throws `PermissionDenied` without it, identically on\n * local and cloud — so `canEdit()` is the UI mirror of a real guard, not the\n * guard itself. Typed as `DocCapability` so a typo is a compile error.\n */\nconst ASSEMBLE_CAPABILITY: DocCapability = 'doc.pages.assemble';\n\n/**\n * Forwards structural edits to the document's engine handle, addressed by PON.\n * The handle (`ctx.doc`) and the page registry (`ctx.document()`, kept in sync\n * by the kernel's event→registry bridge) are both already on the plugin\n * context — this capability is the thin layer that turns the relative rotate\n * gesture into the engine's absolute wire.\n */\nexport function createPageEditCapability(ctx: PluginContext<unknown>): PageEditCapability {\n const requireDoc = () => {\n const doc = ctx.doc;\n if (!doc) throw new Error('[page-edit] no document bound');\n return doc;\n };\n\n /** Current absolute rotation of a page from the registry; 0 if unknown. */\n const rotationOf = (pon: PageObjectNumber): PageRotation => {\n const page = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n return page?.rotation ?? 0;\n };\n\n /**\n * Resolve a placement to the engine's index wire, from the registry at\n * call time — the PON anchors are why \"add after this thumbnail\" survives\n * a concurrent reorder. Returns the anchor page too, so `addBlank` can\n * default its size without a second lookup.\n */\n const resolvePlacement = (placement?: PagePlacement) => {\n if (!placement) return { destIndex: undefined, anchor: undefined };\n if ('index' in placement) return { destIndex: placement.index, anchor: undefined };\n const pon = 'after' in placement ? placement.after : placement.before;\n const anchor = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n if (!anchor) throw new Error(`[page-edit] placement page not found: ${pon}`);\n return { destIndex: 'after' in placement ? anchor.index + 1 : anchor.index, anchor };\n };\n\n /**\n * Default blank-page size: the insertion point's predecessor (the page\n * the new one will follow), else the first page, else — only reachable\n * with an empty registry, which an open document never has — US Letter.\n */\n const neighbourSize = (destIndex: number | undefined): PdfSize => {\n const pages = ctx.document()?.pages ?? [];\n if (pages.length === 0) return { width: 612, height: 792 };\n if (destIndex === undefined) return pages[pages.length - 1].size;\n return pages[Math.max(0, Math.min(destIndex - 1, pages.length - 1))].size;\n };\n\n return {\n canEdit() {\n // Wildcard-aware predicate (mirrors the engine's own enforcement),\n // identical on both engines. NOT `effectiveScope.includes(...)` — that\n // enumeration drops the `*` admin grant, so it would hide the UI on a\n // default admin open even though the engine allows the edit.\n return ctx.doc?.security.allows(ASSEMBLE_CAPABILITY) ?? false;\n },\n\n rotateBy(pon, delta) {\n // Wrap to [0, 360) — the double-mod keeps -90 from current 0 landing on 270.\n const next = ((((rotationOf(pon) + delta) % 360) + 360) % 360) as PageRotation;\n return requireDoc().pages.rotate([pon], next);\n },\n\n setRotation(pons, rotation) {\n return requireDoc().pages.rotate(pons, rotation);\n },\n\n move(pons, destIndex) {\n return requireDoc().pages.move(pons, destIndex);\n },\n\n delete(pons) {\n return requireDoc().pages.delete(pons);\n },\n\n addBlank(opts = {}) {\n const doc = requireDoc();\n const { destIndex, anchor } = resolvePlacement(opts.placement);\n // PON placements match the anchor the user is looking at; everything\n // else matches the neighbour the new page will follow.\n const size = opts.size ?? anchor?.size ?? neighbourSize(destIndex);\n return doc.pages.insertBlank({ size, count: opts.count }, destIndex);\n },\n\n insert(bytes, opts = {}) {\n const doc = requireDoc();\n const { destIndex } = resolvePlacement(opts.placement);\n return doc.pages.insert(bytes, destIndex);\n },\n };\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { createPageEditCapability } from './capability';\nimport { PageEditToken } from './types';\n\n/**\n * Document-scoped, stateless: turns the engine handle's page service into a\n * PON-addressed edit capability. The relative→absolute rotation lives in the\n * capability so the four framework adapters never re-derive it, and this is the\n * home for client-side edit state (pending/optimistic/undo) when it lands —\n * adapters keep their thin hook over `PageEditToken` unchanged.\n */\nexport const pageEditPlugin = () =>\n definePlugin({\n id: 'page-edit',\n scope: 'document',\n token: PageEditToken,\n capability: createPageEditCapability,\n });\n"],"mappings":";;;;;;;;;;;AAgBA,MAAM,sBAAqC;;;;;;;;AAS3C,SAAgB,yBAAyB,KAAiD;CACxF,MAAM,mBAAmB;EACvB,MAAM,MAAM,IAAI;EAChB,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,+BAA+B;EACzD,OAAO;CACT;;CAGA,MAAM,cAAc,QAAwC;EAE1D,QADa,IAAI,SAAS,CAAC,EAAE,MAAM,MAAM,MAAM,EAAE,qBAAqB,GAAG,EAAA,EAC5D,YAAY;CAC3B;;;;;;;CAQA,MAAM,oBAAoB,cAA8B;EACtD,IAAI,CAAC,WAAW,OAAO;GAAE,WAAW,KAAA;GAAW,QAAQ,KAAA;EAAU;EACjE,IAAI,WAAW,WAAW,OAAO;GAAE,WAAW,UAAU;GAAO,QAAQ,KAAA;EAAU;EACjF,MAAM,MAAM,WAAW,YAAY,UAAU,QAAQ,UAAU;EAC/D,MAAM,SAAS,IAAI,SAAS,CAAC,EAAE,MAAM,MAAM,MAAM,EAAE,qBAAqB,GAAG;EAC3E,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,yCAAyC,KAAK;EAC3E,OAAO;GAAE,WAAW,WAAW,YAAY,OAAO,QAAQ,IAAI,OAAO;GAAO;EAAO;CACrF;;;;;;CAOA,MAAM,iBAAiB,cAA2C;EAChE,MAAM,QAAQ,IAAI,SAAS,CAAC,EAAE,SAAS,CAAC;EACxC,IAAI,MAAM,WAAW,GAAG,OAAO;GAAE,OAAO;GAAK,QAAQ;EAAI;EACzD,IAAI,cAAc,KAAA,GAAW,OAAO,MAAM,MAAM,SAAS,EAAE,CAAC;EAC5D,OAAO,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,YAAY,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC;CACvE;CAEA,OAAO;EACL,UAAU;GAKR,OAAO,IAAI,KAAK,SAAS,OAAO,mBAAmB,KAAK;EAC1D;EAEA,SAAS,KAAK,OAAO;GAEnB,MAAM,SAAW,WAAW,GAAG,IAAI,SAAS,MAAO,OAAO;GAC1D,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,GAAG,IAAI;EAC9C;EAEA,YAAY,MAAM,UAAU;GAC1B,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,MAAM,QAAQ;EACjD;EAEA,KAAK,MAAM,WAAW;GACpB,OAAO,WAAW,CAAC,CAAC,MAAM,KAAK,MAAM,SAAS;EAChD;EAEA,OAAO,MAAM;GACX,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,IAAI;EACvC;EAEA,SAAS,OAAO,CAAC,GAAG;GAClB,MAAM,MAAM,WAAW;GACvB,MAAM,EAAE,WAAW,WAAW,iBAAiB,KAAK,SAAS;GAG7D,MAAM,OAAO,KAAK,QAAQ,QAAQ,QAAQ,cAAc,SAAS;GACjE,OAAO,IAAI,MAAM,YAAY;IAAE;IAAM,OAAO,KAAK;GAAM,GAAG,SAAS;EACrE;EAEA,OAAO,OAAO,OAAO,CAAC,GAAG;GACvB,MAAM,MAAM,WAAW;GACvB,MAAM,EAAE,cAAc,iBAAiB,KAAK,SAAS;GACrD,OAAO,IAAI,MAAM,OAAO,OAAO,SAAS;EAC1C;CACF;AACF;;;;;;;;;;AChGA,MAAa,wBAAA,GAAA,eAAA,aAAA,CACE;CACX,IAAI;CACJ,OAAO;CACP,OAAOA,iBAAAA;CACP,YAAY;AACd,CAAC"}
1
+ {"version":3,"file":"index.cjs","names":["PluginError","DocumentsToken","PageEditToken","PageEditToken"],"sources":["../src/model.ts","../src/controller.ts","../src/page-edit.plugin.ts","../src/state.ts"],"sourcesContent":["/**\n * The page-edit session state: which pages are selected. A plain list of\n * refs, changed only by the transitions here; the controller reads it over the\n * document's pages, so a page that went leaves the selection.\n */\nimport type { PageRef } from '@embedpdf/core';\n\nexport interface PageEditState {\n /** The selected pages, in the order they were selected. */\n readonly selected: readonly PageRef[];\n}\n\nexport const initialPageEditState = (): PageEditState => ({ selected: [] });\n\n/** The selection becomes `refs`, each page once. */\nexport function selected(state: PageEditState, refs: readonly PageRef[]): PageEditState {\n const numbers = refs.map((ref) => ref.objectNumber);\n const unique = refs.filter((_ref, i) => numbers.indexOf(numbers[i]!) === i);\n const same =\n unique.length === state.selected.length &&\n unique.every((ref, i) => ref.objectNumber === state.selected[i]!.objectNumber);\n return same ? state : { ...state, selected: unique };\n}\n","/**\n * The page-edit controller: page edits that take refs or indexes, and the\n * page selection. Turning a page relative to its own rotation, and fixing a\n * position's page, live here once instead of in every app's click handler.\n *\n * A verb refuses before anything starts (permission, pages, position), then\n * stages its edit as one change on the document's queue (`ctx.changes`): one\n * step of the history, sent after everything staged before it. The page list\n * shows it once the engine answers; nothing predicts pages yet.\n */\nimport { memo, PluginError, toPluginError, DocumentsToken } from '@embedpdf/core';\nimport type {\n ChangeLabel,\n DocCapability,\n OperationOptions,\n PageLayout,\n PagePosition,\n PageRef,\n PdfRotation,\n PdfSize,\n PendingChange,\n PluginContext,\n PredictedOp,\n} from '@embedpdf/core';\n\nimport type { PageEditCapability, PageEditInsertResult } from './contract';\nimport { selected, type PageEditState } from './model';\n\n/** Inserting, rotating and deleting pages (PDF permission bit 4 or 11). The engine enforces it too. */\nconst ASSEMBLE: DocCapability = 'doc.pages.assemble';\n/** Copying pages out of a document is a partial download. The engine enforces it too. */\nconst DOWNLOAD: DocCapability = 'doc.download';\n\n/** The size of a blank page when the document has no page to match: Letter, in points. */\nconst LETTER_SIZE: PdfSize = { width: 612, height: 792 };\n\nconst NO_PAGES: readonly PageRef[] = Object.freeze([]);\nconst NO_LAYOUTS: readonly PageLayout[] = Object.freeze([]);\n\n/** A page op, as a verb stages it. */\ntype PageOp = Extract<PredictedOp, { type: `pages.${string}` }>;\n\n/** What a change is called in history and refusals. */\ntype PageVerb = 'rotate' | 'reorder' | 'delete' | 'insertBlank' | 'insert' | 'duplicate';\n\nexport function createPageEditController(ctx: PluginContext<PageEditState>) {\n /** Engine calls outside the guarded `ctx.doc` (another document, a scratch document) map their errors here. */\n const mapErrors = async <T>(work: () => Promise<T>): Promise<T> => {\n try {\n return await work();\n } catch (error) {\n throw toPluginError('page-edit', error);\n }\n };\n const registry = (): readonly PageLayout[] => ctx.document()?.pages ?? NO_LAYOUTS;\n const noPages = () => new PluginError('invalid-input', 'page-edit', 'no pages given');\n\n /**\n * A verb's pages as refs, checked when it is called: an index names the\n * page at that position now, which is the page the user clicked.\n */\n const refsOf = (pages: readonly (PageRef | number)[]): PageRef[] => {\n if (pages.length === 0) throw noPages();\n return pages.map((page) => ctx.pageOf(page).ref);\n };\n\n /** A position, its page checked when the verb is called, like the verb's own pages. Default `'end'`. */\n const positionOf = (position: PagePosition | undefined): PagePosition => {\n if (!position || position === 'start' || position === 'end') return position ?? 'end';\n if ('after' in position) return { after: ctx.pageOf(position.after).ref };\n return { before: ctx.pageOf(position.before).ref };\n };\n /**\n * Default blank-page size: the page the position names, else the page the\n * new ones go next to (the first or the last), else Letter.\n */\n const sizeAt = (position: PagePosition): PdfSize => {\n if (typeof position === 'object') {\n return ctx.pageOf('after' in position ? position.after : position.before).size;\n }\n const pages = registry();\n if (pages.length === 0) return LETTER_SIZE;\n return (position === 'start' ? pages[0] : pages[pages.length - 1]).size;\n };\n\n /* ── the selection ─────────────────────────────────────────────────────── */\n\n const getSelected = memo(\n () => [registry(), ctx.state.get().selected] as const,\n (pages, refs) => {\n if (refs.length === 0) return NO_PAGES;\n const present = new Set(pages.map((page) => page.ref.objectNumber));\n return refs.filter((ref) => present.has(ref.objectNumber));\n },\n );\n const select = (pages: readonly (PageRef | number)[]): void => {\n const refs = pages.flatMap((page) => {\n const found = ctx.getPage(page);\n return found ? [found.ref] : [];\n });\n ctx.state.update(selected, refs);\n };\n\n /* ── writes ────────────────────────────────────────────────────────────── */\n\n /** Refuse before anything starts: without the right, or once the caller cancelled. */\n const refuse = (\n permission: DocCapability,\n operation: string,\n options: OperationOptions | undefined,\n ): void => {\n ctx.assertAllowed(permission, operation);\n if (options?.signal?.aborted) {\n throw new PluginError('operation-cancelled', 'page-edit', `${operation} was cancelled`);\n }\n };\n\n /**\n * Stage one user action: one change, one step of the history. Its label\n * counts the pages it acts on, when the verb knows how many.\n */\n const stageChange = (\n verb: PageVerb,\n count: number | null,\n ops: readonly PageOp[],\n undo?: readonly PageOp[],\n ): PendingChange => {\n const label: ChangeLabel =\n count === null ? { key: `pages.${verb}` } : { key: `pages.${verb}`, count };\n return ctx.changes.stage({ label, ops, ...(undo ? { undo } : {}) });\n };\n\n /** The engine's answer to a staged change: its first item. */\n const answer = async (change: PendingChange, options: OperationOptions | undefined) => {\n try {\n const result = await ctx.cancellable(options?.signal, change.result);\n return result.items[0]!;\n } catch (error) {\n throw toPluginError('page-edit', error);\n }\n };\n\n /** What an insert's answer names: the new pages. */\n const inserted = async (\n change: PendingChange,\n options: OperationOptions | undefined,\n ): Promise<PageEditInsertResult> => {\n const item = await answer(change, options);\n return { pages: 'insertedPages' in item ? item.insertedPages : NO_PAGES };\n };\n\n /**\n * A page's rotation once this session's changes on their way apply: the\n * last rotation one of them gives it, else the page's own. What a turn\n * starts from, so two quick quarter turns are a half turn.\n */\n const rotationOf = (ref: PageRef): PdfRotation => {\n let rotation = ctx.pageOf(ref).rotation;\n for (const change of ctx.changes.pending()) {\n for (const op of change.shows) {\n if (op.type !== 'pages.rotate') continue;\n if (op.pages.some((page) => page.objectNumber === ref.objectNumber)) {\n rotation = op.rotation;\n }\n }\n }\n return rotation;\n };\n\n /**\n * Rotate pages as one change: one op per rotation they end up with. What\n * undoing it looks like is each page turned back, so a turn right after an\n * undo starts from where the undo leaves the page.\n */\n const rotate = (refs: readonly PageRef[], rotationAfter: (ref: PageRef) => PdfRotation) => {\n const after = new Map<PdfRotation, PageRef[]>();\n const before = new Map<PdfRotation, PageRef[]>();\n for (const ref of refs) {\n const add = (groups: Map<PdfRotation, PageRef[]>, rotation: PdfRotation) =>\n groups.set(rotation, [...(groups.get(rotation) ?? []), ref]);\n add(before, rotationOf(ref));\n add(after, rotationAfter(ref));\n }\n const opsOf = (groups: Map<PdfRotation, PageRef[]>): PageOp[] =>\n [...groups].map(([rotation, pages]) => ({ type: 'pages.rotate', pages, rotation }));\n return stageChange('rotate', refs.length, opsOf(after), opsOf(before));\n };\n\n /**\n * The pages as they show: every change on its way sent and answered first,\n * so a copy holds the edits made before it.\n */\n const settled = async (options: OperationOptions | undefined): Promise<void> => {\n ctx.changes.sendHolds();\n await ctx.cancellable(options?.signal, ctx.changes.whenSettled());\n };\n\n /** Copy pages of this document out, as they show. */\n const extractShown = async (\n refs: readonly PageRef[],\n options: OperationOptions | undefined,\n ): Promise<Uint8Array> => {\n await settled(options);\n return ctx.cancellable(options?.signal, ctx.doc.pages.extract([...refs]));\n };\n\n /** Stage the insert of a PDF's pages; resolves the new pages. */\n const insertBytes = (\n verb: 'insert' | 'duplicate',\n count: number | null,\n bytes: Uint8Array | ArrayBuffer,\n position: PagePosition,\n options: OperationOptions | undefined,\n ): Promise<PageEditInsertResult> => {\n const source = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n const change = stageChange(verb, count, [{ type: 'pages.insert', source, position }]);\n return inserted(change, options);\n };\n\n /** Some pages of a PDF: open the bytes beside the document and extract them. */\n const pagesOfBytes = async (\n bytes: Uint8Array | ArrayBuffer,\n pageIndexes: readonly number[],\n signal: AbortSignal | undefined,\n ): Promise<Uint8Array> => {\n const source = await mapErrors(() =>\n ctx.engine.open(\n {\n kind: 'bytes',\n id: `page-edit-source-${Date.now().toString(36)}`,\n bytes: new Uint8Array(bytes),\n },\n { scope: ['*'] },\n ),\n );\n try {\n const layout = await mapErrors(() => ctx.cancellable(signal, source.pages.list()));\n const refs = pageIndexes.map((index) => {\n const page = layout.pages[index];\n if (!page) {\n throw new PluginError('not-found', 'page-edit', `the PDF has no page ${index}`);\n }\n return page.ref;\n });\n return await mapErrors(() => ctx.cancellable(signal, source.pages.extract(refs)));\n } finally {\n await source.close();\n }\n };\n\n const api: PageEditCapability = {\n canEdit: () => ctx.allows(ASSEMBLE),\n canExtract: () => ctx.allows(DOWNLOAD),\n\n getSelected,\n select,\n\n rotateBy: async (pages, delta, options) => {\n refuse(ASSEMBLE, 'pageEdit.rotateBy', options);\n const refs = refsOf(pages);\n // The double modulo keeps -90 from 0 at 270.\n const change = rotate(\n refs,\n (ref) => ((((rotationOf(ref) + delta) % 360) + 360) % 360) as PdfRotation,\n );\n await answer(change, options);\n },\n\n setRotation: async (pages, rotation, options) => {\n refuse(ASSEMBLE, 'pageEdit.setRotation', options);\n const change = rotate(refsOf(pages), () => rotation);\n await answer(change, options);\n },\n\n reorder: async (pages, position, options) => {\n refuse(ASSEMBLE, 'pageEdit.reorder', options);\n const refs = refsOf(pages);\n const change = stageChange('reorder', refs.length, [\n { type: 'pages.reorder', pages: refs, position: positionOf(position) },\n ]);\n await answer(change, options);\n },\n\n delete: async (pages, options) => {\n refuse(ASSEMBLE, 'pageEdit.delete', options);\n const refs = refsOf(pages);\n const deleting = new Set(refs.map((ref) => ref.objectNumber));\n if (registry().every((page) => deleting.has(page.ref.objectNumber))) {\n throw new PluginError('invalid-input', 'page-edit', 'a document keeps at least one page');\n }\n const change = stageChange('delete', refs.length, [{ type: 'pages.delete', pages: refs }]);\n await answer(change, options);\n },\n\n insertBlank: async (options = {}) => {\n refuse(ASSEMBLE, 'pageEdit.insertBlank', options);\n const position = positionOf(options.position);\n const count = options.count ?? 1;\n const size = options.size ?? sizeAt(position);\n const change = stageChange('insertBlank', count, [\n { type: 'pages.insertBlank', spec: { size, count }, position },\n ]);\n return inserted(change, options);\n },\n\n insertFromBytes: async (bytes, options = {}) => {\n refuse(ASSEMBLE, 'pageEdit.insertFromBytes', options);\n const position = positionOf(options.position);\n const { pageIndexes, signal } = options;\n if (pageIndexes?.length === 0) throw noPages();\n const source = pageIndexes ? await pagesOfBytes(bytes, pageIndexes, signal) : bytes;\n return insertBytes('insert', pageIndexes?.length ?? null, source, position, options);\n },\n\n insertFromDocument: async (documentId, pages, options = {}) => {\n refuse(ASSEMBLE, 'pageEdit.insertFromDocument', options);\n if (pages.length === 0) throw noPages();\n const other = ctx.documentHandle(documentId);\n if (!other) {\n throw new PluginError('not-found', 'page-edit', `document '${documentId}' is not open`);\n }\n // The pages are the other document's, so its page list resolves them.\n const otherPages = ctx.get(DocumentsToken).listPages(documentId);\n const refs = pages.map((page) => {\n const found =\n typeof page === 'number'\n ? otherPages[page]\n : otherPages.find((candidate) => candidate.ref.objectNumber === page.objectNumber);\n if (!found) {\n throw new PluginError(\n 'not-found',\n 'page-edit',\n `document '${documentId}' has no such page`,\n );\n }\n return found.ref;\n });\n const position = positionOf(options.position);\n // The other document's session decides whether its pages may be copied\n // out: the engine refuses `doc.download` there with the permission named.\n const bytes = await mapErrors(() =>\n ctx.cancellable(options.signal, other.pages.extract(refs)),\n );\n return insertBytes('insert', refs.length, bytes, position, options);\n },\n\n duplicate: async (pages, options = {}) => {\n refuse(ASSEMBLE, 'pageEdit.duplicate', options);\n ctx.assertAllowed(DOWNLOAD, 'pageEdit.duplicate');\n const refs = refsOf(pages);\n const position = options.position\n ? positionOf(options.position)\n : { after: refs[refs.length - 1]! };\n const bytes = await extractShown(refs, options);\n return insertBytes('duplicate', refs.length, bytes, position, options);\n },\n\n extract: async (pages, options) => {\n refuse(DOWNLOAD, 'pageEdit.extract', options);\n return extractShown(refsOf(pages), options);\n },\n };\n return { api };\n}\n","import { definePlugin } from '@embedpdf/core';\n\nimport { PageEditToken } from './contract';\nimport { createPageEditController } from './controller';\nimport { initialPageEditState } from './model';\n\n/**\n * Document-scoped: the document's page edits, each one change of its\n * history, and which of its pages are selected. Turning a page relative to\n * its own rotation lives in the controller, so no app re-derives it.\n */\nexport const pageEditPlugin = () =>\n definePlugin({\n id: 'page-edit',\n scope: 'document',\n token: PageEditToken,\n state: initialPageEditState,\n create: createPageEditController,\n });\n","/**\n * The pages page's State table as code: what `usePageEditState()` returns,\n * and the same fields in every other framework.\n */\nimport { defineState } from '@embedpdf/core';\n\nimport { PageEditToken } from './contract';\n\nexport const pageEditState = defineState(PageEditToken, {\n read: (pageEdit) => ({\n selected: pageEdit.getSelected(),\n canEdit: pageEdit.canEdit(),\n canExtract: pageEdit.canExtract(),\n }),\n empty: {\n selected: [],\n canEdit: false,\n canExtract: false,\n },\n});\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,13 +1,25 @@
1
- import { n as PageEditToken, t as PageEditCapability } from "./contract-BnXi11KD.cjs";
1
+ import { a as PagePosition, c as PdfSize, i as PageInsertFromBytesOptions, l as PageEditToken, n as PageEditInsertResult, o as PagePositionOptions, r as PageInsertBlankOptions, s as PdfRotation, t as PageEditCapability } from "./contract-IN8rk5LG.cjs";
2
+ import { PageRef } from "@embedpdf/core";
3
+ //#region src/model.d.ts
4
+ interface PageEditState {
5
+ /** The selected pages, in the order they were selected. */
6
+ readonly selected: readonly PageRef[];
7
+ }
8
+ //#endregion
2
9
  //#region src/page-edit.plugin.d.ts
3
10
  /**
4
- * Document-scoped, stateless: turns the engine handle's page service into a
5
- * PON-addressed edit capability. The relative→absolute rotation lives in the
6
- * capability so the four framework adapters never re-derive it, and this is the
7
- * home for client-side edit state (pending/optimistic/undo) when it lands —
8
- * adapters keep their thin hook over `PageEditToken` unchanged.
11
+ * Document-scoped: the document's page edits, each one change of its
12
+ * history, and which of its pages are selected. Turning a page relative to
13
+ * its own rotation lives in the controller, so no app re-derives it.
9
14
  */
10
- declare const pageEditPlugin: () => import("@embedpdf/core").PluginDef<unknown, import("@embedpdf/core").Action, PageEditCapability>;
15
+ declare const pageEditPlugin: () => import("@embedpdf/core").PluginDef<PageEditState, PageEditCapability, import("@embedpdf/core").NoSettings>;
16
+ //#endregion
17
+ //#region src/state.d.ts
18
+ declare const pageEditState: import("@embedpdf/core").StateDeclaration<PageEditCapability, {
19
+ selected: readonly import("@embedpdf/core").PageRef[];
20
+ canEdit: boolean;
21
+ canExtract: boolean;
22
+ }>;
11
23
  //#endregion
12
- export { type PageEditCapability, PageEditToken, pageEditPlugin };
24
+ export { PageEditCapability, PageEditInsertResult, PageEditToken, PageInsertBlankOptions, PageInsertFromBytesOptions, type PagePosition, PagePositionOptions, type PdfRotation, type PdfSize, pageEditPlugin, pageEditState };
13
25
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,13 +1,25 @@
1
- import { n as PageEditToken, t as PageEditCapability } from "./contract-BnXi11KD.js";
1
+ import { a as PagePosition, c as PdfSize, i as PageInsertFromBytesOptions, l as PageEditToken, n as PageEditInsertResult, o as PagePositionOptions, r as PageInsertBlankOptions, s as PdfRotation, t as PageEditCapability } from "./contract-IN8rk5LG.js";
2
+ import { PageRef } from "@embedpdf/core";
3
+ //#region src/model.d.ts
4
+ interface PageEditState {
5
+ /** The selected pages, in the order they were selected. */
6
+ readonly selected: readonly PageRef[];
7
+ }
8
+ //#endregion
2
9
  //#region src/page-edit.plugin.d.ts
3
10
  /**
4
- * Document-scoped, stateless: turns the engine handle's page service into a
5
- * PON-addressed edit capability. The relative→absolute rotation lives in the
6
- * capability so the four framework adapters never re-derive it, and this is the
7
- * home for client-side edit state (pending/optimistic/undo) when it lands —
8
- * adapters keep their thin hook over `PageEditToken` unchanged.
11
+ * Document-scoped: the document's page edits, each one change of its
12
+ * history, and which of its pages are selected. Turning a page relative to
13
+ * its own rotation lives in the controller, so no app re-derives it.
9
14
  */
10
- declare const pageEditPlugin: () => import("@embedpdf/core").PluginDef<unknown, import("@embedpdf/core").Action, PageEditCapability>;
15
+ declare const pageEditPlugin: () => import("@embedpdf/core").PluginDef<PageEditState, PageEditCapability, import("@embedpdf/core").NoSettings>;
16
+ //#endregion
17
+ //#region src/state.d.ts
18
+ declare const pageEditState: import("@embedpdf/core").StateDeclaration<PageEditCapability, {
19
+ selected: readonly import("@embedpdf/core").PageRef[];
20
+ canEdit: boolean;
21
+ canExtract: boolean;
22
+ }>;
11
23
  //#endregion
12
- export { type PageEditCapability, PageEditToken, pageEditPlugin };
24
+ export { PageEditCapability, PageEditInsertResult, PageEditToken, PageInsertBlankOptions, PageInsertFromBytesOptions, type PagePosition, PagePositionOptions, type PdfRotation, type PdfSize, pageEditPlugin, pageEditState };
13
25
  //# sourceMappingURL=index.d.ts.map