@openpresentation/opf-editor 0.12.1 → 0.15.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 (103) hide show
  1. package/README.md +122 -80
  2. package/dist/annotations.d.ts +16 -16
  3. package/dist/annotations.js +71 -79
  4. package/dist/assets.d.ts +7 -7
  5. package/dist/assets.js +16 -16
  6. package/dist/background-options.d.ts +12 -5
  7. package/dist/background-options.js +44 -19
  8. package/dist/block-controls.js +7 -7
  9. package/dist/block-convert.d.ts +7 -7
  10. package/dist/block-convert.js +22 -21
  11. package/dist/blocks.d.ts +8 -8
  12. package/dist/blocks.js +41 -40
  13. package/dist/canvas-fields.js +35 -12
  14. package/dist/canvas.d.ts +30 -31
  15. package/dist/canvas.js +64 -68
  16. package/dist/catalogs.d.ts +48 -0
  17. package/dist/catalogs.js +217 -0
  18. package/dist/chart-data.d.ts +10 -10
  19. package/dist/chart-data.js +21 -21
  20. package/dist/chart-options-panel.d.ts +2 -2
  21. package/dist/chart-options-panel.js +82 -8
  22. package/dist/chart-options.d.ts +35 -9
  23. package/dist/chart-options.js +133 -40
  24. package/dist/checks.js +23 -0
  25. package/dist/color-authoring.js +1 -1
  26. package/dist/content-actions.d.ts +17 -17
  27. package/dist/content-actions.js +74 -73
  28. package/dist/content-controls.js +21 -21
  29. package/dist/data-grid.js +7 -8
  30. package/dist/data.d.ts +4 -4
  31. package/dist/data.js +5 -5
  32. package/dist/dataset-refs.d.ts +2 -2
  33. package/dist/dataset-refs.js +5 -5
  34. package/dist/design-controls.d.ts +12 -5
  35. package/dist/design-controls.js +312 -111
  36. package/dist/design-options.d.ts +20 -18
  37. package/dist/design-options.js +91 -99
  38. package/dist/edit-helpers.js +8 -7
  39. package/dist/exact-source.js +5 -5
  40. package/dist/export.d.ts +7 -7
  41. package/dist/export.js +48 -17
  42. package/dist/find-panel.js +5 -5
  43. package/dist/find-replace.d.ts +3 -3
  44. package/dist/find-replace.js +23 -20
  45. package/dist/font-gate.js +49 -58
  46. package/dist/galleries.js +50 -101
  47. package/dist/grid-model.d.ts +25 -27
  48. package/dist/grid-model.js +92 -102
  49. package/dist/image-crop.d.ts +4 -4
  50. package/dist/image-crop.js +28 -29
  51. package/dist/image-cropper.js +3 -3
  52. package/dist/image-options.d.ts +45 -0
  53. package/dist/image-options.js +195 -0
  54. package/dist/index.d.ts +48 -31
  55. package/dist/index.js +154 -126
  56. package/dist/json-editor.js +2 -2
  57. package/dist/json-menu.js +2 -2
  58. package/dist/json-options.d.ts +3 -3
  59. package/dist/json-options.js +44 -54
  60. package/dist/layout-handles.js +5 -5
  61. package/dist/layout-placeholders.js +4 -4
  62. package/dist/layout.d.ts +2 -2
  63. package/dist/layout.js +8 -7
  64. package/dist/numbering-panel.js +3 -5
  65. package/dist/numbering.d.ts +3 -5
  66. package/dist/numbering.js +14 -26
  67. package/dist/outline-view.js +24 -24
  68. package/dist/outline.d.ts +12 -12
  69. package/dist/outline.js +52 -50
  70. package/dist/persistence.d.ts +1 -1
  71. package/dist/persistence.js +19 -18
  72. package/dist/presentation-ids.js +2 -2
  73. package/dist/react.js +3 -3
  74. package/dist/review-panel.d.ts +30 -13
  75. package/dist/review-panel.js +160 -53
  76. package/dist/review.d.ts +36 -43
  77. package/dist/review.js +92 -53
  78. package/dist/rich-text-input.js +4 -3
  79. package/dist/rich-text-toolbar.js +5 -3
  80. package/dist/rich-text.js +5 -2
  81. package/dist/schema-inspector.d.ts +2 -2
  82. package/dist/schema-inspector.js +16 -11
  83. package/dist/schema.d.ts +1 -1
  84. package/dist/schema.js +26 -6
  85. package/dist/slide-manager.d.ts +2 -2
  86. package/dist/slide-manager.js +19 -19
  87. package/dist/slides.d.ts +21 -21
  88. package/dist/slides.js +104 -101
  89. package/dist/svelte.js +2 -3
  90. package/dist/switches.d.ts +37 -22
  91. package/dist/switches.js +282 -171
  92. package/dist/table-options.d.ts +8 -8
  93. package/dist/table-options.js +28 -27
  94. package/dist/table-structure.d.ts +10 -10
  95. package/dist/table-structure.js +21 -21
  96. package/dist/template-panel.d.ts +3 -3
  97. package/dist/template-panel.js +37 -13
  98. package/dist/templates.d.ts +22 -11
  99. package/dist/templates.js +48 -55
  100. package/dist/transfer.d.ts +15 -4
  101. package/dist/transfer.js +107 -161
  102. package/package.json +12 -6
  103. package/dist/font-defaults.js +0 -25
package/dist/galleries.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { assertOpf, unwrapOpf, MAX_OPF_BYTES } from "./transfer.js";
2
- import { catalogs } from "@openpresentation/opf";
3
2
  export function galleryUrl(input, base) {
4
3
  const url = new URL(input, base);
5
4
  if (
@@ -119,44 +118,29 @@ export async function loadOpfGallery(input, options = {}) {
119
118
  }
120
119
  const FONT_SCHEME_ENUMS = {
121
120
  type: ["sans-serif", "serif", "monospace"],
122
- app: ["PowerPoint", "Google Slides"],
123
- languageFamily: ["latin", "ea", "cs"],
121
+ app: ["powerpoint", "google-slides"],
122
+ languageFamily: ["latin", "ea", "cs", "eastAsian", "complexScript"],
124
123
  };
125
- // A Font role object ({ family, weight?, style?, letterSpacing? }), or undefined.
124
+ // A font role is a family name (a non-empty string), or undefined.
126
125
  function fontRole(value) {
127
- if (typeof value === "string") return value ? { family: value } : undefined;
128
- if (!value || typeof value !== "object" || typeof value.family !== "string")
129
- return undefined;
130
- const role = { family: value.family };
131
- if (typeof value.weight === "number" && Number.isFinite(value.weight))
132
- role.weight = value.weight;
133
- if (value.style === "normal" || value.style === "italic")
134
- role.style = value.style;
135
- if (
136
- typeof value.letterSpacing === "number" &&
137
- Number.isFinite(value.letterSpacing)
138
- )
139
- role.letterSpacing = value.letterSpacing;
140
- return role;
126
+ return typeof value === "string" && value ? value : undefined;
141
127
  }
142
128
  // Keep every font-scheme role that the catalog record schema defines: the OOXML
143
- // pair and `code`. Gallery `heading`/`body` (string or Font object) map onto
129
+ // pair and `code`. Gallery `heading`/`body` (family names) map onto
144
130
  // major/minor only, never role objects, so a later inline design.fontScheme
145
131
  // major/minor override still wins (resolveFontFamilies checks roles before the
146
132
  // pair). `accent` is not part of the record schema and is dropped.
147
133
  function fontSchemeRecord(source, id) {
148
134
  const record = {
149
- $schema: "https://openpresentation.org/schema/opf-font-scheme/v1",
150
- id,
151
135
  name: source.name ?? id,
152
136
  major:
153
137
  typeof source.major === "string"
154
138
  ? source.major
155
- : fontRole(source.heading)?.family,
139
+ : fontRole(source.heading),
156
140
  minor:
157
141
  typeof source.minor === "string"
158
142
  ? source.minor
159
- : fontRole(source.body)?.family,
143
+ : fontRole(source.body),
160
144
  };
161
145
  for (const [field, values] of Object.entries(FONT_SCHEME_ENUMS))
162
146
  if (values.includes(source[field])) record[field] = source[field];
@@ -164,10 +148,25 @@ function fontSchemeRecord(source, id) {
164
148
  if (code) record.code = code;
165
149
  return record;
166
150
  }
167
- function attachDefinition(document, descriptor) {
151
+ const isPlain = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
152
+ // Embedded records carry no `$schema`, `id` or `x-*` display metadata (OPF 0.15): the key is the id.
153
+ function embeddedRecord(source) {
154
+ return Object.fromEntries(
155
+ Object.entries(structuredClone(source)).filter(
156
+ ([key]) => key !== "$schema" && key !== "id" && key !== "slug" && !key.startsWith("x-"),
157
+ ),
158
+ );
159
+ }
160
+ /**
161
+ * A gallery item that describes one catalog record (a layout, theme, colour or font scheme) is embedded in the
162
+ * document it previews: under `default` with the gallery's origin as its `source` when the document has no default
163
+ * group (or its default is that gallery), else under `custom` (an item with no gallery URL, or another default
164
+ * catalog). Records the document already embeds are kept.
165
+ */
166
+ function attachDefinition(presentation, descriptor, gallerySource) {
168
167
  const source = descriptor.metadata?.source,
169
168
  category = descriptor.category ?? descriptor.metadata?.category;
170
- if (!source) return;
169
+ if (!isPlain(source)) return;
171
170
  const kind = {
172
171
  layouts: "layouts",
173
172
  "font-schemes": "fontSchemes",
@@ -176,49 +175,31 @@ function attachDefinition(document, descriptor) {
176
175
  }[category];
177
176
  if (!kind) return;
178
177
  const id = source.id ?? source.slug;
179
- if (!id) return;
180
- if (
181
- catalogs[kind]?.some((record) => record.id === id) ||
182
- document.catalogs?.[kind]?.records?.some((record) => record.id === id)
183
- )
184
- return;
178
+ if (typeof id !== "string" || !/^[a-z][a-z0-9-]*$/.test(id)) return;
179
+ const groups = (presentation.catalogs ??= {});
180
+ if (groups.custom?.[kind]?.[id] !== undefined || groups.default?.[kind]?.[id] !== undefined) return;
181
+ const group = gallerySource && (groups.default === undefined || groups.default?.source === gallerySource) ? "default" : "custom";
182
+ if (group === "default") groups.default ??= { source: gallerySource };
183
+ else groups.custom ??= {};
184
+ groups[group][kind] ??= {};
185
185
  let record;
186
186
  if (kind === "layouts") {
187
- const n = Math.min(
188
- 12,
189
- Number(String(source.contentMultiple ?? "").replace("x", "")) || 1,
190
- );
191
- const type =
192
- { Image: "picture", Chart: "chart", List: "list" }[source.contentType] ??
193
- "text";
187
+ // A gallery layout item is a layout record: its placeholders, design hints and composition are the
188
+ // record's own. Only an item that declares no placeholders gets the default title and text.
189
+ const placeholders = Array.isArray(source.placeholders)
190
+ ? source.placeholders
191
+ .filter((placeholder) => typeof placeholder?.type === "string")
192
+ .map((placeholder) => structuredClone(placeholder))
193
+ : [{ type: "title" }, { type: "text" }];
194
194
  record = {
195
- $schema: "https://openpresentation.org/schema/opf-layout/v1",
196
- id,
197
195
  name: source.label ?? source.name ?? id,
198
- placeholders: [
199
- { type: "title" },
200
- ...Array.from({ length: n }, () => ({ type })),
201
- ],
202
- ...(n > 1
203
- ? {
204
- composition: {
205
- mode: n <= 3 ? "row" : "grid",
206
- ...(n > 3 ? { columns: 2 } : {}),
207
- },
208
- }
209
- : {}),
196
+ ...(isPlain(source.design) ? { design: structuredClone(source.design) } : {}),
197
+ placeholders,
198
+ ...(isPlain(source.composition) ? { composition: structuredClone(source.composition) } : {}),
210
199
  };
211
200
  } else if (kind === "fontSchemes") record = fontSchemeRecord(source, id);
212
- else {
213
- record = {
214
- ...source,
215
- $schema: `https://openpresentation.org/schema/opf-${kind === "themes" ? "theme" : "color-scheme"}/v1`,
216
- };
217
- }
218
- document.catalogs ??= {};
219
- document.catalogs[kind] ??= { records: [] };
220
- document.catalogs[kind].records ??= [];
221
- document.catalogs[kind].records.push(record);
201
+ else record = embeddedRecord(source);
202
+ groups[group][kind][id] = record;
222
203
  }
223
204
  export async function loadOpfGalleryItem(item, { gallery, ...options } = {}) {
224
205
  const base = gallery ?? options.base;
@@ -234,44 +215,12 @@ export async function loadOpfGalleryItem(item, { gallery, ...options } = {}) {
234
215
  );
235
216
  if (!descriptor) throw new Error("This gallery item was not found.");
236
217
  }
237
- const document = structuredClone(unwrapOpf(descriptor));
238
- if (!document?.slides)
218
+ const presentation = structuredClone(unwrapOpf(descriptor));
219
+ if (!presentation?.slides)
239
220
  throw new Error("This gallery item does not include a presentation.");
240
- attachDefinition(document, descriptor);
241
- // PPTX.gallery's older descriptors omit inline layout records. Resolve only named
242
- // layout references from its known, same-origin registry; no arbitrary dependency crawl.
243
- if (
244
- url &&
245
- ["www.pptx.gallery", "pptx.gallery"].includes(new URL(url).hostname)
246
- ) {
247
- const missing = [
248
- ...new Set(
249
- document.slides
250
- .map((slide) => slide.layout)
251
- .filter(
252
- (id) =>
253
- typeof id === "string" &&
254
- !catalogs.layouts.some((record) => record.id === id) &&
255
- !document.catalogs?.layouts?.records?.some(
256
- (record) => record.id === id,
257
- ),
258
- ),
259
- ),
260
- ];
261
- if (missing.length > 12)
262
- throw new Error(
263
- "This item needs too many layout definitions. Supply self-contained OPF.",
264
- );
265
- for (const id of missing) {
266
- if (!/^[a-z][a-z0-9-]*$/.test(id))
267
- throw new Error("Unsupported layout reference.");
268
- const dependency = await fetchGalleryJson(
269
- `/registry/layouts/${id}.json`,
270
- { ...options, base: url, origin: new URL(url).origin },
271
- );
272
- attachDefinition(document, dependency);
273
- }
274
- }
275
- assertOpf(document);
276
- return document;
221
+ // A gallery publishes self-contained OPF (every record a document uses is embedded, FA-23); the item's own record is
222
+ // embedded too when the snippet omits it. Nothing else is fetched.
223
+ attachDefinition(presentation, descriptor, url ? new URL(url).origin : undefined);
224
+ assertOpf(presentation);
225
+ return presentation;
277
226
  }
@@ -34,18 +34,18 @@ export interface PreparedGridChange {
34
34
  path: string;
35
35
  /** The id of the shared dataset the edit changes, when the chart or table shows one (RR-54). */
36
36
  dataset?: string;
37
- document: unknown;
37
+ presentation: unknown;
38
38
  patches: JsonPatchOperation[];
39
39
  changed: boolean;
40
40
  [key: string]: unknown;
41
41
  }
42
- export interface GridChange extends Omit<EditorChange, "document" | "patches"> {
42
+ export interface GridChange extends Omit<EditorChange, "presentation" | "patches"> {
43
43
  action: string;
44
44
  kind: "chart" | "table";
45
45
  path: string;
46
46
  /** The id of the shared dataset the edit changed, when the chart or table shows one. */
47
47
  dataset?: string;
48
- document: unknown;
48
+ presentation: unknown;
49
49
  patches: JsonPatchOperation[];
50
50
  changed: boolean;
51
51
  [key: string]: unknown;
@@ -136,11 +136,11 @@ export declare function chartColumnRoles(columnCount: number, chartType?: string
136
136
  /** The text a cell shows. */
137
137
  export declare function cellText(raw: unknown, kind: "chart" | "table", decimal?: "." | ","): string;
138
138
  /**
139
- * Find the data a path points at (`…chart`, `…chart.data`, `…table`). Throws `grid-target-not-found`, `table-not-found`, `chart-data-source` or
139
+ * Find the data a path points at (`…chart`, `…chart.data`, `…table`). Throws `grid-target-not-found`, `table-not-found` or
140
140
  * `dataset-unavailable`. For a chart or table that shows a shared dataset, `data` is the dataset, `dataParts` is `["datasets", id]` and `dataset`
141
141
  * says which columns `fields` selects (`indices`, into the dataset's own columns) and where the reference is (`refParts`).
142
142
  */
143
- export declare function locateGridData(document: unknown, path: string | string[]): {
143
+ export declare function locateGridData(presentation: unknown, path: string | string[]): {
144
144
  kind: "chart" | "table";
145
145
  parts: string[];
146
146
  dataParts: string[];
@@ -150,48 +150,46 @@ export declare function locateGridData(document: unknown, path: string | string[
150
150
  dataset?: { id: string; fields?: string[]; indices: number[]; refParts: string[]; ref: Record<string, unknown> };
151
151
  };
152
152
  /** The chart or table a selection path belongs to, or undefined. */
153
- export declare function resolveDataGridTarget(document: unknown, selectedPath: string): DataGridTarget | undefined;
153
+ export declare function resolveDataGridTarget(presentation: unknown, selectedPath: string): DataGridTarget | undefined;
154
154
  /** The grid view of a chart or table: lines of cells with spans, covered positions, text, warnings. */
155
- export declare function describeDataGrid(document: unknown, path: string, options?: GridOptions): DataGridDescription;
155
+ export declare function describeDataGrid(presentation: unknown, path: string, options?: GridOptions): DataGridDescription;
156
156
  /** The smallest guarded patch from `before` to `after` at `parts`. */
157
157
  export declare function gridPatches(parts: string[], before: unknown, after: unknown): JsonPatchOperation[];
158
158
  /** The problems the edits would have, without applying them or validating the document. */
159
- export declare function gridCellIssues(document: unknown, path: string, edits: GridCellEdit[], options?: GridOptions): GridIssue[];
159
+ export declare function gridCellIssues(presentation: unknown, path: string, edits: GridCellEdit[], options?: GridOptions): GridIssue[];
160
160
  /** The text of a range (`{ from, to }` addresses; the whole grid when omitted) as TSV, or another delimiter. */
161
- export declare function gridRangeText(document: unknown, path: string, range?: { from: GridAddress; to: GridAddress }, options?: GridOptions): string;
161
+ export declare function gridRangeText(presentation: unknown, path: string, range?: { from: GridAddress; to: GridAddress }, options?: GridOptions): string;
162
162
 
163
- export declare function prepareGridCells(document: unknown, path: string, edits: GridCellEdit[], options?: GridOptions): PreparedGridChange;
164
- export declare function preparePaste(document: unknown, path: string, anchor: GridAddress, source: string | string[][], options?: GridOptions): PreparedGridChange;
165
- export declare function prepareInsertRows(document: unknown, path: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
166
- export declare function prepareDeleteRows(document: unknown, path: string, indices: number[], options?: GridOptions): PreparedGridChange;
167
- export declare function prepareMoveRows(document: unknown, path: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
168
- export declare function prepareInsertColumns(document: unknown, path: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
169
- export declare function prepareDeleteColumns(document: unknown, path: string, indices: number[], options?: GridOptions): PreparedGridChange;
170
- export declare function prepareMoveColumns(document: unknown, path: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
171
- export declare function prepareSortRows(document: unknown, path: string, column: number, options?: GridSortOptions): PreparedGridChange;
172
- export declare function prepareSetHeader(document: unknown, path: string, enabled: boolean, options?: { use?: "first-row" | "new" }): PreparedGridChange;
173
- export declare function prepareTranspose(document: unknown, path: string): PreparedGridChange;
163
+ export declare function prepareGridCells(presentation: unknown, path: string, edits: GridCellEdit[], options?: GridOptions): PreparedGridChange;
164
+ export declare function preparePaste(presentation: unknown, path: string, anchor: GridAddress, source: string | string[][], options?: GridOptions): PreparedGridChange;
165
+ export declare function prepareInsertRows(presentation: unknown, path: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
166
+ export declare function prepareDeleteRows(presentation: unknown, path: string, indices: number[], options?: GridOptions): PreparedGridChange;
167
+ export declare function prepareMoveRows(presentation: unknown, path: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
168
+ export declare function prepareInsertColumns(presentation: unknown, path: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
169
+ export declare function prepareDeleteColumns(presentation: unknown, path: string, indices: number[], options?: GridOptions): PreparedGridChange;
170
+ export declare function prepareMoveColumns(presentation: unknown, path: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
171
+ export declare function prepareSortRows(presentation: unknown, path: string, column: number, options?: GridSortOptions): PreparedGridChange;
172
+ export declare function prepareSetHeader(presentation: unknown, path: string, enabled: boolean, options?: { use?: "first-row" | "new" }): PreparedGridChange;
173
+ export declare function prepareTranspose(presentation: unknown, path: string): PreparedGridChange;
174
174
  /** Why `format` is not a valid number format ("#,##0", "0.0%", "$#,##0.00"), or undefined when it is valid or empty (empty clears the format). */
175
175
  export declare function columnFormatError(format: unknown): string | undefined;
176
- /** Whether the installed core has the RR-54 chart and table data contract (number formats, datasets, strict chart numbers). An older core edits documents that use none of it. */
177
- export declare const supportsChartTableData: boolean;
178
176
  /**
179
177
  * Set or clear (`null` or "") the number format of column `column` (its header cell). A string header becomes `{ name, format }` and returns to a string
180
178
  * when the format is cleared. Throws `number-format-invalid` with the reason, or `grid-no-header` for a table without a header row.
181
179
  */
182
- export declare function prepareGridColumnFormat(document: unknown, path: string, column: number, format: string | null | undefined, options?: GridOptions): PreparedGridChange;
180
+ export declare function prepareGridColumnFormat(presentation: unknown, path: string, column: number, format: string | null | undefined, options?: GridOptions): PreparedGridChange;
183
181
  /** The columns a chart plots and how they got that role (the chart's `mapping`, or core's positional default). */
184
- export declare function describeChartMapping(document: unknown, path: string): ChartMappingDescription;
182
+ export declare function describeChartMapping(presentation: unknown, path: string): ChartMappingDescription;
185
183
  /**
186
184
  * Set a chart's category, X and series columns by name; what `wanted` leaves out keeps its current value. A field that equals the default is not
187
185
  * written, and `chart.mapping` is removed when nothing is left. One patch. Throws `chart-mapping-unknown-column`, `chart-mapping-x-unsupported`,
188
186
  * `chart-mapping-conflict` or `chart-mapping-no-series`.
189
187
  */
190
- export declare function prepareChartMapping(document: unknown, path: string, wanted?: ChartMappingColumns): PreparedGridChange;
188
+ export declare function prepareChartMapping(presentation: unknown, path: string, wanted?: ChartMappingColumns): PreparedGridChange;
191
189
  /** Replace the chart's or table's dataset reference with its own inline copy of the data. The dataset stays for the other items. One patch. */
192
- export declare function prepareDetachDataset(document: unknown, path: string): PreparedGridChange;
190
+ export declare function prepareDetachDataset(presentation: unknown, path: string): PreparedGridChange;
193
191
  /** The charts and tables that use dataset `id`, in document order. */
194
- export declare function datasetUsage(document: unknown, id: string): Array<{ kind: "chart" | "table"; path: string; parts: string[] }>;
192
+ export declare function datasetUsage(presentation: unknown, id: string): Array<{ kind: "chart" | "table"; path: string; parts: string[] }>;
195
193
 
196
194
  export declare function setGridCells(editor: EditorSession, path: string, edits: GridCellEdit[], options?: GridOptions): GridChange;
197
195
  export declare function pasteGridText(editor: EditorSession, path: string, anchor: GridAddress, source: string | string[][], options?: GridOptions): GridChange;