@openpresentation/opf-editor 0.14.2 → 0.16.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 (50) hide show
  1. package/README.md +93 -35
  2. package/dist/assets.js +1 -1
  3. package/dist/background-options.d.ts +10 -3
  4. package/dist/background-options.js +38 -13
  5. package/dist/canvas.d.ts +8 -1
  6. package/dist/canvas.js +25 -10
  7. package/dist/catalog-control.js +21 -0
  8. package/dist/catalogs.d.ts +48 -0
  9. package/dist/catalogs.js +217 -0
  10. package/dist/checks.js +9 -7
  11. package/dist/content-actions.d.ts +2 -2
  12. package/dist/content-actions.js +3 -3
  13. package/dist/content-controls.js +9 -9
  14. package/dist/design-controls.d.ts +12 -5
  15. package/dist/design-controls.js +286 -82
  16. package/dist/design-options.d.ts +4 -4
  17. package/dist/design-options.js +6 -26
  18. package/dist/export.d.ts +8 -1
  19. package/dist/export.js +14 -4
  20. package/dist/find-replace.js +1 -0
  21. package/dist/font-gate.js +0 -11
  22. package/dist/galleries.js +32 -67
  23. package/dist/image-crop.js +8 -9
  24. package/dist/image-options.d.ts +45 -0
  25. package/dist/image-options.js +195 -0
  26. package/dist/index.d.ts +26 -8
  27. package/dist/index.js +107 -51
  28. package/dist/json-editor.js +2 -2
  29. package/dist/json-menu.js +2 -2
  30. package/dist/json-options.d.ts +3 -3
  31. package/dist/json-options.js +37 -47
  32. package/dist/layout-handles.js +13 -0
  33. package/dist/layout-placeholders.js +15 -0
  34. package/dist/react.d.ts +1 -0
  35. package/dist/react.js +6 -2
  36. package/dist/review-panel.js +8 -2
  37. package/dist/schema-inspector.js +1 -1
  38. package/dist/schema.js +1 -1
  39. package/dist/slide-manager.js +4 -2
  40. package/dist/slides.d.ts +1 -1
  41. package/dist/slides.js +6 -5
  42. package/dist/svelte.d.ts +1 -0
  43. package/dist/svelte.js +4 -4
  44. package/dist/switches.d.ts +31 -16
  45. package/dist/switches.js +194 -92
  46. package/dist/table-options.d.ts +6 -1
  47. package/dist/table-options.js +43 -0
  48. package/dist/transfer.d.ts +12 -1
  49. package/dist/transfer.js +87 -131
  50. package/package.json +22 -9
@@ -6,6 +6,7 @@
6
6
  // document state: every fix is an undoable edit through the editor session. Importing this module does not need a DOM;
7
7
  // mounting does.
8
8
  import { validate } from "@openpresentation/opf";
9
+ import { mergeCatalogs } from "./catalogs.js";
9
10
  import {
10
11
  applyReviewFix,
11
12
  countFindings,
@@ -115,6 +116,11 @@ function summaryText(counts, scopeLabel) {
115
116
  */
116
117
  export function createReviewPanel(container, options) {
117
118
  const { editor, getSlideIndex, getValidateOptions, onGoTo, onFocusField, onStatus, onIgnoredChange, onChange, review } = options;
119
+ // Core resolves the deck's references with the session's registered catalogs (and any the host adds): the same list as the preview.
120
+ const validateOptions = () => {
121
+ const host = getValidateOptions?.(editor.presentation) ?? {};
122
+ return { ...host, catalogs: mergeCatalogs(editor.catalogs, host.catalogs) };
123
+ };
118
124
  if (review !== undefined && typeof review !== "function") throw new TypeError("The Review panel's review hook must be a function (presentation, options) returning a FindingReport.");
119
125
  if (!editor || typeof editor.subscribe !== "function") throw new Error("The Review panel needs an editor session created by createEditorSession.");
120
126
  const doc = container.ownerDocument;
@@ -171,7 +177,7 @@ export function createReviewPanel(container, options) {
171
177
  function validateNow() {
172
178
  lastAuditError = undefined;
173
179
  try {
174
- coreReport = validate(editor.presentation, getValidateOptions?.(editor.presentation) ?? {});
180
+ coreReport = validate(editor.presentation, validateOptions());
175
181
  } catch (error) {
176
182
  coreReport = undefined;
177
183
  lastAuditError = error;
@@ -204,7 +210,7 @@ export function createReviewPanel(container, options) {
204
210
  if (!coreReport && !lastAuditError) validateNow();
205
211
  renderList();
206
212
  try {
207
- const result = await review(editor.presentation, { signal: controller.signal, report: coreReport, validateOptions: getValidateOptions?.(editor.presentation) ?? {} });
213
+ const result = await review(editor.presentation, { signal: controller.signal, report: coreReport, validateOptions: validateOptions() });
208
214
  if (destroyed || hookRunning !== running) return;
209
215
  if (!result || !Array.isArray(result.findings)) throw new TypeError("The review hook must resolve a FindingReport ({ valid, findings, counts }).");
210
216
  hookRunning = undefined;
@@ -18,7 +18,7 @@ export function createSchemaInspector(container, {editor,path='',onDraft,onCommi
18
18
  function showError(message){errorBox.textContent=message;}
19
19
  function check(){
20
20
  // The draft must be valid OPF (the format category, as every edit); unresolved references (an unknown variable, a missing asset) are listed as notes.
21
- const report=validate(draft,{only:['format','references']});
21
+ const report=validate(draft,{only:['format','references'],...(editor?.catalogs?.length?{catalogs:editor.catalogs}:{})});
22
22
  const errors=report.findings.filter(finding=>finding.severity==='error'&&finding.category==='format'),warnings=report.findings.filter(finding=>finding.category!=='format'&&finding.severity!=='info');
23
23
  const validation={valid:!errors.length};
24
24
  apply.disabled=!dirty||!validation.valid;
package/dist/schema.js CHANGED
@@ -69,7 +69,7 @@ export function schemaAtPath(presentation, path, root = schemas.presentation) {
69
69
  const parts=splitOpfPath(path);let current=root,value=presentation;
70
70
  for(let index=0; index<parts.length;index++) {
71
71
  const key=parts[index], resolved=activeSchema(current,value,root);
72
- if(parts[0]==='catalogs' && parts[2]==='records' && index===3) current=schemas[catalogSchemaNames[parts[1]]]??{};
72
+ if(parts[0]==='catalogs' && index===3 && catalogSchemaNames[parts[2]]) current=schemas[catalogSchemaNames[parts[2]]]??{};
73
73
  else current=Array.isArray(value)||resolved.type==='array' ? resolved.items??{} : resolved.properties?.[key] ?? Object.entries(resolved.patternProperties??{}).find(([pattern])=>new RegExp(pattern).test(key))?.[1] ?? (typeof resolved.additionalProperties==='object'?resolved.additionalProperties:{});
74
74
  value=value?.[key];
75
75
  }
@@ -7,6 +7,7 @@ import {
7
7
  addSection, addSlide, duplicateSlides, hasSections, listSections, moveSection, moveSlides, moveSlidesBy, removeSection, removeSlides, renameSection, setHidden, setSection, slideTitle,
8
8
  } from "./slides.js";
9
9
  import { listSwitchOptions } from "./switches.js";
10
+ import { layoutSlotSummary } from "./layout-placeholders.js";
10
11
 
11
12
  let instances = 0;
12
13
  const SVG_NS = "http://www.w3.org/2000/svg";
@@ -482,7 +483,7 @@ export function createSlideManager(container, options) {
482
483
  });
483
484
  }
484
485
  function layoutChoices() {
485
- return options.layoutOptions ? options.layoutOptions(deck()) : listSwitchOptions(deck(), "layouts");
486
+ return options.layoutOptions ? options.layoutOptions(deck()) : listSwitchOptions(deck(), "layouts", { catalogs: editor.catalogs });
486
487
  }
487
488
  function openLayoutPicker() {
488
489
  const choices = layoutChoices();
@@ -503,7 +504,8 @@ export function createSlideManager(container, options) {
503
504
  };
504
505
  const describe = () => {
505
506
  const choice = choices.find((entry) => entry.id === select.value);
506
- const slots = Array.isArray(choice?.record?.placeholders) ? choice.record.placeholders.map((slot) => slot.type).join(", ") : "";
507
+ // FA-26: nested placeholder groups show with their mode, for example `title, column (text, text), chart`.
508
+ const slots = layoutSlotSummary(choice?.record);
507
509
  detail.textContent = choice ? `${choice.record?.description ?? ""}${slots ? ` Slots: ${slots}.` : choice.record?.placeholders ? " An empty slide." : ""}`.trim() : "No layout matches.";
508
510
  };
509
511
  const form = el("form", { method: "dialog" },
package/dist/slides.d.ts CHANGED
@@ -48,7 +48,7 @@ export type MoveSectionMode = "adopt" | "keep" | string | null;
48
48
  export interface AddSlideOptions {
49
49
  /** The index the slide takes (default: the end). */
50
50
  at?: number;
51
- /** A layouts catalog id; the layout's placeholders are added as empty slots. */
51
+ /** A layout reference (`id` or `name:id`); the layout's placeholders are added as empty slots. */
52
52
  layout?: string;
53
53
  title?: string;
54
54
  text?: string;
package/dist/slides.js CHANGED
@@ -11,6 +11,7 @@
11
11
  import { checkedDocument, fail, same } from "./edit-helpers.js";
12
12
  import { collectReservedPresentationIds, remapSlideTreeIds } from "./presentation-ids.js";
13
13
  import { prepareDimensionSwitch } from "./switches.js";
14
+ import { mergeCatalogs } from "./catalogs.js";
14
15
  import { checkFormat } from "./checks.js";
15
16
 
16
17
  const isIndex = (value) => Number.isInteger(value) && value >= 0;
@@ -130,10 +131,10 @@ function newSlideId(presentation) {
130
131
  }
131
132
 
132
133
  /**
133
- * Compute adding a slide. Options: `at` (the index the slide takes, default the end), `layout` (a layouts
134
- * catalog id; the layout's placeholders are added as empty slots, exactly like switching a slide's layout),
134
+ * Compute adding a slide. Options: `at` (the index the slide takes, default the end), `layout` (a layout
135
+ * reference, `id` or `name:id`; the layout's placeholders are added as empty slots, exactly like switching a slide's layout),
135
136
  * `title`, `text`, `id`, `section` (default: the section of the slide before it), `slide` (a ready slide to
136
- * insert, for hosts that build their own), `catalogs`/`record` for catalog lookup (as `prepareDimensionSwitch`).
137
+ * insert, for hosts that build their own), `catalogs`/`record`/`recordSource` for catalog lookup (as `prepareDimensionSwitch`).
137
138
  */
138
139
  export function prepareAddSlide(presentation, options = {}) {
139
140
  const slides = requireSlides(presentation);
@@ -153,7 +154,7 @@ export function prepareAddSlide(presentation, options = {}) {
153
154
  // Switch the layout on a scratch copy to reuse the placeholder rules and the catalog lookup.
154
155
  const scratch = { ...clone(presentation), slides: [...clone(slides)] };
155
156
  scratch.slides.splice(at, 0, slide);
156
- const switched = prepareDimensionSwitch(scratch, "layouts", options.layout, { slideIndex: at, catalogs: options.catalogs, record: options.record });
157
+ const switched = prepareDimensionSwitch(scratch, "layouts", options.layout, { slideIndex: at, catalogs: options.catalogs, record: options.record, recordSource: options.recordSource });
157
158
  const catalogOps = switched.patches.filter((patch) => !patch.path.startsWith("/slides/"));
158
159
  slide = switched.presentation.slides[at];
159
160
  const patches = [...catalogOps, { op: "add", path: slidePointer(at), value: slide }];
@@ -164,7 +165,7 @@ export function prepareAddSlide(presentation, options = {}) {
164
165
  /** Add a slide (see `prepareAddSlide`) as one undoable step. The change reports `index`, `id` and `selection`. */
165
166
  export function addSlide(editor, options = {}, meta) {
166
167
  requireEditor(editor);
167
- return apply(editor, prepareAddSlide(editor.presentation, options), meta, "add");
168
+ return apply(editor, prepareAddSlide(editor.presentation, { ...options, catalogs: mergeCatalogs(editor.catalogs, options.catalogs) }), meta, "add");
168
169
  }
169
170
 
170
171
  // --- duplicate -------------------------------------------------------------------------------------
package/dist/svelte.d.ts CHANGED
@@ -11,6 +11,7 @@ export interface OPFCatalogSelectActionParams extends CatalogOptionsInput {
11
11
  path: string;
12
12
  catalogKind: string;
13
13
  label?: string;
14
+ onError?: (error: Error) => void;
14
15
  }
15
16
 
16
17
  export interface OPFTraceActionParams extends SvgTraceBindingOptions {
package/dist/svelte.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { commitCatalogControl } from "./catalog-control.js";
1
2
  import {
2
3
  createSvgTraceBinding,
3
4
  getCatalogOptions,
@@ -50,8 +51,8 @@ export function opfCatalogSelect(node, params) {
50
51
  node.setAttribute?.("data-opf-catalog-kind", catalogKind);
51
52
  if (label) node.setAttribute?.("aria-label", label);
52
53
  replaceOptions(node, getCatalogOptions(catalogKind, {
54
+ editor,
53
55
  catalogs: nextParams.catalogs,
54
- catalogSources: nextParams.catalogSources,
55
56
  presentation: nextParams.presentation ?? editor.presentation
56
57
  }));
57
58
 
@@ -59,12 +60,11 @@ export function opfCatalogSelect(node, params) {
59
60
  const value = editor.get(path);
60
61
  node.value = value && typeof value === "object" ? value.id : value ?? "";
61
62
  };
62
- const onChange = () => setCatalogId(editor, path, catalogKind, node.value, {
63
+ const onChange = () => commitCatalogControl(node, nextParams.onError, sync, () => setCatalogId(editor, path, catalogKind, node.value, {
63
64
  catalogs: nextParams.catalogs,
64
- catalogSources: nextParams.catalogSources,
65
65
  presentation: nextParams.presentation,
66
66
  source: "svelte-catalog-select"
67
- });
67
+ }));
68
68
  const unsubscribe = editor.subscribe(sync);
69
69
 
70
70
  node.addEventListener?.("change", onChange);
@@ -1,6 +1,14 @@
1
1
  import type { ConvertOptions } from "@openpresentation/opf/convert";
2
+ import type { Catalog } from "@openpresentation/opf";
2
3
  import type { EditorChange, EditorSession, JsonPatchOperation } from "./index.js";
3
4
 
5
+ /** Display metadata for the engine vocabularies, in the shape of `catalogDisplay` (`@openpresentation/opf/catalog`): records by id, or a list of records with `id` (`bcp47` for a language). */
6
+ export interface SwitchVocabularies {
7
+ languages?: unknown;
8
+ chartTypes?: unknown;
9
+ socialPlatforms?: unknown;
10
+ }
11
+
4
12
  /** The schema's DimensionPreset values: the values of the slide-sizes switch. */
5
13
  export declare const SLIDE_SIZE_PRESETS: readonly ["16:9", "4:3", "16:10", "1:1", "4:5", "9:16", "letter", "a4", "widescreen", "standard"];
6
14
  export type SlideSizePreset = (typeof SLIDE_SIZE_PRESETS)[number];
@@ -29,14 +37,17 @@ export type SwitchDimension = (typeof SWITCH_DIMENSIONS)[number];
29
37
  export interface DimensionSwitchOptions {
30
38
  /** Required for layouts and charts; for design dimensions it scopes the switch to one slide (default: the deck). */
31
39
  slideIndex?: number;
32
- /** blocks: the complete block (`slides.0.blocks.1`) or one-payload slide/region to replace. charts: the block holding the chart (default: the slide's first chart). */
40
+ /** blocks: the complete block (`slides.0.blocks.1`) or one-payload slide/region to replace. charts: the block holding the chart (default: the slide's first chart). image-treatments: the image block. */
33
41
  path?: string | string[];
34
- /** A gallery item's catalog record, added inline in the same transaction when the id is not already defined. */
42
+ /** A gallery item's catalog record, embedded in the same transaction (under the group of `recordSource`, else `custom`) when it is not embedded yet. */
35
43
  record?: Record<string, unknown> & { id: string };
36
- /** Extra caller-loaded catalog records by kind. */
37
- catalogs?: Record<string, unknown>;
38
- catalogSources?: Record<string, unknown>;
39
- /** themes: also write the theme's color scheme, font scheme and background (and, for the deck, dimensions) to the design (default true). A slide's design cannot set dimensions. */
44
+ /** The catalog source of `record` (for example "https://www.pptx.gallery"). */
45
+ recordSource?: string;
46
+ /** Host catalogs (`Catalog[]`): references resolve in the document, then in these. */
47
+ catalogs?: readonly Catalog[];
48
+ /** Display metadata for languages, chart types and social platforms (labels in pickers). */
49
+ vocabularies?: SwitchVocabularies;
50
+ /** themes: the theme's own color scheme, font scheme and background (and, for the deck, dimensions) apply: overrides of those at the switched scope are removed; on a slide whose deck sets one, the theme's value is written on the slide (default true). */
40
51
  bundle?: boolean;
41
52
  /** Deck-scope design switches: remove slide-level values that would hide the switch. */
42
53
  clearSlideOverrides?: boolean;
@@ -55,12 +66,11 @@ export interface DimensionSwitchOptions {
55
66
 
56
67
  export type DimensionSwitchValue =
57
68
  | null /* backgrounds: remove it */
58
- | string /* slide-sizes: a SlideSizePreset. purposes: a catalog id or free-form goal text. */
59
- | string[] /* audiences: catalog ids */
69
+ | string /* slide-sizes: a SlideSizePreset. purposes: a catalog reference or free-form goal text. languages: a BCP-47 tag. Catalog dimensions: a reference, `id` or `name:id`. */
70
+ | string[] /* audiences: catalog references */
60
71
  | { platform: string; handle: string }
61
72
  | { header?: unknown; footer?: unknown }
62
- | { slideImage?: unknown; imageFill?: string | null }
63
- | Record<string, unknown>;
73
+ | Record<string, unknown> /* image-treatments: fit, focus, aspectRatio, shape, cornerRadius, border, opacity, recolor, overlay, placement (null removes) */;
64
74
 
65
75
  export interface PreparedDimensionSwitch {
66
76
  dimension: SwitchDimension;
@@ -92,18 +102,23 @@ export declare function prepareDimensionSwitch(presentation: unknown, dimension:
92
102
  export declare function switchDimension(editor: EditorSession, dimension: SwitchDimension, value: DimensionSwitchValue, options?: DimensionSwitchOptions): DimensionSwitchChange;
93
103
 
94
104
  export interface SwitchOption {
105
+ /** The value to switch to: for a catalog dimension the reference, `id` or `name:id`. */
95
106
  id: string;
96
107
  label: string;
97
108
  record?: Record<string, unknown>;
109
+ reference?: string;
110
+ group?: string;
111
+ source?: string;
112
+ origin?: "document" | "host";
98
113
  }
99
114
  export interface CompatibleChartType extends SwitchOption {
100
115
  /** True for the chart's present type, which is always listed. */
101
116
  current: boolean;
102
117
  }
103
- /** Values a picker can offer for a catalog-backed dimension (document inline records, caller catalogs, then the bundled catalog, without duplicates); `blocks` lists the content kinds and `slide-sizes` the presets (labelled with their inches). `purposes` lists the catalog; any other goal text is also a valid switch value. */
104
- export declare function listSwitchOptions(presentation: unknown, dimension: SwitchDimension, options?: Pick<DimensionSwitchOptions, "catalogs" | "catalogSources">): SwitchOption[];
105
- /** Chart types the chart's inline data can use as it is (data-shape compatibility, not an engine-support claim). `path` or `slideIndex` picks the chart. */
106
- export declare function compatibleChartTypes(presentation: unknown, options?: Pick<DimensionSwitchOptions, "slideIndex" | "path" | "catalogs" | "catalogSources">): CompatibleChartType[];
107
- /** The value a dimension currently has: `{ value, scope }`, with the catalog id for catalog dimensions. `slide-sizes` reads the deck's design.dimensions, else its theme's (a preset string, or the object for a custom size); `purposes` reads the goal text or Purpose id. */
108
- export declare function currentSwitchValue(presentation: unknown, dimension: SwitchDimension, options?: Pick<DimensionSwitchOptions, "slideIndex" | "path" | "owner" | "index">): { value: unknown; scope: "deck" | "slide" | "block" };
118
+ /** Values a picker can offer: a catalog dimension lists core's `catalogRecords` (embedded records, then `catalogs`); `charts` and `socials` list core's vocabularies, `languages` core's `LANGUAGES` tags; `blocks` lists the content kinds and `slide-sizes` the presets. `purposes` lists the catalogs; any other goal text is also a valid switch value. */
119
+ export declare function listSwitchOptions(presentation: unknown, dimension: SwitchDimension, options?: Pick<DimensionSwitchOptions, "catalogs" | "vocabularies">): SwitchOption[];
120
+ /** Chart types the chart's inline data can use as it is (data-shape compatibility from `vocabularies.chartTypes`, not an engine-support claim). `path` or `slideIndex` picks the chart. */
121
+ export declare function compatibleChartTypes(presentation: unknown, options?: Pick<DimensionSwitchOptions, "slideIndex" | "path" | "catalogs" | "vocabularies">): CompatibleChartType[];
122
+ /** The value a dimension currently has: `{ value, scope }`, with the reference (`id` or `name:id`) for catalog dimensions. `slide-sizes` reads the deck's design.dimensions, else its theme's (a preset string, or the object for a custom size); `purposes` reads the goal text or Purpose id. */
123
+ export declare function currentSwitchValue(presentation: unknown, dimension: SwitchDimension, options?: Pick<DimensionSwitchOptions, "slideIndex" | "path" | "owner" | "index" | "catalogs">): { value: unknown; scope: "deck" | "slide" | "block" };
109
124
  export { blockConversionTargets } from "./block-convert.js";