@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
@@ -1,4 +1,5 @@
1
- import { catalogKinds, catalogSchemaNames, schemas, validateCatalogRecord, validatePresentation } from "@openpresentation/opf";
1
+ import { catalogKinds, catalogSchemaNames, schemas, validateCatalogRecord } from "@openpresentation/opf";
2
+ import { checkFormat, firstErrorMessage } from "./checks.js";
2
3
  import { getCatalogOptions } from "./index.js";
3
4
  import { schemaAtPath, schemaVariants } from "./schema.js";
4
5
  import { applyEdits, findNodeAtLocation, findNodeAtOffset, getNodePath, modify, parseTree } from "jsonc-parser";
@@ -19,8 +20,8 @@ function placeholderSummary(types) {
19
20
  return "Placeholders not specified";
20
21
  if (!types.length)
21
22
  return "No placeholders";
22
- const labels = { title: "Title", subtitle: "Subtitle", text: "Text", metric: "Metric", list: "List", picture: "Image", chart: "Chart", table: "Table", code: "Code", media: "Video", quote: "Quote", timeline: "Timeline", tag: "Tag", diagram: "Image" };
23
- const order = ["title", "subtitle", "text", "metric", "list", "picture", "chart", "table", "code", "media", "quote", "timeline", "tag", "diagram"];
23
+ const labels = { title: "Title", subtitle: "Subtitle", tag: "Tag", text: "Text", list: "List", image: "Image", video: "Video", chart: "Chart", table: "Table", code: "Code", metric: "Metric", quote: "Quote", timeline: "Timeline" };
24
+ const order = ["title", "subtitle", "tag", "text", "list", "image", "video", "chart", "table", "code", "metric", "quote", "timeline"];
24
25
  return [...new Set(types)].sort((a, b) => order.indexOf(a) - order.indexOf(b)).map(type => {
25
26
  const count = types.filter(value => value === type).length;
26
27
  return `${labels[type] ?? type}${count > 1 ? ` × ${count}` : ""}`;
@@ -64,9 +65,9 @@ function describeOptions(choices, records, current, catalog) {
64
65
  // The installed schema identifies fields; a document field merely named "layout"
65
66
  // inside arbitrary metadata is not mistaken for an OPF layout reference.
66
67
  export function getJsonFieldContext(source, position, loadedCatalogs = {}) {
67
- let document;
68
+ let presentation;
68
69
  try {
69
- document = JSON.parse(source);
70
+ presentation = JSON.parse(source);
70
71
  }
71
72
  catch {
72
73
  return null;
@@ -84,18 +85,18 @@ export function getJsonFieldContext(source, position, loadedCatalogs = {}) {
84
85
  const path = getNodePath(node);
85
86
  if (!path.length)
86
87
  return null;
87
- const schema = schemaAtPath(document, path.map(String));
88
+ const schema = schemaAtPath(presentation, path.map(String));
88
89
  const variants = schemaVariants(schema);
89
90
  const description = String(schema.description ?? "");
90
91
  let catalog = catalogFor(description);
91
92
  // Audience arrays inherit the catalog context from the array property.
92
93
  if (!catalog && typeof path.at(-1) === "number")
93
- catalog = catalogFor(String(schemaAtPath(document, path.slice(0, -1).map(String)).description ?? ""));
94
+ catalog = catalogFor(String(schemaAtPath(presentation, path.slice(0, -1).map(String)).description ?? ""));
94
95
  const options = new Map();
95
96
  const records = new Map();
96
97
  let unloadedSource = false;
97
98
  if (catalog && typeof node.value === "string") {
98
- const entries = document.catalogs;
99
+ const entries = presentation.catalogs;
99
100
  const local = entries?.[catalog];
100
101
  unloadedSource = Boolean(local?.source);
101
102
  for (const option of getCatalogOptions(catalog)) {
@@ -148,18 +149,18 @@ export function replaceFieldOption(context, value) {
148
149
  if (!context.options.some(option => option.value === value))
149
150
  throw new Error("This choice is no longer available.");
150
151
  let next = context.source.slice(0, context.offset) + JSON.stringify(value) + context.source.slice(context.offset + context.length);
151
- const document = JSON.parse(next);
152
- if (context.path.reduce((current, key) => current?.[key], document) !== value)
152
+ const presentation = JSON.parse(next);
153
+ if (context.path.reduce((current, key) => current?.[key], presentation) !== value)
153
154
  throw new Error("This field has a duplicate key. Resolve it in the JSON before choosing an option.");
154
155
  const types = context.options.find(option => option.value === value).placeholderTypes;
155
156
  if (value !== context.value && types && context.catalog === 'layouts' && context.path.length === 3
156
- && context.path[0] === 'slides' && context.path[2] === 'layout' && validatePresentation(document).valid) {
157
- const populated = populateLayoutPlaceholders(document, context.path[1], types);
158
- next = updateSource(next, document, populated);
157
+ && context.path[0] === 'slides' && context.path[2] === 'layout' && checkFormat(presentation).valid) {
158
+ const populated = populateLayoutPlaceholders(presentation, context.path[1], types);
159
+ next = updateSource(next, presentation, populated);
159
160
  }
160
- const result = validatePresentation(JSON.parse(next));
161
- if (!result.valid && validatePresentation(JSON.parse(context.source)).valid)
162
- throw new Error(result.errors[0]?.message ?? "This option is not valid here.");
161
+ const result = checkFormat(JSON.parse(next));
162
+ if (!result.valid && checkFormat(JSON.parse(context.source)).valid)
163
+ throw new Error(firstErrorMessage(result, "This option is not valid here."));
163
164
  return next;
164
165
  }
165
166
 
@@ -22,11 +22,11 @@ export function createLayoutHandles(root, options) {
22
22
  const edit=active;
23
23
  edit.fraction=fraction;
24
24
  try{
25
- const prepared=prepareTrackResize(editor.document,active.flow,active.boundary,fraction);
26
- options.render(prepared.document);
25
+ const prepared=prepareTrackResize(editor.presentation,active.flow,active.boundary,fraction);
26
+ options.render(prepared.presentation);
27
27
  if(active!==edit)return false;
28
28
  edit.prepared=prepared;edit.valid=true;
29
- options.onDraft?.({document:prepared.document,path:active.flow.path+'.composition',value:prepared.composition});
29
+ options.onDraft?.({presentation:prepared.presentation,path:active.flow.path+'.composition',value:prepared.composition});
30
30
  if(active!==edit)return false;
31
31
  options.clearError?.();return true;
32
32
  }catch(error){if(active===edit)edit.valid=false;options.onError(error);return false;}
@@ -41,7 +41,7 @@ export function createLayoutHandles(root, options) {
41
41
  active=null;release(edit);committing=true;
42
42
  try{
43
43
  // Rebase unrelated document edits, retaining the guarded container.
44
- const prepared=prepareTrackResize(editor.document,edit.flow,edit.boundary,edit.fraction);
44
+ const prepared=prepareTrackResize(editor.presentation,edit.flow,edit.boundary,edit.fraction);
45
45
  if(edit.moved)editor.applyPatch(prepared.patches,{source:'canvas-layout',rejectInvalid:true});
46
46
  else renderCanonical();
47
47
  options.onCommit?.({path:edit.flow.path+'.composition',editor});return true;
@@ -56,7 +56,7 @@ export function createLayoutHandles(root, options) {
56
56
  if(event)layer.setPointerCapture(event.pointerId);
57
57
  return true;
58
58
  }
59
- function update(document,geometry) {
59
+ function update(presentation,geometry) {
60
60
  lastGeometry=geometry;
61
61
  layer.replaceChildren();
62
62
  if(!enabled||options.isTextEditing())return;
@@ -2,7 +2,7 @@ import {listBlockContainers,prepareBlockInsert} from './blocks.js';
2
2
 
3
3
  const payloadFields=['text','items','bullets','image','video','chart','table','code','metric','quote','timeline'];
4
4
  const headingFields=new Set(['title','subtitle','tag']);
5
- const fields={text:'text',list:'items',picture:'image',media:'video',diagram:'image',chart:'chart',table:'table',code:'code',metric:'metric',quote:'quote',timeline:'timeline'};
5
+ const fields={text:'text',list:'items',image:'image',video:'video',chart:'chart',table:'table',code:'code',metric:'metric',quote:'quote',timeline:'timeline'};
6
6
  const region=key=>/^(?:(?:top|middle|bottom)(?:\+(?:top|middle|bottom))*(?::(?:left|center|right)(?:\+(?:left|center|right))*)?|(?:left|center|right)(?:\+(?:left|center|right))*)$/.test(key);
7
7
 
8
8
  function emptyPayload(field){
@@ -16,8 +16,8 @@ function emptyPayload(field){
16
16
  }
17
17
 
18
18
  /** Supply authored slots without replacing content, metadata or region placement. */
19
- export function populateLayoutPlaceholders(document,slideIndex,types){
20
- let next=structuredClone(document),slide=next.slides[slideIndex];
19
+ export function populateLayoutPlaceholders(presentation,slideIndex,types){
20
+ let next=structuredClone(presentation),slide=next.slides[slideIndex];
21
21
  const counts=new Map();
22
22
  function count(host){
23
23
  if(!host||typeof host!=='object')return;
@@ -48,7 +48,7 @@ export function populateLayoutPlaceholders(document,slideIndex,types){
48
48
  const path=regions.length?`/slides/${slideIndex}/${regions.at(-1)}`:`/slides/${slideIndex}`;
49
49
  const containers=listBlockContainers(next,{slideIndex,includeImplicit:true});
50
50
  if(!containers.some(container=>container.path===path))throw new Error('This slide has no editable content container for the new placeholder.');
51
- next=prepareBlockInsert(next,path,{[field]:value}).document;
51
+ next=prepareBlockInsert(next,path,{[field]:value}).presentation;
52
52
  slide=next.slides[slideIndex];
53
53
  }
54
54
  }
package/dist/layout.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type {ComposedFlow, Composition} from '@openpresentation/opf/composition';
2
2
  import type {JsonPatchOperation} from './index.js';
3
3
  /** Resize adjacent tracks. Fractions clamp to 5–95%; automatic flows become explicit grids. */
4
- export declare function prepareTrackResize(document:unknown,flow:ComposedFlow,boundary:number,fraction:number):{
5
- document:unknown;patches:JsonPatchOperation[];composition:Composition;fraction:number;
4
+ export declare function prepareTrackResize(presentation:unknown,flow:ComposedFlow,boundary:number,fraction:number):{
5
+ presentation:unknown;patches:JsonPatchOperation[];composition:Composition;fraction:number;
6
6
  };
7
7
 
8
8
  export {prepareBlockMove,listBlockContainers,prepareBlockInsert,prepareBlockDuplicate,prepareBlockRemove,prepareBlockReplace,createContentBlock} from './blocks.js';
package/dist/layout.js CHANGED
@@ -1,7 +1,8 @@
1
- import {getValueAtPath, createValuePatch, applyJsonPatch, validateOpfDocument, opfPathToJsonPointer} from './index.js';
1
+ import { getValueAtPath, createValuePatch, applyJsonPatch, opfPathToJsonPointer } from './index.js';
2
+ import { firstErrorMessage, checkFormat } from "./checks.js";
2
3
 
3
4
  /** Prepare a validated track resize; the test operation guards its container revision. */
4
- export function prepareTrackResize(document, flow, boundary, fraction) {
5
+ export function prepareTrackResize(presentation, flow, boundary, fraction) {
5
6
  const vertical = flow?.composition?.mode === 'column';
6
7
  const tracks = vertical ? flow?.rows : flow?.columns;
7
8
  if (!Array.isArray(tracks) || tracks.length < 2 || tracks.length > 12)
@@ -12,7 +13,7 @@ export function prepareTrackResize(document, flow, boundary, fraction) {
12
13
  throw new RangeError('Track boundary is out of range.');
13
14
  if (!Number.isFinite(fraction) || fraction < 0 || fraction > 1)
14
15
  throw new RangeError('Track fraction must be between zero and one.');
15
- const container = getValueAtPath(document,flow.path);
16
+ const container = getValueAtPath(presentation,flow.path);
16
17
  if (!container || typeof container !== 'object') throw new Error('The layout container no longer exists.');
17
18
  const effective = flow.composition.weights ?? [];
18
19
  const weights = Array.from({length:Math.max(tracks.length,effective.length)},(_,index)=>effective[index]??1);
@@ -25,10 +26,10 @@ export function prepareTrackResize(document, flow, boundary, fraction) {
25
26
  const composition={...container.composition,mode:flow.composition.mode??'auto',weights};
26
27
  // A drag expresses an explicit arrangement. Preserve the automatic engine's chosen columns.
27
28
  if(composition.mode==='auto'){composition.mode='grid';composition.columns=flow.columns.length;}
28
- const patches=[{op:'test',path:opfPathToJsonPointer(flow.path),value:structuredClone(container)},...createValuePatch(document,[...flow.path.split('.'),'composition'],composition)];
29
- const next=applyJsonPatch(document,patches),validation=validateOpfDocument(next);
30
- if(!validation.valid)throw new Error(validation.errors[0]?.message??'Track resize is not valid OPF.');
31
- return {document:next,patches,composition,fraction:share};
29
+ const patches=[{op:'test',path:opfPathToJsonPointer(flow.path),value:structuredClone(container)},...createValuePatch(presentation,[...flow.path.split('.'),'composition'],composition)];
30
+ const next=applyJsonPatch(presentation,patches),validation=checkFormat(next);
31
+ if(!validation.valid)throw new Error(firstErrorMessage(validation, 'Track resize is not valid OPF.'));
32
+ return {presentation:next,patches,composition,fraction:share};
32
33
  }
33
34
 
34
35
  export {prepareBlockMove,listBlockContainers,prepareBlockInsert,prepareBlockDuplicate,prepareBlockRemove,prepareBlockReplace,createContentBlock} from './blocks.js';
@@ -8,7 +8,6 @@ import {
8
8
  NUMBERING_STYLE_OPTIONS,
9
9
  NUMBERING_SUFFIX_OPTIONS,
10
10
  findNumberableLists,
11
- numberingAvailable,
12
11
  numberingState,
13
12
  numberingValue,
14
13
  setEntryStart,
@@ -36,7 +35,6 @@ function h(doc, tag, attributes = {}, ...children) {
36
35
  * `onStatus(message, {error})`.
37
36
  */
38
37
  export function createNumberingPanel(container, options) {
39
- if (!numberingAvailable()) throw new Error("The numbering control needs a core release that ships numbering.");
40
38
  const { editor, getTarget, getSlideIndex, onStatus } = options;
41
39
  const doc = container.ownerDocument;
42
40
  const id = `opf-numbering-${++panelCounter}`;
@@ -58,9 +56,9 @@ export function createNumberingPanel(container, options) {
58
56
 
59
57
  const currentPath = () => {
60
58
  const slide = getSlideIndex?.() ?? 0;
61
- const lists = findNumberableLists(editor.document, slide);
59
+ const lists = findNumberableLists(editor.presentation, slide);
62
60
  const wanted = explicit ?? getTarget?.();
63
- if (wanted && numberingState(editor.document, wanted)) return { path: wanted, lists };
61
+ if (wanted && numberingState(editor.presentation, wanted)) return { path: wanted, lists };
64
62
  return { path: lists[0]?.path, lists };
65
63
  };
66
64
 
@@ -100,7 +98,7 @@ export function createNumberingPanel(container, options) {
100
98
  function draw(force = false) {
101
99
  if (destroyed) return;
102
100
  const { path, lists } = currentPath();
103
- const state = path ? numberingState(editor.document, path) : undefined;
101
+ const state = path ? numberingState(editor.presentation, path) : undefined;
104
102
  const signature = JSON.stringify([path, state, lists.map((entry) => [entry.path, entry.numbered])]);
105
103
  if (!force && signature === drawn) return;
106
104
  drawn = signature;
@@ -12,8 +12,6 @@ export declare const NUMBERING_SUFFIX_OPTIONS: readonly { value: NumberingSuffix
12
12
  export declare const MAX_NUMBERING_LEVELS: 9;
13
13
  export declare const MAX_NUMBERING_START: 32767;
14
14
 
15
- /** True when the core this editor runs on composes numbered lists. */
16
- export declare function numberingAvailable(): boolean;
17
15
 
18
16
  export interface ListPayloadLocation {
19
17
  /** The payload that owns the list ("" for a document-level path is not possible; a slide, region or block). */
@@ -26,11 +24,11 @@ export interface ListPayloadLocation {
26
24
  entry?: number;
27
25
  }
28
26
  /** Where a list lives for a path that points at it or into it (`slides.0.items`, `slides.0.items.2`, `slides.0.left.items.1.text`). */
29
- export declare function listPayloadAt(document: unknown, path: string): ListPayloadLocation | undefined;
27
+ export declare function listPayloadAt(presentation: unknown, path: string): ListPayloadLocation | undefined;
30
28
 
31
29
  export interface NumberableList { path: string; payloadPath: string; field: "items" | "bullets"; label: string; count: number; numbered: boolean }
32
30
  /** Every list of a slide: path, label, entry count and whether it is numbered. */
33
- export declare function findNumberableLists(document: unknown, slideIndex: number): NumberableList[];
31
+ export declare function findNumberableLists(presentation: unknown, slideIndex: number): NumberableList[];
34
32
 
35
33
  /** The shortest `numbering` value for per-level settings (a style name, an object of the non-default fields, or an array). */
36
34
  export declare function numberingValue(levels: readonly Partial<NumberingLevel>[]): NumberingValue;
@@ -54,7 +52,7 @@ export interface NumberingState {
54
52
  /** The entry the path selects, with its own `start` and the marker it draws. */
55
53
  entry?: { index: number; start?: number; marker?: string };
56
54
  }
57
- export declare function numberingState(document: unknown, path: string): NumberingState | undefined;
55
+ export declare function numberingState(presentation: unknown, path: string): NumberingState | undefined;
58
56
 
59
57
  /** Number a list, change its numbering, or (with `undefined` or `null`) turn it off, as one undoable validated edit. Turning it off also removes the entry `start` values. */
60
58
  export declare function setNumbering(editor: EditorSession, path: string, value: NumberingValue | undefined | null, meta?: Record<string, unknown>): unknown;
package/dist/numbering.js CHANGED
@@ -1,9 +1,8 @@
1
1
  // List numbering (RR-33): the headless model behind the numbering control. A list payload (`items` or `bullets`) can
2
2
  // carry `numbering`: a style name, a { style, start, suffix } object, or an array with one entry per list level. Every
3
3
  // write goes through the session as a validated JSON Patch edit, so each change is one undoable edit. Importing this
4
- // module needs no DOM. Core is read from the namespace so an older core still loads this module; the writes then
5
- // throw "numbering-unavailable".
6
- import * as core from "@openpresentation/opf";
4
+ // module needs no DOM.
5
+ import { listNumbers } from "@openpresentation/opf/composition";
7
6
  import { OPFEditorError, getValueAtPath, opfPathToJsonPointer, splitOpfPath } from "./index.js";
8
7
 
9
8
  /** The number styles and how each draws, in the order a picker lists them. */
@@ -41,21 +40,12 @@ function checkEditor(editor) {
41
40
  }
42
41
  }
43
42
 
44
- /** True when the core this editor runs on composes numbered lists (the core that ships `numbering`). */
45
- export function numberingAvailable() {
46
- return typeof core.listNumbers === "function" && typeof core.formatListNumber === "function";
47
- }
48
-
49
- function requireCore() {
50
- if (!numberingAvailable()) throw fail("numbering-unavailable", "List numbering needs a core release that ships numbering (@openpresentation/opf after 0.11.4).");
51
- }
52
-
53
43
  /**
54
44
  * Where a list lives for a path that points at it or into it: `slides.0.items`, `slides.0.items.2` or
55
45
  * `slides.0.left.items.1.text` all resolve to the payload that owns the list. Returns undefined when the path is not
56
46
  * inside a list.
57
47
  */
58
- export function listPayloadAt(document, path) {
48
+ export function listPayloadAt(presentation, path) {
59
49
  let segments;
60
50
  try {
61
51
  segments = splitOpfPath(path);
@@ -67,7 +57,7 @@ export function listPayloadAt(document, path) {
67
57
  const next = segments[index + 1];
68
58
  if (next !== undefined && !/^\d+$/.test(next)) continue;
69
59
  const payloadPath = segments.slice(0, index);
70
- const payload = payloadPath.length ? getValueAtPath(document, payloadPath) : document;
60
+ const payload = payloadPath.length ? getValueAtPath(presentation, payloadPath) : presentation;
71
61
  if (!isObject(payload) || !Array.isArray(payload[segments[index]])) continue;
72
62
  const entry = next === undefined ? undefined : Number(next);
73
63
  return { payloadPath: payloadPath.join("."), field: segments[index], path: [...payloadPath, segments[index]].join("."), payload, entry };
@@ -78,8 +68,8 @@ export function listPayloadAt(document, path) {
78
68
  const isRegionKey = (key) => /^(?:(?:top|middle|bottom)(?:\+(?:top|middle|bottom))*(?::(?:left|center|right)(?:\+(?:left|center|right))*)?|(?:left|center|right)(?:\+(?:left|center|right))*)$/.test(key);
79
69
 
80
70
  /** Every list (`items` or `bullets`) of a slide: its path, label, entry count and whether it is numbered. */
81
- export function findNumberableLists(document, slideIndex) {
82
- const slide = document?.slides?.[slideIndex];
71
+ export function findNumberableLists(presentation, slideIndex) {
72
+ const slide = presentation?.slides?.[slideIndex];
83
73
  if (!isObject(slide)) return [];
84
74
  const found = [];
85
75
  const visit = (payload, base, label, depth = 0) => {
@@ -137,8 +127,8 @@ const maxLevelOf = (items) => items.reduce((max, item) => Math.max(max, isObject
137
127
  * numbering applies to every level), the number of levels the list uses, the markers it draws and which entry (if any)
138
128
  * the path selects with that entry's own `start`.
139
129
  */
140
- export function numberingState(document, path) {
141
- const target = listPayloadAt(document, path);
130
+ export function numberingState(presentation, path) {
131
+ const target = listPayloadAt(presentation, path);
142
132
  if (!target) return undefined;
143
133
  const { payload, field, entry } = target;
144
134
  const items = payload[field];
@@ -147,9 +137,9 @@ export function numberingState(document, path) {
147
137
  const levels = numbered ? resolveLevels(numbering) : [{ ...DEFAULT_LEVEL }];
148
138
  const depth = Math.min(MAX_NUMBERING_LEVELS, maxLevelOf(items) + 1);
149
139
  let markers = [];
150
- if (numbered && numberingAvailable()) {
140
+ if (numbered) {
151
141
  try {
152
- markers = core.listNumbers(items, numbering).map((number) => ({ index: number.index, level: number.level, text: number.text, value: number.value, adapted: number.adapted }));
142
+ markers = listNumbers(items, numbering).map((number) => ({ index: number.index, level: number.level, text: number.text, value: number.value, adapted: number.adapted }));
153
143
  } catch {
154
144
  markers = [];
155
145
  }
@@ -180,14 +170,13 @@ function numberingPointer(target) {
180
170
  * the entry `start` values, which mean nothing without it. `path` addresses the list or anything inside it.
181
171
  */
182
172
  export function setNumbering(editor, path, value, meta = {}) {
183
- requireCore();
184
173
  checkEditor(editor);
185
- const target = listPayloadAt(editor.document, path);
174
+ const target = listPayloadAt(editor.presentation, path);
186
175
  if (!target) throw fail("not-a-list", "Select a list (items or bullets) to number.", { path });
187
176
  const patches = [];
188
177
  const present = Object.hasOwn(target.payload, "numbering");
189
178
  if (value === undefined || value === null) {
190
- if (!present) return { document: editor.document, patches: [], inversePatches: [], validation: editor.validation };
179
+ if (!present) return { presentation: editor.presentation, patches: [], inversePatches: [], validation: editor.validation };
191
180
  patches.push({ op: "remove", path: numberingPointer(target) });
192
181
  target.payload[target.field].forEach((item, index) => {
193
182
  if (isObject(item) && item.start !== undefined) patches.push({ op: "remove", path: opfPathToJsonPointer(`${target.path}.${index}.start`) });
@@ -203,9 +192,8 @@ export function setNumbering(editor, path, value, meta = {}) {
203
192
  * removes the restart. A plain entry (a string or runs) becomes the object form to carry it. One undoable validated edit.
204
193
  */
205
194
  export function setEntryStart(editor, itemPath, start, meta = {}) {
206
- requireCore();
207
195
  checkEditor(editor);
208
- const target = listPayloadAt(editor.document, itemPath);
196
+ const target = listPayloadAt(editor.presentation, itemPath);
209
197
  if (!target || target.entry === undefined) throw fail("not-an-entry", "Select a list entry to restart its numbering.", { path: itemPath });
210
198
  if (target.payload.numbering === undefined) throw fail("not-numbered", "Number the list first; an entry start has no effect on a bulleted list.", { path: itemPath });
211
199
  const item = target.payload[target.field][target.entry];
@@ -218,6 +206,6 @@ export function setEntryStart(editor, itemPath, start, meta = {}) {
218
206
  if (isObject(item)) patches.push({ op: item.start === undefined ? "add" : "replace", path: `${pointer}/start`, value: start });
219
207
  else patches.push({ op: "replace", path: pointer, value: { text: structuredClone(item), start } });
220
208
  }
221
- if (!patches.length) return { document: editor.document, patches: [], inversePatches: [], validation: editor.validation };
209
+ if (!patches.length) return { presentation: editor.presentation, patches: [], inversePatches: [], validation: editor.validation };
222
210
  return editor.applyPatch(patches, { source: "list-numbering-start", rejectInvalid: true, ...meta });
223
211
  }
@@ -92,9 +92,9 @@ export function createOutlineView(container, options) {
92
92
  const firstDraw = !lastSnapshot;
93
93
  if (snapshot === lastSnapshot && !pendingFocus) return;
94
94
  lastSnapshot = snapshot;
95
- const document = editor.document;
96
- rows = readOutline(document).rows;
97
- const sections = hasSections(document);
95
+ const presentation = editor.presentation;
96
+ rows = readOutline(presentation).rows;
97
+ const sections = hasSections(presentation);
98
98
  const entries = [];
99
99
  let lastSection;
100
100
  for (const row of rows) {
@@ -143,18 +143,18 @@ export function createOutlineView(container, options) {
143
143
  function activeRow() { return activeKey ? rowByKey(activeKey) : undefined; }
144
144
  function updateTools() {
145
145
  const row = activeRow();
146
- const document = editor.document;
146
+ const presentation = editor.presentation;
147
147
  const can = { promote: false, demote: false, up: false, down: false, add: Boolean(row) };
148
148
  if (row) {
149
149
  try {
150
150
  if (row.kind === "item") {
151
- can.demote = prepareShiftOutlineItem(document, row, 1).changed;
152
- can.promote = row.level > 1 ? prepareShiftOutlineItem(document, row, -1).changed : row.editable;
153
- can.up = prepareMoveOutlineItem(document, row, -1).changed;
154
- can.down = prepareMoveOutlineItem(document, row, 1).changed;
151
+ can.demote = prepareShiftOutlineItem(presentation, row, 1).changed;
152
+ can.promote = row.level > 1 ? prepareShiftOutlineItem(presentation, row, -1).changed : row.editable;
153
+ can.up = prepareMoveOutlineItem(presentation, row, -1).changed;
154
+ can.down = prepareMoveOutlineItem(presentation, row, 1).changed;
155
155
  } else if (row.kind === "slide") {
156
156
  can.up = row.slideIndex > 0;
157
- can.down = row.slideIndex < document.slides.length - 1;
157
+ can.down = row.slideIndex < presentation.slides.length - 1;
158
158
  can.demote = row.slideIndex > 0;
159
159
  }
160
160
  } catch { /* a row that vanished mid-edit leaves the tools off */ }
@@ -167,7 +167,7 @@ export function createOutlineView(container, options) {
167
167
  if (!row?.editable) return false;
168
168
  if (input.value === row.text) return false;
169
169
  try {
170
- const prepared = prepareSetOutlineText(editor.document, row, input.value);
170
+ const prepared = prepareSetOutlineText(editor.presentation, row, input.value);
171
171
  if (!prepared.changed) return false;
172
172
  applyOutlineChange(editor, prepared);
173
173
  render();
@@ -203,25 +203,25 @@ export function createOutlineView(container, options) {
203
203
  const key = activeKey;
204
204
  const row = settle(input ?? inputFor(key), key);
205
205
  if (!row) return;
206
- const document = editor.document;
206
+ const presentation = editor.presentation;
207
207
  if (action === "add") {
208
- if (row.kind === "item") apply(() => prepareInsertOutlineItem(document, row), "Line added");
209
- else if (row.kind === "slide") apply(() => prepareAddOutlineBullet(document, row.slideIndex), "Bullet added under the slide title");
208
+ if (row.kind === "item") apply(() => prepareInsertOutlineItem(presentation, row), "Line added");
209
+ else if (row.kind === "slide") apply(() => prepareAddOutlineBullet(presentation, row.slideIndex), "Bullet added under the slide title");
210
210
  else announce("Add a line from a title or a bullet");
211
211
  } else if (action === "demote") {
212
- if (row.kind === "item") apply(() => prepareShiftOutlineItem(document, row, 1), () => "Demoted");
213
- else if (row.kind === "slide") apply(() => prepareOutlineDemoteSlide(document, row.slideIndex), "Slide joined the slide before it as a bullet");
212
+ if (row.kind === "item") apply(() => prepareShiftOutlineItem(presentation, row, 1), () => "Demoted");
213
+ else if (row.kind === "slide") apply(() => prepareOutlineDemoteSlide(presentation, row.slideIndex), "Slide joined the slide before it as a bullet");
214
214
  else announce("Only titles and bullets change level");
215
215
  } else if (action === "promote") {
216
216
  if (row.kind === "item") {
217
- if (row.level > 1) apply(() => prepareShiftOutlineItem(document, row, -1), "Promoted");
218
- else apply(() => prepareOutlinePromote(document, row), (prepared) => `Promoted to slide ${prepared.newSlideIndex + 1}`);
217
+ if (row.level > 1) apply(() => prepareShiftOutlineItem(presentation, row, -1), "Promoted");
218
+ else apply(() => prepareOutlinePromote(presentation, row), (prepared) => `Promoted to slide ${prepared.newSlideIndex + 1}`);
219
219
  } else if (row.kind === "slide") announce("Slide titles are already the top level");
220
220
  else announce("Only titles and bullets change level");
221
221
  } else if (action === "up" || action === "down") {
222
222
  const delta = action === "up" ? -1 : 1;
223
- if (row.kind === "item") apply(() => prepareMoveOutlineItem(document, row, delta), `Moved ${action}`);
224
- else if (row.kind === "slide") apply(() => prepareMoveOutlineSlide(document, row.slideIndex, delta), (prepared) => `Slide moved to position ${prepared.selection[0] + 1} of ${document.slides.length}`);
223
+ if (row.kind === "item") apply(() => prepareMoveOutlineItem(presentation, row, delta), `Moved ${action}`);
224
+ else if (row.kind === "slide") apply(() => prepareMoveOutlineSlide(presentation, row.slideIndex, delta), (prepared) => `Slide moved to position ${prepared.selection[0] + 1} of ${presentation.slides.length}`);
225
225
  else announce("Move bullets or slides");
226
226
  }
227
227
  }
@@ -244,18 +244,18 @@ export function createOutlineView(container, options) {
244
244
  event.preventDefault();
245
245
  const settled = settle(input, key);
246
246
  if (!settled) return;
247
- const document = editor.document;
248
- if (settled.kind === "item") apply(() => prepareInsertOutlineItem(document, settled), "Line added");
249
- else if (settled.kind === "slide") apply(() => prepareInsertOutlineSlide(document, settled.slideIndex), (prepared) => `Slide ${prepared.index + 1} added`);
247
+ const presentation = editor.presentation;
248
+ if (settled.kind === "item") apply(() => prepareInsertOutlineItem(presentation, settled), "Line added");
249
+ else if (settled.kind === "slide") apply(() => prepareInsertOutlineSlide(presentation, settled.slideIndex), (prepared) => `Slide ${prepared.index + 1} added`);
250
250
  else focusAt(index + 1);
251
251
  return;
252
252
  }
253
253
  if (event.key === "Backspace" && input.value === "" && input.selectionStart === 0 && !input.readOnly) {
254
- if (row.kind === "item") { event.preventDefault(); apply(() => prepareRemoveOutlineItem(editor.document, row), "Line removed"); }
254
+ if (row.kind === "item") { event.preventDefault(); apply(() => prepareRemoveOutlineItem(editor.presentation, row), "Line removed"); }
255
255
  else if (row.kind === "slide" && !rows.some((other) => other.slideIndex === row.slideIndex && other.kind !== "slide")) {
256
256
  event.preventDefault();
257
257
  apply(() => {
258
- const prepared = prepareRemoveSlides(editor.document, [row.slideIndex]);
258
+ const prepared = prepareRemoveSlides(editor.presentation, [row.slideIndex]);
259
259
  return { ...prepared, focus: `slide:slides.${Math.max(0, row.slideIndex - 1)}` };
260
260
  }, `Slide ${row.slideIndex + 1} removed`);
261
261
  }
package/dist/outline.d.ts CHANGED
@@ -35,22 +35,22 @@ export interface PreparedOutlineChange extends Omit<PreparedSlideChange, "select
35
35
  reason?: string;
36
36
  newSlideIndex?: number;
37
37
  }
38
- export type OutlineChange = Omit<EditorChange, "document" | "patches"> & Omit<PreparedOutlineChange, "document" | "patches"> & { document: unknown; patches: JsonPatchOperation[] };
38
+ export type OutlineChange = Omit<EditorChange, "presentation" | "patches"> & Omit<PreparedOutlineChange, "presentation" | "patches"> & { presentation: unknown; patches: JsonPatchOperation[] };
39
39
 
40
- export declare function readOutline(document: unknown): Outline;
41
- export declare function prepareSetOutlineText(document: unknown, row: OutlineRow, text: string): PreparedOutlineChange;
40
+ export declare function readOutline(presentation: unknown): Outline;
41
+ export declare function prepareSetOutlineText(presentation: unknown, row: OutlineRow, text: string): PreparedOutlineChange;
42
42
  export declare function setOutlineText(editor: EditorSession, row: OutlineRow, text: string, meta?: Record<string, unknown>): OutlineChange;
43
43
  /** Nest (`delta` 1) or un-nest (-1) a list item with the items under it. A top-level item reports `changed: false` and a `reason`. */
44
- export declare function prepareShiftOutlineItem(document: unknown, row: OutlineRow, delta: 1 | -1): PreparedOutlineChange;
45
- export declare function prepareMoveOutlineItem(document: unknown, row: OutlineRow, delta: 1 | -1): PreparedOutlineChange;
46
- export declare function prepareInsertOutlineItem(document: unknown, row: OutlineRow, text?: string): PreparedOutlineChange;
47
- export declare function prepareAddOutlineBullet(document: unknown, slideIndex: number, text?: string): PreparedOutlineChange;
48
- export declare function prepareRemoveOutlineItem(document: unknown, row: OutlineRow): PreparedOutlineChange;
44
+ export declare function prepareShiftOutlineItem(presentation: unknown, row: OutlineRow, delta: 1 | -1): PreparedOutlineChange;
45
+ export declare function prepareMoveOutlineItem(presentation: unknown, row: OutlineRow, delta: 1 | -1): PreparedOutlineChange;
46
+ export declare function prepareInsertOutlineItem(presentation: unknown, row: OutlineRow, text?: string): PreparedOutlineChange;
47
+ export declare function prepareAddOutlineBullet(presentation: unknown, slideIndex: number, text?: string): PreparedOutlineChange;
48
+ export declare function prepareRemoveOutlineItem(presentation: unknown, row: OutlineRow): PreparedOutlineChange;
49
49
  /** A top-level list item becomes the title of a new slide that takes the items after it. */
50
- export declare function prepareOutlinePromote(document: unknown, row: OutlineRow): PreparedOutlineChange;
50
+ export declare function prepareOutlinePromote(presentation: unknown, row: OutlineRow): PreparedOutlineChange;
51
51
  /** A plain slide becomes a bullet (with its text nested) of the slide before it. Throws `outline-row-not-supported` rather than dropping content. */
52
- export declare function prepareOutlineDemoteSlide(document: unknown, slideIndex: number): PreparedOutlineChange;
53
- export declare function prepareMoveOutlineSlide(document: unknown, slideIndex: number, delta: number): PreparedOutlineChange;
54
- export declare function prepareInsertOutlineSlide(document: unknown, slideIndex: number): PreparedOutlineChange;
52
+ export declare function prepareOutlineDemoteSlide(presentation: unknown, slideIndex: number): PreparedOutlineChange;
53
+ export declare function prepareMoveOutlineSlide(presentation: unknown, slideIndex: number, delta: number): PreparedOutlineChange;
54
+ export declare function prepareInsertOutlineSlide(presentation: unknown, slideIndex: number): PreparedOutlineChange;
55
55
  /** Apply any prepared outline change as one undoable step. */
56
56
  export declare function applyOutlineChange(editor: EditorSession, prepared: PreparedOutlineChange | PreparedSlideChange, meta?: Record<string, unknown>): OutlineChange;