@openpresentation/opf-editor 0.11.3 → 0.14.2

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 (95) hide show
  1. package/README.md +82 -58
  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 +15 -15
  6. package/dist/background-options.d.ts +2 -2
  7. package/dist/background-options.js +6 -6
  8. package/dist/block-controls.js +7 -7
  9. package/dist/block-convert.d.ts +7 -7
  10. package/dist/block-convert.js +23 -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 +22 -30
  15. package/dist/canvas.js +57 -64
  16. package/dist/chart-data.d.ts +12 -10
  17. package/dist/chart-data.js +22 -21
  18. package/dist/chart-options-panel.d.ts +2 -2
  19. package/dist/chart-options-panel.js +82 -8
  20. package/dist/chart-options.d.ts +35 -9
  21. package/dist/chart-options.js +133 -40
  22. package/dist/checks.js +21 -0
  23. package/dist/color-authoring.js +1 -1
  24. package/dist/content-actions.d.ts +15 -15
  25. package/dist/content-actions.js +71 -70
  26. package/dist/content-controls.js +12 -12
  27. package/dist/data-grid.js +179 -10
  28. package/dist/data.d.ts +23 -2
  29. package/dist/data.js +40 -1
  30. package/dist/dataset-refs.d.ts +17 -0
  31. package/dist/dataset-refs.js +45 -0
  32. package/dist/design-controls.js +66 -47
  33. package/dist/design-options.d.ts +16 -14
  34. package/dist/design-options.js +86 -74
  35. package/dist/edit-helpers.js +8 -7
  36. package/dist/exact-source.js +5 -5
  37. package/dist/export.d.ts +5 -7
  38. package/dist/export.js +40 -14
  39. package/dist/find-panel.js +5 -5
  40. package/dist/find-replace.d.ts +3 -3
  41. package/dist/find-replace.js +35 -20
  42. package/dist/font-gate.js +49 -58
  43. package/dist/galleries.js +36 -52
  44. package/dist/grid-model.d.ts +99 -24
  45. package/dist/grid-model.js +577 -101
  46. package/dist/image-crop.d.ts +4 -4
  47. package/dist/image-crop.js +20 -20
  48. package/dist/image-cropper.js +3 -3
  49. package/dist/index.d.ts +28 -28
  50. package/dist/index.js +74 -90
  51. package/dist/json-options.js +17 -16
  52. package/dist/layout-handles.js +5 -5
  53. package/dist/layout-placeholders.js +4 -4
  54. package/dist/layout.d.ts +2 -2
  55. package/dist/layout.js +8 -7
  56. package/dist/numbering-panel.js +3 -5
  57. package/dist/numbering.d.ts +3 -5
  58. package/dist/numbering.js +14 -26
  59. package/dist/outline-view.js +24 -24
  60. package/dist/outline.d.ts +12 -12
  61. package/dist/outline.js +52 -50
  62. package/dist/persistence.d.ts +1 -1
  63. package/dist/persistence.js +19 -18
  64. package/dist/presentation-ids.js +2 -2
  65. package/dist/react.js +3 -3
  66. package/dist/review-panel.d.ts +30 -13
  67. package/dist/review-panel.js +154 -53
  68. package/dist/review.d.ts +36 -43
  69. package/dist/review.js +92 -53
  70. package/dist/rich-text-input.js +4 -3
  71. package/dist/rich-text-toolbar.js +5 -3
  72. package/dist/rich-text.js +5 -2
  73. package/dist/schema-inspector.d.ts +2 -2
  74. package/dist/schema-inspector.js +16 -11
  75. package/dist/schema.d.ts +1 -1
  76. package/dist/schema.js +25 -5
  77. package/dist/slide-manager.d.ts +2 -2
  78. package/dist/slide-manager.js +18 -18
  79. package/dist/slides.d.ts +20 -20
  80. package/dist/slides.js +99 -97
  81. package/dist/svelte.js +1 -1
  82. package/dist/switches.d.ts +10 -10
  83. package/dist/switches.js +136 -109
  84. package/dist/table-options.d.ts +8 -8
  85. package/dist/table-options.js +41 -30
  86. package/dist/table-structure.d.ts +10 -10
  87. package/dist/table-structure.js +21 -21
  88. package/dist/template-panel.d.ts +3 -3
  89. package/dist/template-panel.js +37 -13
  90. package/dist/templates.d.ts +22 -11
  91. package/dist/templates.js +48 -55
  92. package/dist/transfer.d.ts +4 -4
  93. package/dist/transfer.js +74 -54
  94. package/package.json +10 -6
  95. package/dist/font-defaults.js +0 -25
@@ -143,7 +143,7 @@ export function createDesignControls(container, options = {}) {
143
143
  };
144
144
  const reselect = (blockPath) => {
145
145
  if (!blockPath || !options.onSelectPath) return;
146
- const content = readBlockContent(editor.document, blockPath);
146
+ const content = readBlockContent(editor.presentation, blockPath);
147
147
  options.onSelectPath(content && !content.explicit ? `${blockPath}.${content.key}` : blockPath);
148
148
  };
149
149
  /** Run a change; report its outcome; on failure restore every control to the document. */
@@ -299,7 +299,7 @@ export function createDesignControls(container, options = {}) {
299
299
  help: "Describes the image for screen readers. Saved with the image's asset; typed before an upload, it is used for that upload. Press Enter to apply.",
300
300
  onCommit: (value) => {
301
301
  const id = assetIdOf(lastRef);
302
- if (!id || editor.document.assets?.[id] === undefined) return;
302
+ if (!id || editor.presentation.assets?.[id] === undefined) return;
303
303
  run(() => setAssetAlt(editor, id, value), "Alt text saved.");
304
304
  },
305
305
  });
@@ -333,7 +333,7 @@ export function createDesignControls(container, options = {}) {
333
333
  source.setList(assetList());
334
334
  source.set(lastRef, note);
335
335
  const id = assetIdOf(lastRef);
336
- const entry = id ? editor.document.assets?.[id] : undefined;
336
+ const entry = id ? editor.presentation.assets?.[id] : undefined;
337
337
  // Alt text belongs to one image: another target (variant, zone, slide) never inherits what was typed for the last one.
338
338
  alt.set(entry && typeof entry === "object" && typeof entry.alt === "string" ? entry.alt : "");
339
339
  // The field keeps what is being typed, except when the image it describes has changed.
@@ -372,7 +372,7 @@ export function createDesignControls(container, options = {}) {
372
372
  root.append(scopeField.wrap);
373
373
  }
374
374
 
375
- const assetList = () => Object.keys(editor.document.assets ?? {}).map((id) => `asset:${id}`);
375
+ const assetList = () => Object.keys(editor.presentation.assets ?? {}).map((id) => `asset:${id}`);
376
376
  const stringOf = (value) => (typeof value === "string" ? value : value && typeof value === "object" && typeof value.src === "string" ? value.src : "");
377
377
  const isCustomObject = (value) => Boolean(value) && typeof value === "object";
378
378
 
@@ -392,8 +392,8 @@ export function createDesignControls(container, options = {}) {
392
392
  });
393
393
  body.append(field.wrap);
394
394
  syncs.push(() => {
395
- field.setOptions(listSwitchOptions(editor.document, dimension, catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
396
- const current = currentSwitchValue(editor.document, dimension, perSlide ? scoped() : {});
395
+ field.setOptions(listSwitchOptions(editor.presentation, dimension, catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
396
+ const current = currentSwitchValue(editor.presentation, dimension, perSlide ? scoped() : {});
397
397
  field.set(current.value, perSlide ? sourceNote(current.scope, current.value !== undefined) : "");
398
398
  });
399
399
  return field;
@@ -409,8 +409,8 @@ export function createDesignControls(container, options = {}) {
409
409
  });
410
410
  body.append(slideSize.wrap);
411
411
  syncs.push(() => {
412
- slideSize.setOptions(listSwitchOptions(editor.document, "slide-sizes").map((entry) => ({ value: entry.id, label: entry.label })));
413
- const { value } = currentSwitchValue(editor.document, "slide-sizes");
412
+ slideSize.setOptions(listSwitchOptions(editor.presentation, "slide-sizes").map((entry) => ({ value: entry.id, label: entry.label })));
413
+ const { value } = currentSwitchValue(editor.presentation, "slide-sizes");
414
414
  slideSize.set(value && typeof value === "object" ? "Custom size" : value);
415
415
  });
416
416
 
@@ -420,8 +420,8 @@ export function createDesignControls(container, options = {}) {
420
420
  });
421
421
  body.append(layout.wrap);
422
422
  syncs.push(() => {
423
- layout.setOptions(listSwitchOptions(editor.document, "layouts", catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
424
- layout.set(currentSwitchValue(editor.document, "layouts", { slideIndex: getSlide() }).value);
423
+ layout.setOptions(listSwitchOptions(editor.presentation, "layouts", catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
424
+ layout.set(currentSwitchValue(editor.presentation, "layouts", { slideIndex: getSlide() }).value);
425
425
  });
426
426
  }
427
427
 
@@ -525,7 +525,7 @@ export function createDesignControls(container, options = {}) {
525
525
  body.append(typeSelect.wrap, themePanel, solidPanel, gradientPanel, imagePanel, patternPanel, opacity.wrap, h("div", { class: "opf-dc-actions" }, apply, remove));
526
526
 
527
527
  const load = () => {
528
- const current = readBackground(editor.document, scoped());
528
+ const current = readBackground(editor.presentation, scoped());
529
529
  const note = sourceNote(current.scope, current.type !== undefined);
530
530
  typeSelect.set(current.type ?? "theme", note);
531
531
  slot.set(current.type === "theme" ? current.slot : "light1");
@@ -592,10 +592,10 @@ export function createDesignControls(container, options = {}) {
592
592
  onCommit: (value) => image({ size: value }, "Image size changed."),
593
593
  });
594
594
  const inset = checkField("image-inset", "Inset inside the slide padding", { onChange: (checked) => image({ inset: checked ? true : null }, "Image inset changed.") });
595
- const placeholderFill = selectField("image-placeholder-fill", "Picture placeholders", {
595
+ const placeholderFill = selectField("image-placeholder-fill", "Image placeholders", {
596
596
  empty: "Presentation default",
597
- help: "How pictures fill their layout placeholders across the presentation.",
598
- onChange: (value) => run(() => switchDimension(editor, "image-treatments", { imageFill: value === "" ? null : value }), "Picture placeholder fill changed."),
597
+ help: "How images fill their layout placeholders across the presentation.",
598
+ onChange: (value) => run(() => switchDimension(editor, "image-treatments", { imageFill: value === "" ? null : value }), "Image placeholder fill changed."),
599
599
  });
600
600
  placeholderFill.setOptions([
601
601
  { value: "crop", label: "Crop to fill" },
@@ -603,7 +603,7 @@ export function createDesignControls(container, options = {}) {
603
603
  ]);
604
604
  body.append(position.wrap, source.wrap, fill.wrap, shape.wrap, size.wrap, inset.wrap, placeholderFill.wrap);
605
605
  syncs.push(() => {
606
- const option = getDesignOption(editor.document, "slideImage", scoped());
606
+ const option = getDesignOption(editor.presentation, "slideImage", scoped());
607
607
  const value = option.value;
608
608
  const object = value && typeof value === "object" && !Array.isArray(value) && value.position ? value : undefined;
609
609
  const note = sourceNote(option.scope, option.value !== undefined);
@@ -615,7 +615,7 @@ export function createDesignControls(container, options = {}) {
615
615
  inset.set(object?.inset === true);
616
616
  // Fit, shape, size and inset only make sense once the image has a position.
617
617
  for (const control of [fill, shape, size, inset]) control.wrap.hidden = !object;
618
- placeholderFill.set(editor.document.design?.imageFill ?? "");
618
+ placeholderFill.set(editor.presentation.design?.imageFill ?? "");
619
619
  });
620
620
  }
621
621
 
@@ -632,7 +632,7 @@ export function createDesignControls(container, options = {}) {
632
632
  { value: "footer", label: "Footer" },
633
633
  ]);
634
634
  furniture.set(pick.which);
635
- const zoneSelect = selectField("hf-zone", "Zone", { help: "Each zone stacks its parts top to bottom: logo, image, text, organization, socials, section, slide number, date." });
635
+ const zoneSelect = selectField("hf-zone", "Zone", { help: "Each zone stacks its parts top to bottom: logo, image, text, organization, speaker, socials, section, slide number, date." });
636
636
  zoneSelect.setOptions(HEADER_FOOTER_ZONES.map((value) => ({ value, label: titleCase(value) })));
637
637
  zoneSelect.set(pick.zone);
638
638
  furniture.select.addEventListener("change", () => {
@@ -678,22 +678,23 @@ export function createDesignControls(container, options = {}) {
678
678
  onCommit: (value) => edit({ dateFormat: value.trim() }, `${where()} date format changed.`),
679
679
  });
680
680
  const organization = checkField("hf-organization", "Show the organization name", { onChange: (checked) => edit({ organization: checked }, `${where()} organization ${checked ? "shown" : "removed"}.`) });
681
+ const speaker = checkField("hf-speaker", "Show the speaker name and title", { onChange: (checked) => edit({ speaker: checked }, `${where()} speaker ${checked ? "shown" : "removed"}.`) });
681
682
  const socials = checkField("hf-socials", "Show the organization's social profiles", { onChange: (checked) => edit({ socials: checked }, `${where()} social profiles ${checked ? "shown" : "removed"}.`) });
682
683
  const section = checkField("hf-section", "Show the section label", { onChange: (checked) => edit({ section: checked }, `${where()} section label ${checked ? "shown" : "removed"}.`) });
683
684
  const summary = h("ul", { class: "opf-dc-summary", "aria-label": "Zones in use" });
684
685
  const warnings = h("ul", { class: "opf-dc-warnings", "aria-label": "Header and footer warnings" });
685
- const controls = [text, logo, image, number, numberFormat, dateNow, dateFixed, dateFormat, organization, socials, section];
686
+ const controls = [text, logo, image, number, numberFormat, dateNow, dateFixed, dateFormat, organization, speaker, socials, section];
686
687
  body.append(furniture.wrap, hide.wrap, zoneSelect.wrap, summary, ...controls.map((control) => control.wrap), warnings);
687
688
  const describe = (fields) =>
688
689
  Object.entries(fields)
689
690
  .map(([key, value]) => (typeof value === "boolean" ? key : `${key} ${typeof value === "string" ? `“${value}”` : ""}`.trim()))
690
691
  .join(", ");
691
692
  syncs.push(() => {
692
- const state = headerFooterState(editor.document, which(), scoped());
693
+ const state = headerFooterState(editor.presentation, which(), scoped());
693
694
  // Offered on a slide that inherits the deck's furniture or already hides it, never over zones the slide set itself; at the deck only to undo a hidden state.
694
695
  hide.wrap.hidden = scopeIndex() === undefined ? !state.hidden : state.own && !state.hidden;
695
696
  hide.set(state.hidden, state.inherited && !state.hidden ? "from the presentation" : "");
696
- const fields = readHeaderFooterZone(editor.document, which(), zone(), scoped());
697
+ const fields = readHeaderFooterZone(editor.presentation, which(), zone(), scoped());
697
698
  text.set(typeof fields.text === "string" ? fields.text : "");
698
699
  logo.set(fields.logo === true);
699
700
  image.set(stringOf(fields.image), "", `${target()}:${which()}:${zone()}`);
@@ -703,16 +704,17 @@ export function createDesignControls(container, options = {}) {
703
704
  dateFixed.set(typeof fields.date === "string" ? fields.date : "");
704
705
  dateFormat.set(typeof fields.dateFormat === "string" ? fields.dateFormat : "");
705
706
  organization.set(fields.organization === true);
707
+ speaker.set(fields.speaker === true);
706
708
  socials.set(fields.socials === true);
707
709
  section.set(fields.section === true);
708
710
  for (const control of controls) for (const input of control.wrap.querySelectorAll("input,select,button")) input.disabled = state.hidden;
709
711
  summary.replaceChildren(
710
712
  ...HEADER_FOOTER_ZONES.map((name) => {
711
- const zoneFields = readHeaderFooterZone(editor.document, which(), name, scoped());
713
+ const zoneFields = readHeaderFooterZone(editor.presentation, which(), name, scoped());
712
714
  return h("li", {}, `${titleCase(name)}: ${Object.keys(zoneFields).length ? describe(zoneFields) : "empty"}`);
713
715
  }),
714
716
  );
715
- warnings.replaceChildren(...designWarnings(editor.document, getSlide()).filter((warning) => /^design\.(header|footer)/.test(warning.path)).map((warning) => h("li", {}, warning.message)));
717
+ warnings.replaceChildren(...designWarnings(editor.presentation, getSlide()).filter((warning) => /^design\.(header|footer)/.test(warning.path)).map((warning) => h("li", {}, warning.message)));
716
718
  });
717
719
  }
718
720
 
@@ -752,6 +754,18 @@ export function createDesignControls(container, options = {}) {
752
754
  apply: (ref) => setDesignOption(editor, "watermark", ref === null ? null : { src: ref }, scoped()),
753
755
  build: (ref, doc) => prepareDesignOption(doc, "watermark", { src: ref }, scoped()),
754
756
  });
757
+ // FA-13: a watermark is an image or a text stamp; the text field and the image cluster replace each other.
758
+ const watermarkText = textField("watermark-text", "Watermark text", {
759
+ placeholder: "DRAFT",
760
+ help: "A text stamp centered on the slide, drawn diagonally in the theme text color at the opacity below. Press Enter to apply; clear the field to remove it. It replaces a watermark image.",
761
+ onCommit: (value) => run(() => {
762
+ const text = value.trim();
763
+ const current = getDesignOption(editor.presentation, "watermark", scoped()).value;
764
+ if (text === "") return current && typeof current === "object" && typeof current.text === "string" ? setDesignOption(editor, "watermark", null, scoped()) : { changed: false };
765
+ const opacity = current && typeof current === "object" && typeof current.opacity === "number" ? current.opacity : 0.1;
766
+ return setDesignOption(editor, "watermark", { text, opacity }, scoped());
767
+ }, value.trim() === "" ? "Watermark removed." : "Watermark text set."),
768
+ });
755
769
  const watermarkOpacity = numberField("watermark-opacity", "Watermark opacity (0 to 1)", {
756
770
  min: 0,
757
771
  max: 1,
@@ -761,30 +775,31 @@ export function createDesignControls(container, options = {}) {
761
775
  const watermarkOff = checkField("watermark-off", "Hide the inherited watermark", {
762
776
  onChange: (checked) => run(() => setDesignOption(editor, "watermark", checked ? false : null, scoped()), checked ? "Watermark hidden." : "Watermark restored."),
763
777
  });
764
- body.append(variant.wrap, logoSource.wrap, orgLogo.wrap, bullet.wrap, accent.wrap, watermarkSource.wrap, watermarkOpacity.wrap, watermarkOff.wrap);
778
+ body.append(variant.wrap, logoSource.wrap, orgLogo.wrap, bullet.wrap, accent.wrap, watermarkSource.wrap, watermarkText.wrap, watermarkOpacity.wrap, watermarkOff.wrap);
765
779
  const warningList = h("ul", { class: "opf-dc-warnings", "aria-label": "Logo warnings" });
766
780
  body.append(warningList);
767
781
  syncs.push(() => {
768
- const variants = readLogoVariants(editor.document, scoped());
782
+ const variants = readLogoVariants(editor.presentation, scoped());
769
783
  logoSource.set(stringOf(variants[variant.select.value]), "", `${target()}:${variant.select.value}`);
770
- const organization = editor.document.organization;
784
+ const organization = editor.presentation.organization;
771
785
  const owner = Array.isArray(organization) ? organization[0] : organization;
772
786
  orgLogo.set(stringOf(owner?.logo), "", "org");
773
787
  for (const input of orgLogo.wrap.querySelectorAll("input")) input.disabled = !owner;
774
- const bulletOption = getDesignOption(editor.document, "listBullet", scoped());
788
+ const bulletOption = getDesignOption(editor.presentation, "listBullet", scoped());
775
789
  bullet.set(bulletOption.value ?? "", sourceNote(bulletOption.scope, bulletOption.value !== undefined));
776
- const accentOption = getDesignOption(editor.document, "accentFont", scoped());
790
+ const accentOption = getDesignOption(editor.presentation, "accentFont", scoped());
777
791
  accent.set(accentOption.value ?? "", sourceNote(accentOption.scope, accentOption.value !== undefined));
778
- const watermark = getDesignOption(editor.document, "watermark", scoped());
792
+ const watermark = getDesignOption(editor.presentation, "watermark", scoped());
779
793
  const mark = watermark.value;
780
- // The opacity belongs to a watermark image; without one there is nothing to fade.
781
- for (const input of watermarkOpacity.wrap.querySelectorAll("input")) input.disabled = !(typeof mark === "string" || (mark && typeof mark === "object" && typeof mark.src === "string"));
794
+ // The opacity belongs to a watermark image or text; without one there is nothing to fade.
795
+ for (const input of watermarkOpacity.wrap.querySelectorAll("input")) input.disabled = !(typeof mark === "string" || (mark && typeof mark === "object" && (typeof mark.src === "string" || typeof mark.text === "string")));
796
+ watermarkText.set(mark && typeof mark === "object" && typeof mark.text === "string" ? mark.text : "", sourceNote(watermark.scope, mark !== undefined));
782
797
  watermarkSource.set(typeof mark === "string" ? mark : mark && typeof mark === "object" ? stringOf(mark) : "", sourceNote(watermark.scope, mark !== undefined), target());
783
798
  watermarkOpacity.set(mark && typeof mark === "object" && typeof mark.opacity === "number" ? String(mark.opacity) : "");
784
- const own = scopeIndex() === undefined ? editor.document.design?.watermark : editor.document.slides?.[scopeIndex()]?.design?.watermark;
799
+ const own = scopeIndex() === undefined ? editor.presentation.design?.watermark : editor.presentation.slides?.[scopeIndex()]?.design?.watermark;
785
800
  watermarkOff.set(own === false);
786
801
  watermarkOff.wrap.hidden = state.scope !== "slide";
787
- warningList.replaceChildren(...designWarnings(editor.document, getSlide()).filter((warning) => warning.path === "design.listBullet").map((warning) => h("li", {}, warning.message)));
802
+ warningList.replaceChildren(...designWarnings(editor.presentation, getSlide()).filter((warning) => warning.path === "design.listBullet").map((warning) => h("li", {}, warning.message)));
788
803
  });
789
804
  }
790
805
 
@@ -801,7 +816,7 @@ export function createDesignControls(container, options = {}) {
801
816
  field.setOptions(values.map((value) => ({ value, label: labels[value] ?? titleCase(value) })));
802
817
  body.append(field.wrap);
803
818
  syncs.push(() => {
804
- const option = getDesignOption(editor.document, id, scoped());
819
+ const option = getDesignOption(editor.presentation, id, scoped());
805
820
  field.set(option.value ?? "", sourceNote(option.scope, option.value !== undefined));
806
821
  });
807
822
  };
@@ -819,7 +834,7 @@ export function createDesignControls(container, options = {}) {
819
834
  ]);
820
835
  body.append(box.wrap);
821
836
  syncs.push(() => {
822
- const option = getDesignOption(editor.document, "contentBox", scoped());
837
+ const option = getDesignOption(editor.presentation, "contentBox", scoped());
823
838
  box.set(option.value === undefined ? "" : option.value ? "yes" : "no", sourceNote(option.scope, option.value !== undefined));
824
839
  });
825
840
  }
@@ -832,11 +847,11 @@ export function createDesignControls(container, options = {}) {
832
847
  const field = selectField(name, label, { help, onChange: (value) => run(() => switchDimension(editor, dimension, value, catalogOptions()), `${label} set to ${field.select.selectedOptions[0]?.textContent ?? value}.`) });
833
848
  body.append(field.wrap);
834
849
  syncs.push(() => {
835
- field.setOptions(listSwitchOptions(editor.document, dimension, catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
836
- field.set(currentSwitchValue(editor.document, dimension).value);
850
+ field.setOptions(listSwitchOptions(editor.presentation, dimension, catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
851
+ field.set(currentSwitchValue(editor.presentation, dimension).value);
837
852
  });
838
853
  };
839
- single("narrative", "Narrative", "narratives", "The storyline the deck follows. Authoring metadata: the slides do not change.");
854
+ single("narrative", "Narrative", "narratives", "The narrative plan the deck points at (a catalog id; a custom one is a record in the JSON source). The slides do not change, and keep their beat links.");
840
855
  single("tone", "Tone", "tones");
841
856
  single("purpose", "Purpose", "purposes", "What the deck is for. Authoring metadata: the slides do not change. A goal written in the JSON source shows as custom.");
842
857
  const audience = selectField("audience", "Audience", {
@@ -850,9 +865,11 @@ export function createDesignControls(container, options = {}) {
850
865
  });
851
866
  body.append(audience.wrap);
852
867
  syncs.push(() => {
853
- audience.setOptions(listSwitchOptions(editor.document, "audiences", catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
854
- const value = editor.document.audience;
855
- audience.set(Array.isArray(value) ? value : value === undefined ? [] : [value]);
868
+ audience.setOptions(listSwitchOptions(editor.presentation, "audiences", catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
869
+ // The root audience is a string, one inline Audience object or an array of both: the picker shows the catalog ids.
870
+ const value = editor.presentation.audience;
871
+ const entries = Array.isArray(value) ? value : value === undefined ? [] : [value];
872
+ audience.set(entries.map((entry) => (entry && typeof entry === "object" ? entry.id : entry)).filter((id) => typeof id === "string"));
856
873
  });
857
874
 
858
875
  const owner = selectField("social-owner", "Socials belong to");
@@ -866,17 +883,17 @@ export function createDesignControls(container, options = {}) {
866
883
  handle.input.addEventListener("change", applySocial);
867
884
  body.append(owner.wrap, platform.wrap, handle.wrap, button("Save handle", applySocial));
868
885
  const showHandle = () => {
869
- const host = editor.document[owner.select.value];
886
+ const host = editor.presentation[owner.select.value];
870
887
  const target = Array.isArray(host) ? host[0] : host;
871
888
  handle.set(typeof target?.socials?.[platform.select.value] === "string" ? target.socials[platform.select.value] : "");
872
889
  };
873
890
  owner.select.addEventListener("change", showHandle);
874
891
  platform.select.addEventListener("change", showHandle);
875
892
  syncs.push(() => {
876
- const owners = ["speaker", "organization"].filter((name) => editor.document[name]);
893
+ const owners = ["speaker", "organization"].filter((name) => editor.presentation[name]);
877
894
  owner.setOptions(owners.length ? owners.map((name) => ({ value: name, label: titleCase(name) })) : [{ value: "speaker", label: "Speaker (add one first)", disabled: true }]);
878
895
  if (!owner.select.value || !owners.includes(owner.select.value)) owner.select.value = owners[0] ?? "speaker";
879
- platform.setOptions(listSwitchOptions(editor.document, "socials", catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
896
+ platform.setOptions(listSwitchOptions(editor.presentation, "socials", catalogOptions()).map((entry) => ({ value: entry.id, label: entry.label })));
880
897
  showHandle();
881
898
  });
882
899
  }
@@ -972,7 +989,7 @@ export function createDesignControls(container, options = {}) {
972
989
 
973
990
  function syncSelection() {
974
991
  const selected = getSelected();
975
- const document_ = editor.document;
992
+ const document_ = editor.presentation;
976
993
  const blockPath = selected ? blockPathForSelection(document_, selected) : undefined;
977
994
  const content = blockPath ? readBlockContent(document_, blockPath) : undefined;
978
995
  dynamic.blockPath = blockPath;
@@ -1009,9 +1026,11 @@ export function createDesignControls(container, options = {}) {
1009
1026
  const tablePath = parsed?.tablePath ?? (content?.key === "table" ? `${blockPath}.table` : undefined);
1010
1027
  dynamic.tablePath = tablePath;
1011
1028
  dynamic.cell = parsed?.cell;
1012
- tableUi.details.hidden = !tablePath;
1013
- if (tablePath) {
1014
- const table = editor.get(tablePath);
1029
+ // A table that shows a shared dataset (RR-54) has no cell styles or merges: the table panel is for inline tables.
1030
+ const tableValue = tablePath ? editor.get(tablePath) : undefined;
1031
+ tableUi.details.hidden = !tablePath || !Array.isArray(tableValue?.rows);
1032
+ if (tablePath && !tableUi.details.hidden) {
1033
+ const table = tableValue;
1015
1034
  tableUi.summary.textContent = `Table (${tablePath})`;
1016
1035
  const style = readTableStyle(document_, tablePath);
1017
1036
  dynamic.tableStyle = style.preset === "custom" ? { header: "theme", banding: false, borders: "theme" } : { header: style.header, banding: style.banding, borders: style.borders };
@@ -25,7 +25,7 @@ export declare const DESIGN_OPTIONS: readonly DesignOptionDescriptor[];
25
25
  export declare const LOGO_VARIANTS: readonly ["default", "light", "dark", "stacked", "stackedLight", "stackedDark", "icon", "iconLight", "iconDark", "wordmark", "wordmarkLight", "wordmarkDark"];
26
26
  export type LogoVariant = (typeof LOGO_VARIANTS)[number];
27
27
  /** Every field a header or footer zone can hold. */
28
- export declare const ZONE_FIELDS: readonly ["logo", "text", "image", "slideNumber", "slideNumberFormat", "date", "dateFormat", "organization", "socials", "section"];
28
+ export declare const ZONE_FIELDS: readonly ["logo", "text", "image", "slideNumber", "slideNumberFormat", "date", "dateFormat", "organization", "speaker", "socials", "section"];
29
29
  /** Date format tokens (English names). */
30
30
  export declare const DATE_FORMAT_TOKENS: readonly string[];
31
31
  export declare const HEADER_FOOTER_ZONES: readonly ["left", "center", "right"];
@@ -53,7 +53,7 @@ export interface PreparedDesignOption {
53
53
  /** The logo variant or header/footer zone an edit named. */
54
54
  variant?: string;
55
55
  zone?: string;
56
- document: unknown;
56
+ presentation: unknown;
57
57
  patches: JsonPatchOperation[];
58
58
  changed: boolean;
59
59
  /** Slides whose own design hides a deck-level change. */
@@ -61,8 +61,8 @@ export interface PreparedDesignOption {
61
61
  /** Settings that need a logo the document does not have. */
62
62
  warnings: DesignWarning[];
63
63
  }
64
- export interface DesignOptionChange extends Omit<EditorChange, "document" | "patches"> {
65
- document: unknown;
64
+ export interface DesignOptionChange extends Omit<EditorChange, "presentation" | "patches"> {
65
+ presentation: unknown;
66
66
  patches: JsonPatchOperation[];
67
67
  option: string;
68
68
  scope: "deck" | "slide";
@@ -73,16 +73,16 @@ export interface DesignOptionChange extends Omit<EditorChange, "document" | "pat
73
73
  }
74
74
 
75
75
  /** Compute the patch for one option. `null` removes it at that scope; object options (watermark, slideImage) merge the fields passed. */
76
- export declare function prepareDesignOption(document: unknown, option: DesignOptionId, value: unknown, options?: DesignOptionOptions): PreparedDesignOption;
76
+ export declare function prepareDesignOption(presentation: unknown, option: DesignOptionId, value: unknown, options?: DesignOptionOptions): PreparedDesignOption;
77
77
  /** Set one option as a single undoable transaction. */
78
78
  export declare function setDesignOption(editor: EditorSession, option: DesignOptionId, value: unknown, options?: DesignOptionOptions): DesignOptionChange;
79
79
  /** `{ value, scope, inherited }`; scope is "slide", "deck" or "default". */
80
- export declare function getDesignOption(document: unknown, option: DesignOptionId, options?: Pick<DesignOptionOptions, "slideIndex" | "index">): { value: unknown; scope: "slide" | "deck" | "default"; inherited: boolean };
81
- export declare function prepareLogoVariant(document: unknown, variant: LogoVariant, source: string | Record<string, unknown> | null, options?: DesignOptionOptions): PreparedDesignOption;
80
+ export declare function getDesignOption(presentation: unknown, option: DesignOptionId, options?: Pick<DesignOptionOptions, "slideIndex" | "index">): { value: unknown; scope: "slide" | "deck" | "default"; inherited: boolean };
81
+ export declare function prepareLogoVariant(presentation: unknown, variant: LogoVariant, source: string | Record<string, unknown> | null, options?: DesignOptionOptions): PreparedDesignOption;
82
82
  /** Set or clear one `design.logo` variant; a lone default stays a bare source. */
83
83
  export declare function setLogoVariant(editor: EditorSession, variant: LogoVariant, source: string | Record<string, unknown> | null, options?: DesignOptionOptions): DesignOptionChange;
84
84
  /** The variants `design.logo` sets at a scope (a bare logo is reported as `default`). */
85
- export declare function readLogoVariants(document: unknown, options?: Pick<DesignOptionOptions, "slideIndex">): Partial<Record<LogoVariant, unknown>>;
85
+ export declare function readLogoVariants(presentation: unknown, options?: Pick<DesignOptionOptions, "slideIndex">): Partial<Record<LogoVariant, unknown>>;
86
86
  export interface HeaderFooterZoneFields {
87
87
  logo?: boolean | null;
88
88
  text?: string | null;
@@ -92,17 +92,19 @@ export interface HeaderFooterZoneFields {
92
92
  date?: boolean | string | null;
93
93
  dateFormat?: string | null;
94
94
  organization?: boolean | null;
95
+ /** The first speaker's name and title ("Ada Lovelace, CTO"). */
96
+ speaker?: boolean | null;
95
97
  socials?: boolean | null;
96
98
  section?: boolean | null;
97
99
  }
98
- export declare function prepareHeaderFooterZone(document: unknown, which: "header" | "footer", zone: HeaderFooterZone, fields: HeaderFooterZoneFields, options?: DesignOptionOptions): PreparedDesignOption;
100
+ export declare function prepareHeaderFooterZone(presentation: unknown, which: "header" | "footer", zone: HeaderFooterZone, fields: HeaderFooterZoneFields, options?: DesignOptionOptions): PreparedDesignOption;
99
101
  /** Merge fields into one header or footer zone; null, false (flags) or "" remove a field, an empty zone and header are removed. A slide's own header replaces the deck's whole one, so the first edit on a slide starts from a copy of the deck's and keeps its other zones. */
100
102
  export declare function setHeaderFooterZone(editor: EditorSession, which: "header" | "footer", zone: HeaderFooterZone, fields: HeaderFooterZoneFields, options?: DesignOptionOptions): DesignOptionChange;
101
103
  /** One zone's fields as they apply at a scope: the slide's own header or footer when it has one, else the deck's. */
102
- export declare function readHeaderFooterZone(document: unknown, which: "header" | "footer", zone: HeaderFooterZone, options?: Pick<DesignOptionOptions, "slideIndex">): HeaderFooterZoneFields;
104
+ export declare function readHeaderFooterZone(presentation: unknown, which: "header" | "footer", zone: HeaderFooterZone, options?: Pick<DesignOptionOptions, "slideIndex">): HeaderFooterZoneFields;
103
105
  /** Whether the scope sets the header or footer itself (`own`), shows the deck's (`inherited`) or hides it with `false` (`hidden`). */
104
- export declare function headerFooterState(document: unknown, which: "header" | "footer", options?: Pick<DesignOptionOptions, "slideIndex">): { own: boolean; inherited: boolean; hidden: boolean };
106
+ export declare function headerFooterState(presentation: unknown, which: "header" | "footer", options?: Pick<DesignOptionOptions, "slideIndex">): { own: boolean; inherited: boolean; hidden: boolean };
105
107
  /** Whether a logo resolves for the slide: slide design, deck design, then the primary organization. */
106
- export declare function hasResolvableLogo(document: unknown, slideIndex: number): boolean;
107
- /** Settings that need content the document does not have: a logo (zones with `logo: true`, picture bullets), or an organization or its social profiles for zones that show them. */
108
- export declare function designWarnings(document: unknown, slideIndex?: number): DesignWarning[];
108
+ export declare function hasResolvableLogo(presentation: unknown, slideIndex: number): boolean;
109
+ /** Settings that need content the document does not have: a logo (zones with `logo: true`, picture bullets), an organization or its social profiles, or a named speaker, for zones that show them. */
110
+ export declare function designWarnings(presentation: unknown, slideIndex?: number): DesignWarning[];