@openpresentation/opf-editor 0.10.6 → 0.11.0

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.
Files changed (76) hide show
  1. package/README.md +333 -8
  2. package/dist/annotations.d.ts +71 -0
  3. package/dist/annotations.js +281 -0
  4. package/dist/assets.d.ts +67 -0
  5. package/dist/assets.js +176 -0
  6. package/dist/background-options.d.ts +48 -0
  7. package/dist/background-options.js +134 -0
  8. package/dist/block-convert.d.ts +64 -0
  9. package/dist/block-convert.js +142 -0
  10. package/dist/canvas.d.ts +16 -0
  11. package/dist/canvas.js +82 -21
  12. package/dist/chart-data.d.ts +32 -0
  13. package/dist/chart-data.js +101 -0
  14. package/dist/chart-options-panel.d.ts +16 -0
  15. package/dist/chart-options-panel.js +127 -0
  16. package/dist/chart-options.d.ts +49 -0
  17. package/dist/chart-options.js +157 -0
  18. package/dist/content-actions.d.ts +91 -0
  19. package/dist/content-actions.js +207 -0
  20. package/dist/content-controls.js +326 -0
  21. package/dist/data-grid.d.ts +37 -0
  22. package/dist/data-grid.js +1035 -0
  23. package/dist/design-controls.d.ts +43 -0
  24. package/dist/design-controls.js +1077 -0
  25. package/dist/design-options.d.ts +108 -0
  26. package/dist/design-options.js +412 -0
  27. package/dist/edit-helpers.js +52 -0
  28. package/dist/export.d.ts +77 -0
  29. package/dist/export.js +216 -0
  30. package/dist/find-panel.d.ts +44 -0
  31. package/dist/find-panel.js +431 -0
  32. package/dist/find-replace.d.ts +100 -0
  33. package/dist/find-replace.js +374 -0
  34. package/dist/grid-model.d.ts +135 -0
  35. package/dist/grid-model.js +836 -0
  36. package/dist/grid-text.d.ts +33 -0
  37. package/dist/grid-text.js +251 -0
  38. package/dist/image-crop.d.ts +59 -0
  39. package/dist/image-crop.js +336 -0
  40. package/dist/image-cropper.d.ts +29 -0
  41. package/dist/image-cropper.js +519 -0
  42. package/dist/index.d.ts +11 -1
  43. package/dist/index.js +104 -171
  44. package/dist/numbering-panel.d.ts +21 -0
  45. package/dist/numbering-panel.js +200 -0
  46. package/dist/numbering.d.ts +62 -0
  47. package/dist/numbering.js +223 -0
  48. package/dist/outline-view.d.ts +17 -0
  49. package/dist/outline-view.js +278 -0
  50. package/dist/outline.d.ts +56 -0
  51. package/dist/outline.js +271 -0
  52. package/dist/persistence-ui.d.ts +24 -0
  53. package/dist/persistence-ui.js +81 -0
  54. package/dist/persistence.d.ts +105 -0
  55. package/dist/persistence.js +429 -0
  56. package/dist/review-panel.d.ts +44 -0
  57. package/dist/review-panel.js +359 -0
  58. package/dist/review.d.ts +75 -0
  59. package/dist/review.js +170 -0
  60. package/dist/slide-manager.d.ts +44 -0
  61. package/dist/slide-manager.js +695 -0
  62. package/dist/slides.d.ts +96 -0
  63. package/dist/slides.js +433 -0
  64. package/dist/switches.d.ts +26 -0
  65. package/dist/switches.js +127 -43
  66. package/dist/table-options.d.ts +80 -0
  67. package/dist/table-options.js +419 -0
  68. package/dist/table-structure.d.ts +30 -0
  69. package/dist/table-structure.js +92 -0
  70. package/dist/template-panel.d.ts +31 -0
  71. package/dist/template-panel.js +377 -0
  72. package/dist/templates.d.ts +126 -0
  73. package/dist/templates.js +331 -0
  74. package/dist/zip.d.ts +4 -0
  75. package/dist/zip.js +71 -0
  76. package/package.json +150 -10
@@ -0,0 +1,331 @@
1
+ // Templates (RR-32): the headless model behind the Fill template panel. A template is an OPF file
2
+ // with variables; filling it resolves the variables to a concrete deck (core resolveVariables).
3
+ // Everything here reads and writes the document through the session, so a fill, a declaration
4
+ // and a token insertion are validated, undoable JSON Patch edits like any other. Importing this
5
+ // module needs no DOM. Core is read from the namespace so an older core still loads this module;
6
+ // the functions then throw "templates-unavailable".
7
+ import * as core from "@openpresentation/opf";
8
+ import { OPFEditorError, getValueAtPath, opfPathToJsonPointer } from "./index.js";
9
+
10
+ const VARIABLE_ID = /^[a-z][a-z0-9-]*$/;
11
+ const KINDS = ["color", "text", "number", "date", "image", "url", "list"];
12
+
13
+ /** The form control each variable kind uses. */
14
+ export const TEMPLATE_INPUT_TYPES = Object.freeze({
15
+ color: "color",
16
+ text: "text",
17
+ number: "number",
18
+ date: "date",
19
+ image: "image",
20
+ url: "url",
21
+ list: "list",
22
+ });
23
+
24
+ function fail(code, message, details) {
25
+ return new OPFEditorError(code, message, details);
26
+ }
27
+
28
+ function requireCore() {
29
+ if (typeof core.resolveVariables !== "function" || typeof core.listVariables !== "function") {
30
+ throw fail("templates-unavailable", "Templates need a core release that ships resolveVariables (@openpresentation/opf after 0.11.4).");
31
+ }
32
+ }
33
+
34
+ function checkEditor(editor) {
35
+ if (!editor || typeof editor.applyPatch !== "function" || typeof editor.subscribe !== "function") {
36
+ throw fail("invalid-editor", "Expected an editor session created by createEditorSession.");
37
+ }
38
+ }
39
+
40
+ const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
41
+
42
+ /** True when the core this editor runs on can resolve template variables. */
43
+ export function templatesAvailable() {
44
+ return typeof core.resolveVariables === "function" && typeof core.listVariables === "function";
45
+ }
46
+
47
+ /** True when the document is marked as a template (`template: true`). */
48
+ export function isTemplateDocument(document) {
49
+ return isObject(document) && document.template === true;
50
+ }
51
+
52
+ /** True when the document is a template or declares a variable that is not a color. */
53
+ export function hasTemplateVariables(document) {
54
+ return templatesAvailable() && core.hasContentVariables(document);
55
+ }
56
+
57
+ function humanize(id) {
58
+ const words = id.replaceAll("-", " ");
59
+ return words.charAt(0).toUpperCase() + words.slice(1);
60
+ }
61
+
62
+ /** The text a form control shows for a value. Rich text shows its plain text. */
63
+ export function valueToFieldText(kind, value) {
64
+ if (value === undefined || value === null) return "";
65
+ switch (kind) {
66
+ case "list":
67
+ return Array.isArray(value) ? value.join("\n") : String(value);
68
+ case "image":
69
+ return isObject(value) ? String(value.src ?? "") : String(value);
70
+ case "text":
71
+ if (Array.isArray(value)) return value.map((run) => (typeof run === "string" ? run : String(run?.text ?? ""))).join("");
72
+ return String(value);
73
+ default:
74
+ return String(value);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Parse what a form control holds. A blank control clears the value (the declared value, if any,
80
+ * applies again). Returns `{ ok: true, value }` (value undefined when cleared) or `{ ok: false, message }`.
81
+ */
82
+ export function fieldTextToValue(kind, text) {
83
+ requireCore();
84
+ if (typeof text !== "string") return { ok: false, message: "Expected text." };
85
+ if (text.trim() === "") return { ok: true, value: undefined };
86
+ const coerced = core.coerceVariableValue(kind, kind === "text" ? text : text.trim());
87
+ return coerced.ok ? { ok: true, value: coerced.value } : { ok: false, message: `${coerced.message}.` };
88
+ }
89
+
90
+ /**
91
+ * The variables of a document as form fields, with the state a panel needs:
92
+ * `status` is "filled" (a value was supplied), "default" (the declaration has a value), "unfilled"
93
+ * (required, no value) or "optional" (not required, no value).
94
+ */
95
+ export function listTemplateFields(document, values = {}) {
96
+ requireCore();
97
+ const declared = new Map(core.variableDeclarations(document).map((declaration) => [declaration.id, declaration]));
98
+ return core.listVariables(document, values).map((info) => {
99
+ const declaration = declared.get(info.id);
100
+ const supplied = Object.hasOwn(values, info.id) && values[info.id] !== undefined && values[info.id] !== null;
101
+ const defaultValue = declaration?.value;
102
+ const value = supplied ? info.value : defaultValue;
103
+ const status = supplied ? "filled" : defaultValue !== undefined ? "default" : info.required ? "unfilled" : "optional";
104
+ return {
105
+ id: info.id,
106
+ kind: info.kind,
107
+ input: TEMPLATE_INPUT_TYPES[info.kind],
108
+ label: info.label ?? humanize(info.id),
109
+ description: info.description,
110
+ required: info.required,
111
+ format: info.format,
112
+ example: info.example,
113
+ value,
114
+ defaultValue,
115
+ text: supplied ? valueToFieldText(info.kind, value) : "",
116
+ placeholder: valueToFieldText(info.kind, defaultValue !== undefined ? defaultValue : info.example),
117
+ rich: info.kind === "text" && Array.isArray(value),
118
+ status,
119
+ uses: info.uses,
120
+ };
121
+ });
122
+ }
123
+
124
+ /** The summary a panel shows: how many required variables still need a value. */
125
+ export function templateStatus(document, values = {}) {
126
+ const fields = listTemplateFields(document, values);
127
+ const required = fields.filter((field) => field.required);
128
+ const unfilled = fields.filter((field) => field.status === "unfilled").map((field) => field.id);
129
+ return {
130
+ template: isTemplateDocument(document),
131
+ fieldCount: fields.length,
132
+ requiredCount: required.length,
133
+ filledRequiredCount: required.length - unfilled.length,
134
+ unfilled,
135
+ complete: unfilled.length === 0,
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Preview data for the supplied values: the concrete deck with each unfilled variable's example (or its
141
+ * token when it has none). Never throws for missing values; the diagnostics say what was unfilled.
142
+ */
143
+ export function previewTemplate(document, values = {}) {
144
+ requireCore();
145
+ return core.resolveVariables(document, values, { examples: true, partial: true });
146
+ }
147
+
148
+ /**
149
+ * A fill session over an editor: values live here until `apply()` fills the document with them as one
150
+ * undoable edit. `set`/`setText`/`clear` change a value, `preview()` shows the result without touching the
151
+ * document, `subscribe` notifies listeners (a panel redraws on it).
152
+ */
153
+ export function createTemplateFill(editor, options = {}) {
154
+ requireCore();
155
+ checkEditor(editor);
156
+ let values = {};
157
+ const listeners = new Set();
158
+ const emit = () => {
159
+ for (const listener of listeners) listener(api);
160
+ };
161
+ const declared = () => new Set(core.variableDeclarations(editor.document).map((declaration) => declaration.id));
162
+ const kindOf = (id) => core.variableDeclarations(editor.document).find((declaration) => declaration.id === id)?.kind;
163
+ const api = {
164
+ get values() {
165
+ return structuredClone(values);
166
+ },
167
+ fields() {
168
+ return listTemplateFields(editor.document, values);
169
+ },
170
+ status() {
171
+ return templateStatus(editor.document, values);
172
+ },
173
+ preview() {
174
+ return previewTemplate(editor.document, values);
175
+ },
176
+ set(id, value) {
177
+ if (!declared().has(id)) throw fail("unknown-variable", `No variable '${id}' is declared.`, { id });
178
+ if (value === undefined || value === null) {
179
+ delete values[id];
180
+ } else {
181
+ const coerced = core.coerceVariableValue(kindOf(id), value);
182
+ if (!coerced.ok) throw fail("invalid-variable-value", `Value for '${id}': ${coerced.message}.`, { id });
183
+ values[id] = coerced.value;
184
+ }
185
+ emit();
186
+ return api;
187
+ },
188
+ setText(id, text) {
189
+ if (!declared().has(id)) throw fail("unknown-variable", `No variable '${id}' is declared.`, { id });
190
+ const parsed = fieldTextToValue(kindOf(id), text);
191
+ if (!parsed.ok) return parsed;
192
+ api.set(id, parsed.value);
193
+ return { ok: true, value: parsed.value };
194
+ },
195
+ clear(id) {
196
+ return api.set(id, undefined);
197
+ },
198
+ /** Drop values for variables the document no longer declares (after an edit removed one). */
199
+ prune() {
200
+ const known = declared();
201
+ let changed = false;
202
+ for (const id of Object.keys(values)) if (!known.has(id)) { delete values[id]; changed = true; }
203
+ if (changed) emit();
204
+ return api;
205
+ },
206
+ reset() {
207
+ values = {};
208
+ emit();
209
+ return api;
210
+ },
211
+ subscribe(listener) {
212
+ listeners.add(listener);
213
+ return () => listeners.delete(listener);
214
+ },
215
+ /**
216
+ * Fill the document: the variables are resolved with the values, and the concrete deck replaces the
217
+ * document as one undoable, validated edit. Unfilled required variables refuse the fill unless
218
+ * `partial` is set (the unfilled ones stay declared for a later pass).
219
+ */
220
+ apply({ partial = false, meta = {} } = {}) {
221
+ const document = editor.document;
222
+ const result = core.resolveVariables(document, values, { partial, template: false });
223
+ const errors = result.diagnostics.filter((entry) => entry.severity === "error");
224
+ if (errors.length) {
225
+ const unfilled = errors.some((entry) => entry.code === "variable-unfilled");
226
+ throw fail(unfilled ? "unfilled-variables" : "invalid-variables", errors[0].message, { issues: errors, unfilled: result.unfilled });
227
+ }
228
+ const change = editor.applyPatch([{ op: "replace", path: "", value: result.presentation }], {
229
+ source: "template-fill",
230
+ rejectInvalid: true,
231
+ ...meta,
232
+ ...options.meta,
233
+ });
234
+ values = {};
235
+ emit();
236
+ return { ...change, unfilled: result.unfilled, complete: result.complete, diagnostics: result.diagnostics };
237
+ },
238
+ };
239
+ return api;
240
+ }
241
+
242
+ /** The token text for a variable: `{{id}}`, or `{{id|format}}` with a one-off format. */
243
+ export function variableToken(id, format) {
244
+ if (!VARIABLE_ID.test(id)) throw fail("invalid-variable-id", "A variable id is lowercase kebab-case: letters, digits and hyphens, starting with a letter.", { id });
245
+ return format ? `{{${id}|${format}}}` : `{{${id}}}`;
246
+ }
247
+
248
+ /** An unused variable id derived from a label, for a "new variable" form. */
249
+ export function suggestVariableId(document, label = "value") {
250
+ const base = String(label).normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/^[^a-z]+/, "") || "value";
251
+ const taken = new Set(isObject(document?.variables) ? Object.keys(document.variables) : []);
252
+ if (!taken.has(base)) return base;
253
+ for (let n = 2; ; n++) if (!taken.has(`${base}-${n}`)) return `${base}-${n}`;
254
+ }
255
+
256
+ function declarationPatches(document, id, declaration) {
257
+ if (!VARIABLE_ID.test(id)) throw fail("invalid-variable-id", "A variable id is lowercase kebab-case: letters, digits and hyphens, starting with a letter.", { id });
258
+ const entry = typeof declaration === "string" ? declaration : structuredClone(declaration);
259
+ if (typeof entry !== "string" && !(isObject(entry) && KINDS.includes(entry.type))) {
260
+ throw fail("invalid-variable", `A variable declaration is a hex color or an object whose type is one of ${KINDS.join(", ")}.`, { id });
261
+ }
262
+ const variables = document.variables;
263
+ if (variables !== undefined && !isObject(variables)) throw fail("invalid-variable", "The document's variables field is not an object.", { id });
264
+ if (variables && Object.hasOwn(variables, id)) throw fail("variable-exists", `A variable '${id}' is already declared.`, { id });
265
+ return variables === undefined
266
+ ? [{ op: "add", path: "/variables", value: { [id]: entry } }]
267
+ : [{ op: "add", path: opfPathToJsonPointer(["variables", id]), value: entry }];
268
+ }
269
+
270
+ /** Declare a variable as one undoable edit. The resulting document must validate (a required variable with no value needs `template: true`). */
271
+ export function declareVariable(editor, id, declaration, meta = {}) {
272
+ requireCore();
273
+ checkEditor(editor);
274
+ return editor.applyPatch(declarationPatches(editor.document, id, declaration), { source: "template-variable", rejectInvalid: true, ...meta });
275
+ }
276
+
277
+ /** Mark the document as a template (or, with `false`, as a normal deck) as one undoable edit. */
278
+ export function setTemplate(editor, enabled, meta = {}) {
279
+ checkEditor(editor);
280
+ const document = editor.document;
281
+ const present = Object.hasOwn(document, "template");
282
+ const patches = enabled
283
+ ? [{ op: present ? "replace" : "add", path: "/template", value: true }]
284
+ : present
285
+ ? [{ op: "remove", path: "/template" }]
286
+ : [];
287
+ if (!patches.length) return { document, patches: [], inversePatches: [], validation: editor.validation };
288
+ return editor.applyPatch(patches, { source: "template-mode", rejectInvalid: true, ...meta });
289
+ }
290
+
291
+ /**
292
+ * Insert a variable token into a text field as one undoable edit. `path` addresses a string, or a TextRun[]
293
+ * (`runIndex` picks the run, default the last). `start`/`end` are UTF-16 offsets into that string (a
294
+ * selection is replaced; default: the end). To use a variable that is not declared yet pass `declare`,
295
+ * the declaration, and both changes commit together.
296
+ */
297
+ export function insertVariableToken(editor, path, id, { start, end, runIndex, format, declare, meta = {} } = {}) {
298
+ requireCore();
299
+ checkEditor(editor);
300
+ const document = editor.document;
301
+ const token = variableToken(id, format);
302
+ const patches = [];
303
+ const declaredNow = isObject(document.variables) && Object.hasOwn(document.variables, id);
304
+ if (!declaredNow) {
305
+ if (declare === undefined) throw fail("unknown-variable", `No variable '${id}' is declared. Declare it first, or pass its declaration.`, { id });
306
+ patches.push(...declarationPatches(document, id, declare));
307
+ }
308
+ const pointer = opfPathToJsonPointer(path);
309
+ const current = getValueAtPath(document, path);
310
+ const splice = (text) => {
311
+ const from = start === undefined ? text.length : start;
312
+ const to = end === undefined ? from : end;
313
+ if (!Number.isInteger(from) || !Number.isInteger(to) || from < 0 || to < from || to > text.length) {
314
+ throw fail("invalid-selection", "The selection is outside the text.", { start: from, end: to });
315
+ }
316
+ return `${text.slice(0, from)}${token}${text.slice(to)}`;
317
+ };
318
+ if (typeof current === "string") {
319
+ patches.push({ op: "replace", path: pointer, value: splice(current) });
320
+ } else if (Array.isArray(current) && current.length) {
321
+ const index = runIndex ?? current.length - 1;
322
+ const run = current[index];
323
+ if (typeof run === "string") patches.push({ op: "replace", path: `${pointer}/${index}`, value: splice(run) });
324
+ else if (isObject(run) && typeof run.text === "string") patches.push({ op: "replace", path: `${pointer}/${index}/text`, value: splice(run.text) });
325
+ else throw fail("not-text", "That run has no text to insert into.", { path });
326
+ } else {
327
+ throw fail("not-text", "Select a text field (a string or rich text runs) to insert a variable.", { path });
328
+ }
329
+ const change = editor.applyPatch(patches, { source: "template-token", rejectInvalid: true, ...meta });
330
+ return { ...change, token };
331
+ }
package/dist/zip.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /** CRC-32 of the bytes, as an unsigned integer. */
2
+ export declare function crc32(bytes: Uint8Array): number;
3
+ /** A ZIP archive of `[{ name, bytes }]` (deflated when the platform can, stored otherwise; fixed timestamps so equal input gives equal bytes). */
4
+ export declare function createZip(entries: Array<{ name: string; bytes: Uint8Array }>, options?: { compress?: boolean; signal?: AbortSignal }): Promise<Uint8Array>;
package/dist/zip.js ADDED
@@ -0,0 +1,71 @@
1
+ // A minimal ZIP writer for the multi-file downloads (all slides as PNG or SVG). One archive, no dependency, a fixed
2
+ // timestamp (1980-01-01) so the same files always give the same bytes. Entries are stored, or deflated when the
3
+ // platform has CompressionStream (the SVG slides compress well; the PNGs do not need it).
4
+
5
+ const CRC_TABLE = (() => {
6
+ const table = new Uint32Array(256);
7
+ for (let index = 0; index < 256; index++) {
8
+ let value = index;
9
+ for (let bit = 0; bit < 8; bit++) value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
10
+ table[index] = value >>> 0;
11
+ }
12
+ return table;
13
+ })();
14
+
15
+ /** CRC-32 of the bytes, as an unsigned integer. */
16
+ export function crc32(bytes) {
17
+ let crc = 0xffffffff;
18
+ for (let index = 0; index < bytes.length; index++) crc = CRC_TABLE[(crc ^ bytes[index]) & 0xff] ^ (crc >>> 8);
19
+ return (crc ^ 0xffffffff) >>> 0;
20
+ }
21
+
22
+ async function deflateRaw(bytes) {
23
+ const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("deflate-raw"));
24
+ return new Uint8Array(await new Response(stream).arrayBuffer());
25
+ }
26
+
27
+ /**
28
+ * Build a ZIP archive from `[{ name, bytes }]`. `compress` (default true) deflates an entry when that makes it smaller and the
29
+ * platform can; the result is a valid ZIP either way. `signal` stops between entries.
30
+ */
31
+ export async function createZip(entries, { compress = true, signal } = {}) {
32
+ const encoder = new TextEncoder();
33
+ const canDeflate = compress && typeof CompressionStream === "function" && typeof Blob === "function";
34
+ const local = [], central = [];
35
+ let offset = 0;
36
+ for (const entry of entries) {
37
+ signal?.throwIfAborted?.();
38
+ const name = encoder.encode(entry.name);
39
+ const bytes = entry.bytes instanceof Uint8Array ? entry.bytes : new Uint8Array(entry.bytes);
40
+ const crc = crc32(bytes);
41
+ let stored = bytes, method = 0;
42
+ if (canDeflate && bytes.length > 64) {
43
+ const packed = await deflateRaw(bytes);
44
+ if (packed.length < bytes.length) { stored = packed; method = 8; }
45
+ }
46
+ // 2.0 to extract deflate; bit 11 says the name is UTF-8; time 00:00:00, date 1980-01-01.
47
+ const header = new DataView(new ArrayBuffer(30));
48
+ header.setUint32(0, 0x04034b50, true); header.setUint16(4, 20, true); header.setUint16(6, 0x0800, true); header.setUint16(8, method, true);
49
+ header.setUint16(10, 0, true); header.setUint16(12, 0x21, true);
50
+ header.setUint32(14, crc, true); header.setUint32(18, stored.length, true); header.setUint32(22, bytes.length, true);
51
+ header.setUint16(26, name.length, true); header.setUint16(28, 0, true);
52
+ local.push(new Uint8Array(header.buffer), name, stored);
53
+ const directory = new DataView(new ArrayBuffer(46));
54
+ directory.setUint32(0, 0x02014b50, true); directory.setUint16(4, 20, true); directory.setUint16(6, 20, true); directory.setUint16(8, 0x0800, true); directory.setUint16(10, method, true);
55
+ directory.setUint16(12, 0, true); directory.setUint16(14, 0x21, true);
56
+ directory.setUint32(16, crc, true); directory.setUint32(20, stored.length, true); directory.setUint32(24, bytes.length, true);
57
+ directory.setUint16(28, name.length, true); directory.setUint32(42, offset, true);
58
+ central.push(new Uint8Array(directory.buffer), name);
59
+ offset += 30 + name.length + stored.length;
60
+ }
61
+ const size = central.reduce((total, part) => total + part.length, 0);
62
+ const end = new DataView(new ArrayBuffer(22));
63
+ end.setUint32(0, 0x06054b50, true); end.setUint16(8, entries.length, true); end.setUint16(10, entries.length, true);
64
+ end.setUint32(12, size, true); end.setUint32(16, offset, true);
65
+ const parts = [...local, ...central, new Uint8Array(end.buffer)];
66
+ const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
67
+ let position = 0;
68
+ for (const part of parts) { out.set(part, position); position += part.length; }
69
+ if (offset > 0xfffffffe || entries.length > 0xfffe) throw new Error("The archive is too large for a ZIP file without ZIP64. Export fewer slides at a time.");
70
+ return out;
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/opf-editor",
3
- "version": "0.10.6",
3
+ "version": "0.11.0",
4
4
  "description": "Embeddable local OPF editor primitives built on the deterministic renderer.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -78,31 +78,163 @@
78
78
  "types": "./dist/json-options.d.ts",
79
79
  "default": "./dist/json-options.js"
80
80
  },
81
+ "./export": {
82
+ "types": "./dist/export.d.ts",
83
+ "default": "./dist/export.js"
84
+ },
81
85
  "./switches": {
82
86
  "types": "./dist/switches.d.ts",
83
87
  "default": "./dist/switches.js"
88
+ },
89
+ "./block-convert": {
90
+ "types": "./dist/block-convert.d.ts",
91
+ "default": "./dist/block-convert.js"
92
+ },
93
+ "./design-options": {
94
+ "types": "./dist/design-options.d.ts",
95
+ "default": "./dist/design-options.js"
96
+ },
97
+ "./tables": {
98
+ "types": "./dist/table-options.d.ts",
99
+ "default": "./dist/table-options.js"
100
+ },
101
+ "./design-controls": {
102
+ "types": "./dist/design-controls.d.ts",
103
+ "default": "./dist/design-controls.js"
104
+ },
105
+ "./templates": {
106
+ "types": "./dist/templates.d.ts",
107
+ "default": "./dist/templates.js"
108
+ },
109
+ "./template-panel": {
110
+ "types": "./dist/template-panel.d.ts",
111
+ "default": "./dist/template-panel.js"
112
+ },
113
+ "./assets": {
114
+ "types": "./dist/assets.d.ts",
115
+ "default": "./dist/assets.js"
116
+ },
117
+ "./backgrounds": {
118
+ "types": "./dist/background-options.d.ts",
119
+ "default": "./dist/background-options.js"
120
+ },
121
+ "./review": {
122
+ "types": "./dist/review.d.ts",
123
+ "default": "./dist/review.js"
124
+ },
125
+ "./review-panel": {
126
+ "types": "./dist/review-panel.d.ts",
127
+ "default": "./dist/review-panel.js"
128
+ },
129
+ "./content-actions": {
130
+ "types": "./dist/content-actions.d.ts",
131
+ "default": "./dist/content-actions.js"
132
+ },
133
+ "./image-crop": {
134
+ "types": "./dist/image-crop.d.ts",
135
+ "default": "./dist/image-crop.js"
136
+ },
137
+ "./image-cropper": {
138
+ "types": "./dist/image-cropper.d.ts",
139
+ "default": "./dist/image-cropper.js"
140
+ },
141
+ "./find": {
142
+ "types": "./dist/find-replace.d.ts",
143
+ "default": "./dist/find-replace.js"
144
+ },
145
+ "./find-panel": {
146
+ "types": "./dist/find-panel.d.ts",
147
+ "default": "./dist/find-panel.js"
148
+ },
149
+ "./data-grid": {
150
+ "types": "./dist/data-grid.d.ts",
151
+ "default": "./dist/data-grid.js"
152
+ },
153
+ "./chart-data": {
154
+ "types": "./dist/chart-data.d.ts",
155
+ "default": "./dist/chart-data.js"
156
+ },
157
+ "./grid-text": {
158
+ "types": "./dist/grid-text.d.ts",
159
+ "default": "./dist/grid-text.js"
160
+ },
161
+ "./slides": {
162
+ "types": "./dist/slides.d.ts",
163
+ "default": "./dist/slides.js"
164
+ },
165
+ "./outline": {
166
+ "types": "./dist/outline.d.ts",
167
+ "default": "./dist/outline.js"
168
+ },
169
+ "./slide-manager": {
170
+ "types": "./dist/slide-manager.d.ts",
171
+ "default": "./dist/slide-manager.js"
172
+ },
173
+ "./outline-view": {
174
+ "types": "./dist/outline-view.d.ts",
175
+ "default": "./dist/outline-view.js"
176
+ },
177
+ "./persistence": {
178
+ "types": "./dist/persistence.d.ts",
179
+ "default": "./dist/persistence.js"
180
+ },
181
+ "./persistence-ui": {
182
+ "types": "./dist/persistence-ui.d.ts",
183
+ "default": "./dist/persistence-ui.js"
184
+ },
185
+ "./chart-options": {
186
+ "types": "./dist/chart-options.d.ts",
187
+ "default": "./dist/chart-options.js"
188
+ },
189
+ "./chart-options-panel": {
190
+ "types": "./dist/chart-options-panel.d.ts",
191
+ "default": "./dist/chart-options-panel.js"
192
+ },
193
+ "./numbering": {
194
+ "types": "./dist/numbering.d.ts",
195
+ "default": "./dist/numbering.js"
196
+ },
197
+ "./numbering-panel": {
198
+ "types": "./dist/numbering-panel.d.ts",
199
+ "default": "./dist/numbering-panel.js"
200
+ },
201
+ "./annotations": {
202
+ "types": "./dist/annotations.d.ts",
203
+ "default": "./dist/annotations.js"
84
204
  }
85
205
  },
86
206
  "types": "./dist/index.d.ts",
87
207
  "sideEffects": false,
88
208
  "scripts": {
89
209
  "build": "node scripts/build.mjs",
90
- "typecheck": "node --check src/index.js && node --check src/react.js && node --check src/svelte.js && node --check scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check test/component-smoke.mjs && node --check src/canvas.js && node --check src/canvas-fields.js && node --check test/canvas-fields.mjs && node --check test/browser-canvas.mjs && node --check src/transfer.js && node --check src/presentation-ids.js && node --check src/font-defaults.js && node --check src/color-authoring.js && node --check test/presentation-ids.mjs && node --check src/galleries.js && node --check examples/transfer-controls.js && node --check src/schema.js && node --check src/schema-inspector.js && node --check test/rich-table-browser.mjs && node --check test/styled-table.mjs && node --check test/styled-table-browser.mjs && node --check examples/pptx-controls.js && node --check scripts/build-playground.mjs && node --check test/playground-pptx.mjs && node --check test/playground-script-fonts.mjs && node --check test/playground-lazy-fonts.mjs && node --check test/playground-base-fonts.mjs && node --check src/text-input.js && node --check src/rich-text.js && node --check src/rich-text-input.js && node --check src/rich-text-toolbar.js && node --check test/rich-text.mjs && node --check test/rich-input-browser.mjs && node --check src/json-editor.js && node --check src/json-menu.js && node --check src/json-options.js && node --check src/json-source.js && node --check src/switches.js && node --check test/switches.mjs && node --check test/switches-export.mjs && node --check test/placeholder-selection.mjs && node --check test/selection-browser.mjs && node --check scripts/check-font-hotlinks.mjs && node --check scripts/check-font-hotlinks.test.mjs && node --check src/exact-source.js && node --check test/exact-source.mjs && node --check test/playground-source.mjs && node --check src/font-gate.js && node --check test/font-gate.mjs && node --check test/font-gate-registry.mjs && node --check test/playground-ensure-fonts.mjs && node --check test/canvas-fonts-browser.mjs && node --check src/text-pointer.js && node --check test/click-entry-browser.mjs",
91
- "test": "npm run build && npm run check:font-hotlinks && node test/accepted-text.mjs && node test/smoke.mjs && node test/component-smoke.mjs && node test/canvas-fields.mjs && node test/transfer.mjs && node test/gallery-font-roles.mjs && node test/default-font-scheme.mjs && node test/presentation-ids.mjs && node test/schema.mjs && node test/rich-text.mjs && node test/layout.mjs && node test/blocks.mjs && node test/styled-table.mjs && node test/json-source.mjs && node test/json-options.mjs && node test/switches.mjs && node test/switches-export.mjs && node test/placeholder-selection.mjs && node test/exact-source.mjs && node test/font-gate.mjs && node test/font-gate-registry.mjs",
210
+ "typecheck": "node --check src/index.js && node --check src/annotations.js && node --check test/annotations.mjs && node --check src/react.js && node --check src/svelte.js && node --check scripts/build.mjs && node --check scripts/validate-package.mjs && node --check test/smoke.mjs && node --check test/component-smoke.mjs && node --check src/canvas.js && node --check src/canvas-fields.js && node --check test/canvas-fields.mjs && node --check test/browser-canvas.mjs && node --check src/transfer.js && node --check src/presentation-ids.js && node --check src/font-defaults.js && node --check src/color-authoring.js && node --check test/presentation-ids.mjs && node --check src/galleries.js && node --check examples/transfer-controls.js && node --check src/schema.js && node --check src/schema-inspector.js && node --check test/rich-table-browser.mjs && node --check test/styled-table.mjs && node --check test/styled-table-browser.mjs && node --check examples/pptx-controls.js && node --check scripts/build-playground.mjs && node --check test/playground-pptx.mjs && node --check test/playground-script-fonts.mjs && node --check test/playground-lazy-fonts.mjs && node --check test/playground-base-fonts.mjs && node --check src/text-input.js && node --check src/rich-text.js && node --check src/rich-text-input.js && node --check src/rich-text-toolbar.js && node --check test/rich-text.mjs && node --check test/rich-input-browser.mjs && node --check src/json-editor.js && node --check src/json-menu.js && node --check src/json-options.js && node --check src/json-source.js && node --check src/switches.js && node --check test/switches.mjs && node --check test/switches-export.mjs && node --check test/placeholder-selection.mjs && node --check test/selection-browser.mjs && node --check scripts/check-font-hotlinks.mjs && node --check scripts/check-font-hotlinks.test.mjs && node --check src/exact-source.js && node --check test/exact-source.mjs && node --check test/playground-source.mjs && node --check src/font-gate.js && node --check test/font-gate.mjs && node --check test/font-gate-registry.mjs && node --check test/playground-ensure-fonts.mjs && node --check test/canvas-fonts-browser.mjs && node --check src/text-pointer.js && node --check test/click-entry-browser.mjs && node --check src/edit-helpers.js && node --check src/block-convert.js && node --check src/design-options.js && node --check src/table-options.js && node --check src/design-controls.js && node --check test/block-convert.mjs && node --check test/design-options.mjs && node --check test/table-options.mjs && node --check test/switches-ui.mjs && node --check test/design-controls-browser.mjs && node --check src/assets.js && node --check src/chart-options.js && node --check src/chart-options-panel.js && node --check examples/chart-controls.js && node --check test/chart-options.mjs && node --check test/chart-options-browser.mjs && node --check src/background-options.js && node --check test/assets.mjs && node --check test/background-options.mjs && node --check test/design-gaps-browser.mjs && node --check src/templates.js && node --check src/template-panel.js && node --check examples/template-controls.js && node --check test/templates.mjs && node --check test/template-panel-browser.mjs && node --check src/grid-text.js && node --check src/grid-model.js && node --check src/table-structure.js && node --check src/chart-data.js && node --check src/data-grid.js && node --check test/data-grid.mjs && node --check test/data-grid-browser.mjs && node --check src/slides.js && node --check src/outline.js && node --check src/slide-manager.js && node --check src/outline-view.js && node --check test/slides.mjs && node --check test/outline.mjs && node --check test/slides-browser.mjs && node --check src/persistence.js && node --check src/persistence-ui.js && node --check test/persistence.mjs && node --check test/persistence-browser.mjs && node --check src/find-replace.js && node --check src/find-panel.js && node --check src/image-crop.js && node --check src/image-cropper.js && node --check examples/find-controls.js && node --check examples/crop-controls.js && node --check examples/mobile-controls.js && node --check test/find-replace.mjs && node --check test/image-crop.mjs && node --check test/support/playground-harness.mjs && node --check test/find-replace-browser.mjs && node --check test/image-crop-browser.mjs && node --check test/mobile-browser.mjs && node --check src/content-actions.js && node --check src/content-controls.js && node --check test/content-actions.mjs && node --check test/font-gate-latin-families.mjs && node --check src/review.js && node --check src/review-panel.js && node --check examples/review-controls.js && node --check test/review.mjs && node --check test/review-panel-browser.mjs && node --check test/patch.mjs && node --check src/export.js && node --check src/zip.js && node --check examples/download-controls.js && node --check test/export.mjs && node --check test/playground-download.mjs && node --check src/numbering.js && node --check src/numbering-panel.js && node --check examples/numbering-controls.js && node --check test/numbering.mjs && node --check test/numbering-panel-browser.mjs",
211
+ "test": "npm run build && npm run check:font-hotlinks && node test/accepted-text.mjs && node test/smoke.mjs && node test/component-smoke.mjs && node test/canvas-fields.mjs && node test/transfer.mjs && node test/gallery-font-roles.mjs && node test/default-font-scheme.mjs && node test/presentation-ids.mjs && node test/schema.mjs && node test/rich-text.mjs && node test/layout.mjs && node test/blocks.mjs && node test/styled-table.mjs && node test/json-source.mjs && node test/json-options.mjs && node test/switches.mjs && node test/block-convert.mjs && node test/design-options.mjs && node test/templates.mjs && node test/table-options.mjs && node test/annotations.mjs && node test/switches-ui.mjs && node test/assets.mjs && node test/chart-options.mjs && node test/background-options.mjs && node test/slides.mjs && node test/outline.mjs && node test/persistence.mjs && node test/switches-export.mjs && node test/placeholder-selection.mjs && node test/exact-source.mjs && node test/font-gate.mjs && node test/data-grid.mjs && node test/font-gate-registry.mjs && node test/find-replace.mjs && node test/image-crop.mjs && node test/content-actions.mjs && node test/font-gate-latin-families.mjs && node test/review.mjs && node test/patch.mjs && node test/export.mjs && node test/numbering.mjs",
92
212
  "validate": "node scripts/validate-package.mjs",
93
213
  "test:packed": "node test/packed-install.mjs",
94
214
  "prepack": "npm run build",
95
215
  "check:font-hotlinks": "node --test scripts/check-font-hotlinks.test.mjs && node scripts/check-font-hotlinks.mjs",
96
216
  "check:font-hotlinks:built": "node scripts/check-font-hotlinks.mjs --built dist artifacts/playground",
97
217
  "build:playground": "node scripts/build-playground.mjs",
98
- "test:playground": "npm run build:playground && npm run check:font-hotlinks:built && node test/playground-pptx.mjs && node test/playground-script-fonts.mjs artifacts/playground-script-fonts && node test/playground-lazy-fonts.mjs artifacts/playground-lazy-fonts && node test/playground-base-fonts.mjs artifacts/playground-base-fonts && node test/playground-source.mjs && node test/playground-ensure-fonts.mjs artifacts/playground-ensure-fonts && node test/canvas-fonts-browser.mjs artifacts/canvas-fonts",
218
+ "test:playground": "npm run build:playground && npm run check:font-hotlinks:built && node test/playground-pptx.mjs && node test/playground-script-fonts.mjs artifacts/playground-script-fonts && node test/playground-lazy-fonts.mjs artifacts/playground-lazy-fonts && node test/playground-base-fonts.mjs artifacts/playground-base-fonts && node test/playground-source.mjs && node test/design-controls-browser.mjs && node test/design-gaps-browser.mjs && node test/chart-options-browser.mjs && node test/persistence-browser.mjs && node test/slides-browser.mjs && node test/template-panel-browser.mjs && node test/numbering-panel-browser.mjs && node test/playground-ensure-fonts.mjs artifacts/playground-ensure-fonts && node test/canvas-fonts-browser.mjs artifacts/canvas-fonts && node test/find-replace-browser.mjs && node test/image-crop-browser.mjs && node test/mobile-browser.mjs && node test/review-panel-browser.mjs && node test/playground-download.mjs artifacts/playground-download",
99
219
  "test:code-browser": "node test/code-browser.mjs",
100
220
  "test:selection-browser": "node test/selection-browser.mjs",
101
221
  "test:click-entry-browser": "node test/click-entry-browser.mjs",
102
222
  "test:metric-browser": "node test/metric-browser.mjs",
103
223
  "test:rich-input-browser": "node test/rich-input-browser.mjs",
104
224
  "test:json": "npm run build && node test/json-source.mjs && node test/json-options.mjs",
105
- "test:json-browser": "node test/json-editor-browser.mjs"
225
+ "test:json-browser": "node test/json-editor-browser.mjs",
226
+ "test:design-controls-browser": "node test/design-controls-browser.mjs",
227
+ "test:template-panel-browser": "node test/template-panel-browser.mjs",
228
+ "test:design-gaps-browser": "node test/design-gaps-browser.mjs",
229
+ "test:data-grid-browser": "node test/data-grid-browser.mjs",
230
+ "test:slides-browser": "node test/slides-browser.mjs",
231
+ "test:persistence-browser": "node test/persistence-browser.mjs",
232
+ "test:find-browser": "node test/find-replace-browser.mjs",
233
+ "test:crop-browser": "node test/image-crop-browser.mjs",
234
+ "test:mobile-browser": "node test/mobile-browser.mjs",
235
+ "test:review-panel-browser": "node test/review-panel-browser.mjs",
236
+ "test:chart-options-browser": "node test/chart-options-browser.mjs",
237
+ "test:numbering-panel-browser": "node test/numbering-panel-browser.mjs"
106
238
  },
107
239
  "dependencies": {
108
240
  "@codemirror/autocomplete": "6.20.3",
@@ -114,11 +246,11 @@
114
246
  "@codemirror/state": "6.7.6",
115
247
  "@codemirror/view": "6.43.13",
116
248
  "@lezer/highlight": "1.2.4",
117
- "@openpresentation/opf": "^0.11.4",
249
+ "@openpresentation/opf": "^0.12.0",
118
250
  "jsonc-parser": "3.3.1"
119
251
  },
120
252
  "peerDependencies": {
121
- "@openpresentation/opf-render": "^0.11.0"
253
+ "@openpresentation/opf-render": "^0.12.0"
122
254
  },
123
255
  "peerDependenciesMeta": {
124
256
  "@openpresentation/opf-render": {
@@ -144,11 +276,14 @@
144
276
  "@expo-google-fonts/noto-sans-kr": "0.4.3",
145
277
  "@expo-google-fonts/noto-sans-lao": "0.4.2",
146
278
  "@expo-google-fonts/noto-sans-malayalam": "0.4.2",
279
+ "@expo-google-fonts/noto-sans-math": "0.4.2",
147
280
  "@expo-google-fonts/noto-sans-mongolian": "0.4.1",
148
281
  "@expo-google-fonts/noto-sans-myanmar": "0.4.2",
149
282
  "@expo-google-fonts/noto-sans-oriya": "0.4.2",
150
283
  "@expo-google-fonts/noto-sans-sc": "0.4.3",
151
284
  "@expo-google-fonts/noto-sans-sinhala": "0.4.2",
285
+ "@expo-google-fonts/noto-sans-symbols": "0.4.1",
286
+ "@expo-google-fonts/noto-sans-symbols-2": "0.4.1",
152
287
  "@expo-google-fonts/noto-sans-syriac": "0.4.2",
153
288
  "@expo-google-fonts/noto-sans-tamil": "0.4.3",
154
289
  "@expo-google-fonts/noto-sans-tc": "0.4.3",
@@ -157,9 +292,14 @@
157
292
  "@expo-google-fonts/noto-sans-thai": "0.4.2",
158
293
  "@expo-google-fonts/noto-serif-hebrew": "0.4.2",
159
294
  "@expo-google-fonts/noto-serif-tibetan": "0.4.2",
160
- "@openpresentation/opf-pptx": "^0.11.7",
161
- "@openpresentation/opf-render": "^0.11.9",
295
+ "@openpresentation/opf-pptx": "^0.12.0",
296
+ "@openpresentation/opf-render": "^0.12.0",
297
+ "axe-core": "4.13.0",
162
298
  "esbuild": "0.28.2",
163
- "playwright": "1.63.0"
299
+ "pdfjs-dist": "6.3.289",
300
+ "playwright": "1.63.0",
301
+ "@expo-google-fonts/noto-color-emoji": "0.4.6",
302
+ "@expo-google-fonts/noto-emoji": "0.4.7",
303
+ "@expo-google-fonts/stix-two-math": "0.4.0"
164
304
  }
165
305
  }