@openpresentation/opf-editor 0.12.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +122 -80
  2. package/dist/annotations.d.ts +16 -16
  3. package/dist/annotations.js +71 -79
  4. package/dist/assets.d.ts +7 -7
  5. package/dist/assets.js +16 -16
  6. package/dist/background-options.d.ts +12 -5
  7. package/dist/background-options.js +44 -19
  8. package/dist/block-controls.js +7 -7
  9. package/dist/block-convert.d.ts +7 -7
  10. package/dist/block-convert.js +22 -21
  11. package/dist/blocks.d.ts +8 -8
  12. package/dist/blocks.js +41 -40
  13. package/dist/canvas-fields.js +35 -12
  14. package/dist/canvas.d.ts +30 -31
  15. package/dist/canvas.js +64 -68
  16. package/dist/catalogs.d.ts +48 -0
  17. package/dist/catalogs.js +217 -0
  18. package/dist/chart-data.d.ts +10 -10
  19. package/dist/chart-data.js +21 -21
  20. package/dist/chart-options-panel.d.ts +2 -2
  21. package/dist/chart-options-panel.js +82 -8
  22. package/dist/chart-options.d.ts +35 -9
  23. package/dist/chart-options.js +133 -40
  24. package/dist/checks.js +23 -0
  25. package/dist/color-authoring.js +1 -1
  26. package/dist/content-actions.d.ts +17 -17
  27. package/dist/content-actions.js +74 -73
  28. package/dist/content-controls.js +21 -21
  29. package/dist/data-grid.js +7 -8
  30. package/dist/data.d.ts +4 -4
  31. package/dist/data.js +5 -5
  32. package/dist/dataset-refs.d.ts +2 -2
  33. package/dist/dataset-refs.js +5 -5
  34. package/dist/design-controls.d.ts +12 -5
  35. package/dist/design-controls.js +312 -111
  36. package/dist/design-options.d.ts +20 -18
  37. package/dist/design-options.js +91 -99
  38. package/dist/edit-helpers.js +8 -7
  39. package/dist/exact-source.js +5 -5
  40. package/dist/export.d.ts +7 -7
  41. package/dist/export.js +48 -17
  42. package/dist/find-panel.js +5 -5
  43. package/dist/find-replace.d.ts +3 -3
  44. package/dist/find-replace.js +23 -20
  45. package/dist/font-gate.js +49 -58
  46. package/dist/galleries.js +50 -101
  47. package/dist/grid-model.d.ts +25 -27
  48. package/dist/grid-model.js +92 -102
  49. package/dist/image-crop.d.ts +4 -4
  50. package/dist/image-crop.js +28 -29
  51. package/dist/image-cropper.js +3 -3
  52. package/dist/image-options.d.ts +45 -0
  53. package/dist/image-options.js +195 -0
  54. package/dist/index.d.ts +48 -31
  55. package/dist/index.js +154 -126
  56. package/dist/json-editor.js +2 -2
  57. package/dist/json-menu.js +2 -2
  58. package/dist/json-options.d.ts +3 -3
  59. package/dist/json-options.js +44 -54
  60. package/dist/layout-handles.js +5 -5
  61. package/dist/layout-placeholders.js +4 -4
  62. package/dist/layout.d.ts +2 -2
  63. package/dist/layout.js +8 -7
  64. package/dist/numbering-panel.js +3 -5
  65. package/dist/numbering.d.ts +3 -5
  66. package/dist/numbering.js +14 -26
  67. package/dist/outline-view.js +24 -24
  68. package/dist/outline.d.ts +12 -12
  69. package/dist/outline.js +52 -50
  70. package/dist/persistence.d.ts +1 -1
  71. package/dist/persistence.js +19 -18
  72. package/dist/presentation-ids.js +2 -2
  73. package/dist/react.js +3 -3
  74. package/dist/review-panel.d.ts +30 -13
  75. package/dist/review-panel.js +160 -53
  76. package/dist/review.d.ts +36 -43
  77. package/dist/review.js +92 -53
  78. package/dist/rich-text-input.js +4 -3
  79. package/dist/rich-text-toolbar.js +5 -3
  80. package/dist/rich-text.js +5 -2
  81. package/dist/schema-inspector.d.ts +2 -2
  82. package/dist/schema-inspector.js +16 -11
  83. package/dist/schema.d.ts +1 -1
  84. package/dist/schema.js +26 -6
  85. package/dist/slide-manager.d.ts +2 -2
  86. package/dist/slide-manager.js +19 -19
  87. package/dist/slides.d.ts +21 -21
  88. package/dist/slides.js +104 -101
  89. package/dist/svelte.js +2 -3
  90. package/dist/switches.d.ts +37 -22
  91. package/dist/switches.js +282 -171
  92. package/dist/table-options.d.ts +8 -8
  93. package/dist/table-options.js +28 -27
  94. package/dist/table-structure.d.ts +10 -10
  95. package/dist/table-structure.js +21 -21
  96. package/dist/template-panel.d.ts +3 -3
  97. package/dist/template-panel.js +37 -13
  98. package/dist/templates.d.ts +22 -11
  99. package/dist/templates.js +48 -55
  100. package/dist/transfer.d.ts +15 -4
  101. package/dist/transfer.js +107 -161
  102. package/package.json +12 -6
  103. package/dist/font-defaults.js +0 -25
@@ -39,15 +39,15 @@ export interface TableCellState extends TableCellAddress {
39
39
  export interface PreparedTableChange {
40
40
  action: "merge" | "split" | "style" | "table-style";
41
41
  tablePath: string;
42
- document: unknown;
42
+ presentation: unknown;
43
43
  patches: JsonPatchOperation[];
44
44
  changed: boolean;
45
45
  [key: string]: unknown;
46
46
  }
47
- export interface TableChange extends Omit<EditorChange, "document" | "patches"> {
47
+ export interface TableChange extends Omit<EditorChange, "presentation" | "patches"> {
48
48
  action: PreparedTableChange["action"];
49
49
  tablePath: string;
50
- document: unknown;
50
+ presentation: unknown;
51
51
  patches: JsonPatchOperation[];
52
52
  changed: boolean;
53
53
  [key: string]: unknown;
@@ -60,14 +60,14 @@ export declare function tableMerges(table: unknown): TableMerge[];
60
60
  /** The state of one cell, including merge anchor/covered status. */
61
61
  export declare function describeTableCell(table: unknown, cell: TableCellAddress): TableCellState;
62
62
  /** Merge `span.colSpan` x `span.rowSpan` cells from `cell`. `join` keeps the text of covered cells by joining it into the anchor; without it, covered text refuses with `merge-would-lose-content`. */
63
- export declare function prepareTableMerge(document: unknown, tablePath: string, cell: TableCellAddress, span: { colSpan?: number; rowSpan?: number }, options?: { join?: boolean }): PreparedTableChange;
64
- export declare function prepareTableSplit(document: unknown, tablePath: string, cell: TableCellAddress): PreparedTableChange;
63
+ export declare function prepareTableMerge(presentation: unknown, tablePath: string, cell: TableCellAddress, span: { colSpan?: number; rowSpan?: number }, options?: { join?: boolean }): PreparedTableChange;
64
+ export declare function prepareTableSplit(presentation: unknown, tablePath: string, cell: TableCellAddress): PreparedTableChange;
65
65
  /** Merge style fields into the cells' styles (null removes a field, `style: null` clears). */
66
- export declare function prepareTableCellStyle(document: unknown, tablePath: string, cells: TableCellAddress | TableCellAddress[], style: Record<string, unknown> | null): PreparedTableChange;
66
+ export declare function prepareTableCellStyle(presentation: unknown, tablePath: string, cells: TableCellAddress | TableCellAddress[], style: Record<string, unknown> | null): PreparedTableChange;
67
67
  /** Apply `{ header, banding, borders }` (missing fields mean theme/false) or a named preset: sets the header fill, banded-row fill and borders of every cell and clears them elsewhere; `theme` removes a previous style. */
68
- export declare function prepareTableStyle(document: unknown, tablePath: string, preset: keyof typeof TABLE_STYLE_PRESETS | Partial<TableStyle>): PreparedTableChange;
68
+ export declare function prepareTableStyle(presentation: unknown, tablePath: string, preset: keyof typeof TABLE_STYLE_PRESETS | Partial<TableStyle>): PreparedTableChange;
69
69
  /** The current style per axis, or "custom" in every field when the fills and borders are not one this module writes. */
70
- export declare function readTableStyle(document: unknown, tablePath: string): (TableStyle | { header: "custom"; banding: "custom"; borders: "custom" }) & { preset: keyof typeof TABLE_STYLE_PRESETS | "custom" };
70
+ export declare function readTableStyle(presentation: unknown, tablePath: string): (TableStyle | { header: "custom"; banding: "custom"; borders: "custom" }) & { preset: keyof typeof TABLE_STYLE_PRESETS | "custom" };
71
71
  export declare function mergeTableCells(editor: EditorSession, tablePath: string, cell: TableCellAddress, span: { colSpan?: number; rowSpan?: number }, options?: { join?: boolean; meta?: Record<string, unknown> }): TableChange;
72
72
  export declare function splitTableCell(editor: EditorSession, tablePath: string, cell: TableCellAddress, meta?: Record<string, unknown>): TableChange;
73
73
  export declare function setTableCellStyle(editor: EditorSession, tablePath: string, cells: TableCellAddress | TableCellAddress[], style: Record<string, unknown> | null, meta?: Record<string, unknown>): TableChange;
@@ -5,8 +5,9 @@
5
5
  // Cells are addressed by `{ section: "header" | "body", row, column }`; `row` is ignored for the
6
6
  // header section. Nothing here invents text: merging cells that hold text refuses unless you pass
7
7
  // `join: true`, which keeps every word in the merged cell.
8
- import { getValueAtPath, opfPathToJsonPointer, splitOpfPath, validateOpfDocument } from "./index.js";
8
+ import { getValueAtPath, opfPathToJsonPointer, splitOpfPath } from "./index.js";
9
9
  import { checkedDocument, fail } from "./edit-helpers.js";
10
+ import { checkFormat } from "./checks.js";
10
11
 
11
12
  /**
12
13
  * Table styles. A style is `{ header, banding, borders }`. The renderers' table defaults are a header
@@ -27,7 +28,7 @@ export const TABLE_BORDER_STYLES = Object.freeze(["theme", "none", "horizontal",
27
28
  // The style fields a table style owns; text color, alignment and padding are never touched.
28
29
  const STYLE_KEYS = ["fill", "borders"];
29
30
  const HEADER_FILL = Object.freeze({ plain: "surface", accent: "accent" });
30
- const BAND_FILL = "background";
31
+ const BAND_FILL = "surfaceAlt";
31
32
  const RULE_COLOR = "textSecondary";
32
33
 
33
34
  const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -59,9 +60,9 @@ export function parseTableCellPath(path) {
59
60
  return undefined;
60
61
  }
61
62
 
62
- function tableAt(document, tablePath) {
63
+ function tableAt(presentation, tablePath) {
63
64
  const parts = splitOpfPath(tablePath);
64
- const table = getValueAtPath(document, parts);
65
+ const table = getValueAtPath(presentation, parts);
65
66
  if (isObject(table) && !Array.isArray(table.rows) && typeof table.dataset === "string")
66
67
  throw fail("table-dataset-backed", `This table shows the shared dataset '${table.dataset}', which holds no cell styles or merged cells. Use a copy of the data (in the data grid) to style it.`, { tablePath, dataset: table.dataset });
67
68
  if (!isObject(table) || !Array.isArray(table.rows)) throw fail("table-not-found", "Choose a table (a path ending in .table).", { tablePath });
@@ -163,16 +164,16 @@ function withCell(cell, patch) {
163
164
  return keys.length === 1 && keys[0] === "value" ? merged.value : merged;
164
165
  }
165
166
 
166
- function transaction(document, tablePath, change, extra = {}) {
167
- const { parts, table } = tableAt(document, tablePath);
167
+ function transaction(presentation, tablePath, change, extra = {}) {
168
+ const { parts, table } = tableAt(presentation, tablePath);
168
169
  const next = clone(table);
169
170
  const info = change(next);
170
171
  const pointer = opfPathToJsonPointer(parts);
171
172
  const changed = JSON.stringify(table) !== JSON.stringify(next);
172
173
  const patches = changed ? [{ op: "test", path: pointer, value: clone(table) }, { op: "replace", path: pointer, value: next }] : [];
173
- const before = validateOpfDocument(document);
174
- const result = changed ? checkedDocument(document, patches, before) : document;
175
- return { ...extra, ...info, tablePath: parts.join("."), document: clone(result), patches, changed };
174
+ const before = checkFormat(presentation);
175
+ const result = changed ? checkedDocument(presentation, patches, before) : presentation;
176
+ return { ...extra, ...info, tablePath: parts.join("."), presentation: clone(result), patches, changed };
176
177
  }
177
178
 
178
179
  // --- merge and split ---------------------------------------------------------------------------
@@ -200,11 +201,11 @@ function unmerge(table, cell) {
200
201
  * `merge-overlap` for a rectangle that crosses another merged cell. A header cell merges across the
201
202
  * header only (`rowSpan` 1). Re-merging an anchor first undoes its old merge.
202
203
  */
203
- export function prepareTableMerge(document, tablePath, cell, span, options = {}) {
204
+ export function prepareTableMerge(presentation, tablePath, cell, span, options = {}) {
204
205
  const colSpan = span.colSpan ?? 1;
205
206
  const rowSpan = span.rowSpan ?? 1;
206
207
  if (!Number.isInteger(colSpan) || colSpan < 1 || !Number.isInteger(rowSpan) || rowSpan < 1) throw fail("invalid-table-span", "Spans are whole numbers of 1 or more.", { span });
207
- return transaction(document, tablePath, (table) => {
208
+ return transaction(presentation, tablePath, (table) => {
208
209
  const target = { section: cell.section, row: cell.section === "header" ? 0 : cell.row, column: cell.column };
209
210
  const existing = describeTableCell(table, target);
210
211
  if (existing.covered) throw fail("merge-overlap", "This cell is covered by a merged cell. Select the merged cell itself.", { cell });
@@ -242,8 +243,8 @@ export function prepareTableMerge(document, tablePath, cell, span, options = {})
242
243
  }
243
244
 
244
245
  /** Compute the patch that splits a merged cell back into single cells (covered positions become empty text). */
245
- export function prepareTableSplit(document, tablePath, cell) {
246
- return transaction(document, tablePath, (table) => {
246
+ export function prepareTableSplit(presentation, tablePath, cell) {
247
+ return transaction(presentation, tablePath, (table) => {
247
248
  const target = { section: cell.section, row: cell.section === "header" ? 0 : cell.row, column: cell.column };
248
249
  const entry = describeTableCell(table, target);
249
250
  if (entry.covered) throw fail("table-cell-covered", "This position is covered by a merged cell. Select the merged cell to split it.", { cell });
@@ -259,11 +260,11 @@ export function prepareTableSplit(document, tablePath, cell) {
259
260
  * clears the whole style. Covered (merged) positions are skipped. A cell left with no style or spans
260
261
  * becomes a plain value again. Style is appearance only: values and spans are untouched.
261
262
  */
262
- export function prepareTableCellStyle(document, tablePath, cells, style) {
263
+ export function prepareTableCellStyle(presentation, tablePath, cells, style) {
263
264
  const list = Array.isArray(cells) ? cells : [cells];
264
265
  if (!list.length) throw fail("table-cell-not-found", "Choose at least one cell.", {});
265
266
  if (style !== null && !isObject(style)) throw fail("invalid-table-style", "A cell style is an object, or null to clear it.", { style });
266
- return transaction(document, tablePath, (table) => {
267
+ return transaction(presentation, tablePath, (table) => {
267
268
  let skipped = 0;
268
269
  for (const cell of list) {
269
270
  const target = { section: cell.section, row: cell.section === "header" ? 0 : cell.row, column: cell.column };
@@ -329,12 +330,12 @@ function resolveStyle(preset) {
329
330
  * `theme`/false) or a named preset from `TABLE_STYLE_PRESETS`. It sets the header fill, the
330
331
  * alternating body-row fill and the cell borders, and clears those fields on every other cell, so
331
332
  * `theme` removes a previous style. Text color, alignment, padding, values and merges are kept.
332
- * Fills use scheme roles (surface, accent, background), so they follow the color scheme and the
333
+ * Fills use scheme roles (surface, surfaceAlt, accent, background), so they follow the color scheme and the
333
334
  * renderers' automatic text contrast keeps text readable.
334
335
  */
335
- export function prepareTableStyle(document, tablePath, preset) {
336
+ export function prepareTableStyle(presentation, tablePath, preset) {
336
337
  const style = resolveStyle(preset);
337
- return transaction(document, tablePath, (table) => {
338
+ return transaction(presentation, tablePath, (table) => {
338
339
  styleTable(table, style);
339
340
  return {};
340
341
  }, { action: "table-style", style });
@@ -367,8 +368,8 @@ export function applyTableStyleToTable(table, preset) {
367
368
  * its fills and borders are not one of the styles this module writes (for example hand-set fills).
368
369
  * `preset` is the named preset the style equals, or "custom".
369
370
  */
370
- export function readTableStyle(document, tablePath) {
371
- return readTableStyleOfTable(tableAt(document, tablePath).table);
371
+ export function readTableStyle(presentation, tablePath) {
372
+ return readTableStyleOfTable(tableAt(presentation, tablePath).table);
372
373
  }
373
374
 
374
375
  /** {@link readTableStyle} for a table object. */
@@ -396,10 +397,10 @@ export * from "./table-structure.js";
396
397
  // --- session forms ----------------------------------------------------------------------------
397
398
 
398
399
  function commit(editor, prepared, meta = {}) {
399
- const { document, patches, ...summary } = prepared;
400
- void document;
400
+ const { presentation, patches, ...summary } = prepared;
401
+ void presentation;
401
402
  if (!editor || typeof editor.applyPatch !== "function") throw fail("invalid-editor", "Expected an editor session created by createEditorSession.");
402
- if (!prepared.changed) return { ...summary, document: editor.document, patches: [], inversePatches: [], validation: editor.validation };
403
+ if (!prepared.changed) return { ...summary, presentation: editor.presentation, patches: [], inversePatches: [], validation: editor.validation };
403
404
  const change = editor.applyPatch(patches, { ...meta, source: meta.source ?? "table-option", action: prepared.action, path: prepared.tablePath });
404
405
  return { ...change, ...summary };
405
406
  }
@@ -410,20 +411,20 @@ function checkEditor(editor) {
410
411
  export function mergeTableCells(editor, tablePath, cell, span, options = {}) {
411
412
  checkEditor(editor);
412
413
  const { meta, ...rest } = options;
413
- return commit(editor, prepareTableMerge(editor.document, tablePath, cell, span, rest), meta);
414
+ return commit(editor, prepareTableMerge(editor.presentation, tablePath, cell, span, rest), meta);
414
415
  }
415
416
  /** Split a merged cell as one undoable transaction. */
416
417
  export function splitTableCell(editor, tablePath, cell, meta = {}) {
417
418
  checkEditor(editor);
418
- return commit(editor, prepareTableSplit(editor.document, tablePath, cell), meta);
419
+ return commit(editor, prepareTableSplit(editor.presentation, tablePath, cell), meta);
419
420
  }
420
421
  /** Style cells as one undoable transaction. See {@link prepareTableCellStyle}. */
421
422
  export function setTableCellStyle(editor, tablePath, cells, style, meta = {}) {
422
423
  checkEditor(editor);
423
- return commit(editor, prepareTableCellStyle(editor.document, tablePath, cells, style), meta);
424
+ return commit(editor, prepareTableCellStyle(editor.presentation, tablePath, cells, style), meta);
424
425
  }
425
426
  /** Apply a table style as one undoable transaction. See {@link prepareTableStyle}. */
426
427
  export function setTableStyle(editor, tablePath, preset, meta = {}) {
427
428
  checkEditor(editor);
428
- return commit(editor, prepareTableStyle(editor.document, tablePath, preset), meta);
429
+ return commit(editor, prepareTableStyle(editor.presentation, tablePath, preset), meta);
429
430
  }
@@ -2,29 +2,29 @@ import type { EditorSession } from "./index.js";
2
2
  import type { GridAddress, GridCellEdit, GridChange, GridOptions, GridSortOptions, PreparedGridChange } from "./grid-model.js";
3
3
 
4
4
  /** Set cell text (or typed values) in a table. Rich cells keep their runs' formatting and styled cells their style and spans. One patch; every problem is in `error.issues`. */
5
- export declare function prepareTableCells(document: unknown, tablePath: string, edits: GridCellEdit[], options?: GridOptions): PreparedGridChange;
5
+ export declare function prepareTableCells(presentation: unknown, tablePath: string, edits: GridCellEdit[], options?: GridOptions): PreparedGridChange;
6
6
  export declare function setTableCells(editor: EditorSession, tablePath: string, edits: GridCellEdit[], options?: GridOptions): GridChange;
7
7
  /** Paste TSV/CSV text or rows at a cell, growing the table as needed. Covered (merged) positions refuse the paste. One patch. */
8
- export declare function prepareTablePaste(document: unknown, tablePath: string, anchor: GridAddress, source: string | string[][], options?: GridOptions): PreparedGridChange;
8
+ export declare function prepareTablePaste(presentation: unknown, tablePath: string, anchor: GridAddress, source: string | string[][], options?: GridOptions): PreparedGridChange;
9
9
  export declare function pasteTableText(editor: EditorSession, tablePath: string, anchor: GridAddress, source: string | string[][], options?: GridOptions): GridChange;
10
10
  /** Insert `count` rows so the first is body row `at`. A merged cell spanning that position grows. */
11
- export declare function prepareTableInsertRows(document: unknown, tablePath: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
11
+ export declare function prepareTableInsertRows(presentation: unknown, tablePath: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
12
12
  export declare function insertTableRows(editor: EditorSession, tablePath: string, at: number, count?: number, options?: GridOptions): GridChange;
13
13
  /** Delete body rows. A merged cell spanning a deleted row shrinks and keeps its text. The last row cannot be deleted. */
14
- export declare function prepareTableDeleteRows(document: unknown, tablePath: string, indices: number[], options?: GridOptions): PreparedGridChange;
14
+ export declare function prepareTableDeleteRows(presentation: unknown, tablePath: string, indices: number[], options?: GridOptions): PreparedGridChange;
15
15
  export declare function deleteTableRows(editor: EditorSession, tablePath: string, indices: number[], options?: GridOptions): GridChange;
16
16
  /** Move `count` rows from `from` so the first is at `to`. Refused when it would split a merged cell. */
17
- export declare function prepareTableMoveRows(document: unknown, tablePath: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
17
+ export declare function prepareTableMoveRows(presentation: unknown, tablePath: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
18
18
  export declare function moveTableRows(editor: EditorSession, tablePath: string, from: number, to: number, count?: number, options?: GridOptions): GridChange;
19
- export declare function prepareTableInsertColumns(document: unknown, tablePath: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
19
+ export declare function prepareTableInsertColumns(presentation: unknown, tablePath: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
20
20
  export declare function insertTableColumns(editor: EditorSession, tablePath: string, at: number, count?: number, options?: GridOptions): GridChange;
21
- export declare function prepareTableDeleteColumns(document: unknown, tablePath: string, indices: number[], options?: GridOptions): PreparedGridChange;
21
+ export declare function prepareTableDeleteColumns(presentation: unknown, tablePath: string, indices: number[], options?: GridOptions): PreparedGridChange;
22
22
  export declare function deleteTableColumns(editor: EditorSession, tablePath: string, indices: number[], options?: GridOptions): GridChange;
23
- export declare function prepareTableMoveColumns(document: unknown, tablePath: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
23
+ export declare function prepareTableMoveColumns(presentation: unknown, tablePath: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
24
24
  export declare function moveTableColumns(editor: EditorSession, tablePath: string, from: number, to: number, count?: number, options?: GridOptions): GridChange;
25
25
  /** Sort the body rows by a column, stably and by type (numbers, ISO dates, text; empty cells last). Rows joined by a row-spanning merge move as a block. */
26
- export declare function prepareTableSort(document: unknown, tablePath: string, column: number, options?: GridSortOptions): PreparedGridChange;
26
+ export declare function prepareTableSort(presentation: unknown, tablePath: string, column: number, options?: GridSortOptions): PreparedGridChange;
27
27
  export declare function sortTableRows(editor: EditorSession, tablePath: string, column: number, options?: GridSortOptions): GridChange;
28
28
  /** Turn the header row on (the first row becomes the header, or `{ use: "new" }` adds an empty one) or off. */
29
- export declare function prepareTableHeader(document: unknown, tablePath: string, enabled: boolean, options?: { use?: "first-row" | "new" }): PreparedGridChange;
29
+ export declare function prepareTableHeader(presentation: unknown, tablePath: string, enabled: boolean, options?: { use?: "first-row" | "new" }): PreparedGridChange;
30
30
  export declare function setTableHeader(editor: EditorSession, tablePath: string, enabled: boolean, options?: { use?: "first-row" | "new"; meta?: Record<string, unknown> }): GridChange;
@@ -5,7 +5,7 @@
5
5
  // correct. A table cell is a plain value, rich runs or a styled cell; editing keeps runs' formatting and a cell's style and spans.
6
6
  // Rows are addressed from 0 in the body; the header row is addressed as `{ section: "header", column }`.
7
7
  //
8
- // `prepare*` computes `{ document, patches, changed }` without touching a session; the session forms apply it as one undo step.
8
+ // `prepare*` computes `{ presentation, patches, changed }` without touching a session; the session forms apply it as one undo step.
9
9
  import {
10
10
  prepareGridCells,
11
11
  preparePaste,
@@ -28,64 +28,64 @@ import {
28
28
  prepareSetHeader,
29
29
  setGridHeader,
30
30
  } from "./grid-model.js";
31
- export function prepareTableCells(document, tablePath, edits, options = {}) {
32
- return prepareGridCells(document, tablePath, edits, { ...options, kind: "table" });
31
+ export function prepareTableCells(presentation, tablePath, edits, options = {}) {
32
+ return prepareGridCells(presentation, tablePath, edits, { ...options, kind: "table" });
33
33
  }
34
34
  export function setTableCells(editor, tablePath, edits, options = {}) {
35
35
  return setGridCells(editor, tablePath, edits, { ...options, kind: "table" });
36
36
  }
37
- export function prepareTablePaste(document, tablePath, anchor, source, options = {}) {
38
- return preparePaste(document, tablePath, anchor, source, { ...options, kind: "table" });
37
+ export function prepareTablePaste(presentation, tablePath, anchor, source, options = {}) {
38
+ return preparePaste(presentation, tablePath, anchor, source, { ...options, kind: "table" });
39
39
  }
40
40
  export function pasteTableText(editor, tablePath, anchor, source, options = {}) {
41
41
  return pasteGridText(editor, tablePath, anchor, source, { ...options, kind: "table" });
42
42
  }
43
- export function prepareTableInsertRows(document, tablePath, at, count = 1, options = {}) {
44
- return prepareInsertRows(document, tablePath, at, count, { ...options, kind: "table" });
43
+ export function prepareTableInsertRows(presentation, tablePath, at, count = 1, options = {}) {
44
+ return prepareInsertRows(presentation, tablePath, at, count, { ...options, kind: "table" });
45
45
  }
46
46
  export function insertTableRows(editor, tablePath, at, count = 1, options = {}) {
47
47
  return insertGridRows(editor, tablePath, at, count, { ...options, kind: "table" });
48
48
  }
49
- export function prepareTableDeleteRows(document, tablePath, indices, options = {}) {
50
- return prepareDeleteRows(document, tablePath, indices, { ...options, kind: "table" });
49
+ export function prepareTableDeleteRows(presentation, tablePath, indices, options = {}) {
50
+ return prepareDeleteRows(presentation, tablePath, indices, { ...options, kind: "table" });
51
51
  }
52
52
  export function deleteTableRows(editor, tablePath, indices, options = {}) {
53
53
  return deleteGridRows(editor, tablePath, indices, { ...options, kind: "table" });
54
54
  }
55
- export function prepareTableMoveRows(document, tablePath, from, to, count = 1, options = {}) {
56
- return prepareMoveRows(document, tablePath, from, to, count, { ...options, kind: "table" });
55
+ export function prepareTableMoveRows(presentation, tablePath, from, to, count = 1, options = {}) {
56
+ return prepareMoveRows(presentation, tablePath, from, to, count, { ...options, kind: "table" });
57
57
  }
58
58
  export function moveTableRows(editor, tablePath, from, to, count = 1, options = {}) {
59
59
  return moveGridRows(editor, tablePath, from, to, count, { ...options, kind: "table" });
60
60
  }
61
- export function prepareTableInsertColumns(document, tablePath, at, count = 1, options = {}) {
62
- return prepareInsertColumns(document, tablePath, at, count, { ...options, kind: "table" });
61
+ export function prepareTableInsertColumns(presentation, tablePath, at, count = 1, options = {}) {
62
+ return prepareInsertColumns(presentation, tablePath, at, count, { ...options, kind: "table" });
63
63
  }
64
64
  export function insertTableColumns(editor, tablePath, at, count = 1, options = {}) {
65
65
  return insertGridColumns(editor, tablePath, at, count, { ...options, kind: "table" });
66
66
  }
67
- export function prepareTableDeleteColumns(document, tablePath, indices, options = {}) {
68
- return prepareDeleteColumns(document, tablePath, indices, { ...options, kind: "table" });
67
+ export function prepareTableDeleteColumns(presentation, tablePath, indices, options = {}) {
68
+ return prepareDeleteColumns(presentation, tablePath, indices, { ...options, kind: "table" });
69
69
  }
70
70
  export function deleteTableColumns(editor, tablePath, indices, options = {}) {
71
71
  return deleteGridColumns(editor, tablePath, indices, { ...options, kind: "table" });
72
72
  }
73
- export function prepareTableMoveColumns(document, tablePath, from, to, count = 1, options = {}) {
74
- return prepareMoveColumns(document, tablePath, from, to, count, { ...options, kind: "table" });
73
+ export function prepareTableMoveColumns(presentation, tablePath, from, to, count = 1, options = {}) {
74
+ return prepareMoveColumns(presentation, tablePath, from, to, count, { ...options, kind: "table" });
75
75
  }
76
76
  export function moveTableColumns(editor, tablePath, from, to, count = 1, options = {}) {
77
77
  return moveGridColumns(editor, tablePath, from, to, count, { ...options, kind: "table" });
78
78
  }
79
- export function prepareTableSort(document, tablePath, column, options = {}) {
80
- return prepareSortRows(document, tablePath, column, { ...options, kind: "table" });
79
+ export function prepareTableSort(presentation, tablePath, column, options = {}) {
80
+ return prepareSortRows(presentation, tablePath, column, { ...options, kind: "table" });
81
81
  }
82
82
  export function sortTableRows(editor, tablePath, column, options = {}) {
83
83
  return sortGridRows(editor, tablePath, column, { ...options, kind: "table" });
84
84
  }
85
85
 
86
86
  /** Turn the header row on or off. On uses the first row as the header, or adds an empty one with `{ use: "new" }`. See {@link prepareSetHeader}. */
87
- export function prepareTableHeader(document, tablePath, enabled, options = {}) {
88
- return prepareSetHeader(document, tablePath, enabled, options);
87
+ export function prepareTableHeader(presentation, tablePath, enabled, options = {}) {
88
+ return prepareSetHeader(presentation, tablePath, enabled, options);
89
89
  }
90
90
  export function setTableHeader(editor, tablePath, enabled, options = {}) {
91
91
  return setGridHeader(editor, tablePath, enabled, options);
@@ -4,11 +4,11 @@ import type { TemplateFill, TemplateFillApplyResult } from "./templates.js";
4
4
  export interface TemplatePanelOptions {
5
5
  editor: EditorSession;
6
6
  /**
7
- * Draw the live preview: return the SVG of one slide of `document` with `variables` (the values typed so far)
8
- * as an SVG string, or a promise of one. Typically `renderSvg(document, { variables, slideIndex, ... })` from
7
+ * Draw the live preview: return the SVG of one slide of `presentation` with `variables` (the values typed so far)
8
+ * as an SVG string, or a promise of one. Typically `renderSlideSvg(presentation, slideIndex, { variables, ... })` from
9
9
  * `@openpresentation/opf-render` with the host's fonts. Omit for no preview.
10
10
  */
11
- renderPreview?: (input: { document: unknown; variables: Record<string, unknown>; slideIndex: number }) => string | Promise<string>;
11
+ renderPreview?: (input: { presentation: unknown; variables: Record<string, unknown>; slideIndex: number }) => string | Promise<string>;
12
12
  /** The slide the preview starts on. Call `refresh()` when it changes. */
13
13
  getSlideIndex?: () => number;
14
14
  /** The text field a variable token is inserted into: its OPF path and, optionally, the selection as UTF-16 offsets. Omit to hide the insert section. */
@@ -8,10 +8,11 @@ import {
8
8
  createTemplateFill,
9
9
  insertVariableToken,
10
10
  isTemplateDocument,
11
+ listBuiltins,
11
12
  setTemplate,
12
13
  suggestVariableId,
13
- templatesAvailable,
14
14
  } from "./templates.js";
15
+ import { slideTitle } from "./slides.js";
15
16
 
16
17
  const KIND_LABELS = { text: "Text", number: "Number", date: "Date", image: "Image", url: "Link", list: "List", color: "Color" };
17
18
  const STATUS_TEXT = { filled: "Filled", default: "Default value", unfilled: "Needs a value", optional: "Optional" };
@@ -20,6 +21,13 @@ const NEW_VARIABLE_KINDS = ["text", "number", "date", "image", "url", "list"];
20
21
 
21
22
  let panelCounter = 0;
22
23
 
24
+ /** A short display of a built-in's source value. */
25
+ function builtinValueText(entry) {
26
+ const value = entry.value;
27
+ const text = Array.isArray(value) ? value.join(", ") : value && typeof value === "object" ? String(value.src ?? "") : String(value ?? "");
28
+ return text.length > 60 ? `${text.slice(0, 57)}...` : text;
29
+ }
30
+
23
31
  function h(doc, tag, attributes = {}, ...children) {
24
32
  const element = doc.createElement(tag);
25
33
  for (const [key, value] of Object.entries(attributes)) {
@@ -34,13 +42,12 @@ function h(doc, tag, attributes = {}, ...children) {
34
42
  }
35
43
 
36
44
  /**
37
- * Mount the Fill template panel. Options: `editor` (a session), `renderPreview({document, variables, slideIndex})`
45
+ * Mount the Fill template panel. Options: `editor` (a session), `renderPreview({presentation, variables, slideIndex})`
38
46
  * returning an SVG string (or a promise of one) for the live preview (omit for no preview), `getSlideIndex`,
39
47
  * `getTarget()` returning `{path, start?, end?}` for the text field a token is inserted into, `onStatus(message,
40
48
  * {error})`, `onApply(result)`, `readFile(file)` (default: FileReader as a data URL) for image uploads.
41
49
  */
42
50
  export function createTemplatePanel(container, options) {
43
- if (!templatesAvailable()) throw new Error("The Fill template panel needs a core release that ships resolveVariables.");
44
51
  const { editor, renderPreview, getTarget, onStatus, onApply } = options;
45
52
  const doc = container.ownerDocument;
46
53
  const fill = createTemplateFill(editor);
@@ -56,6 +63,12 @@ export function createTemplatePanel(container, options) {
56
63
  const modeBox = h(doc, "input", { type: "checkbox", id: `${id}-mode` });
57
64
  modeLabel.append(modeBox, " This presentation is a template (it may have unfilled variables)");
58
65
  const fieldList = h(doc, "div", { class: "opf-template-fields" });
66
+ // Built-in variables: read-only values from the document's own speaker, organization and deck fields.
67
+ const builtinList = h(doc, "ul", { class: "opf-template-builtin-list" });
68
+ const builtinSection = h(doc, "details", { class: "opf-template-builtins", "data-opf-component": "template-builtins" },
69
+ h(doc, "summary", { text: "Built-in variables" }),
70
+ h(doc, "p", { class: "opf-template-help", text: "Read from the presentation's own speaker, organization and deck fields; edit those fields to change them. Use them like any variable: {{speaker.name}} inside text, or var:speaker.photo as a whole image field." }),
71
+ builtinList);
59
72
  const live = h(doc, "p", { class: "opf-template-live", role: "status", "aria-live": "polite" });
60
73
  const applyButton = h(doc, "button", { type: "button", class: "opf-template-apply primary", text: "Fill the presentation" });
61
74
  const partialButton = h(doc, "button", { type: "button", class: "opf-template-partial secondary", text: "Fill what is ready" });
@@ -85,7 +98,7 @@ export function createTemplatePanel(container, options) {
85
98
  h(doc, "label", { for: `${id}-new-sample`, text: "Sample value" }), newSample, newButton))
86
99
  : null;
87
100
 
88
- root.append(summary, modeLabel, fieldList, actions, live);
101
+ root.append(summary, modeLabel, fieldList, builtinSection, actions, live);
89
102
  if (previewSection) root.append(previewSection);
90
103
  if (insertSection) root.append(insertSection);
91
104
  container.append(root);
@@ -152,7 +165,7 @@ export function createTemplatePanel(container, options) {
152
165
  extras.push(colorInput);
153
166
  }
154
167
  if (field.kind === "image") {
155
- const assets = editor.document.assets && typeof editor.document.assets === "object" ? Object.keys(editor.document.assets) : [];
168
+ const assets = editor.presentation.assets && typeof editor.presentation.assets === "object" ? Object.keys(editor.presentation.assets) : [];
156
169
  if (assets.length) {
157
170
  assetSelect = h(doc, "select", { "aria-label": `${field.label}: pick a registered asset` }, h(doc, "option", { value: "", text: "Pick an asset…" }), ...assets.map((key) => h(doc, "option", { value: `asset:${key}`, text: key })));
158
171
  assetSelect.addEventListener("change", () => {
@@ -214,11 +227,22 @@ export function createTemplatePanel(container, options) {
214
227
  rows.set(field.id, row);
215
228
  fieldList.append(row.element);
216
229
  }
217
- insertSelect.replaceChildren(...fields.map((field) => h(doc, "option", { value: field.id, text: `${field.label} (${KIND_LABELS[field.kind]})` })));
218
- slideSelect.replaceChildren(...(editor.document.slides ?? []).map((slide, index) => h(doc, "option", { value: String(index), text: `${index + 1}. ${typeof slide.title === "string" && slide.title ? slide.title : "Untitled"}` })));
219
- previewSlide = Math.min(previewSlide, Math.max(0, (editor.document.slides?.length ?? 1) - 1));
230
+ const builtins = listBuiltins(editor.presentation);
231
+ insertSelect.replaceChildren(
232
+ ...fields.map((field) => h(doc, "option", { value: field.id, text: `${field.label} (${KIND_LABELS[field.kind]})` })),
233
+ ...(builtins.length ? [h(doc, "optgroup", { label: "Built-in variables" }, ...builtins.map((entry) => h(doc, "option", { value: entry.name, text: `${entry.label} (${entry.name})` })))] : []),
234
+ );
235
+ builtinSection.hidden = !builtins.length;
236
+ builtinList.replaceChildren(...builtins.map((entry) => {
237
+ const shown = builtinValueText(entry);
238
+ const uses = entry.uses.length;
239
+ return h(doc, "li", { "data-builtin": entry.name, "data-kind": entry.kind, "data-available": entry.available ? "true" : "false" },
240
+ h(doc, "code", { text: `{{${entry.name}}}` }), ` ${entry.label}: `, entry.available ? shown : h(doc, "em", { text: "not set" }), uses ? ` (used ${uses} time${uses === 1 ? "" : "s"})` : "");
241
+ }));
242
+ slideSelect.replaceChildren(...(editor.presentation.slides ?? []).map((slide, index) => h(doc, "option", { value: String(index), text: `${index + 1}. ${slideTitle(slide) || "Untitled"}` })));
243
+ previewSlide = Math.min(previewSlide, Math.max(0, (editor.presentation.slides?.length ?? 1) - 1));
220
244
  slideSelect.value = String(previewSlide);
221
- modeBox.checked = isTemplateDocument(editor.document);
245
+ modeBox.checked = isTemplateDocument(editor.presentation);
222
246
  update();
223
247
  }
224
248
 
@@ -263,7 +287,7 @@ export function createTemplatePanel(container, options) {
263
287
  if (!renderPreview) return;
264
288
  const token = ++previewToken;
265
289
  try {
266
- const svg = await renderPreview({ document: editor.document, variables: fill.values, slideIndex: previewSlide });
290
+ const svg = await renderPreview({ presentation: editor.presentation, variables: fill.values, slideIndex: previewSlide });
267
291
  if (token !== previewToken || destroyed) return;
268
292
  previewBox.innerHTML = svg;
269
293
  const preview = fill.preview();
@@ -304,7 +328,7 @@ export function createTemplatePanel(container, options) {
304
328
  setTemplate(editor, modeBox.checked);
305
329
  say(modeBox.checked ? "Marked as a template." : "Marked as a normal presentation.");
306
330
  } catch (error) {
307
- modeBox.checked = isTemplateDocument(editor.document);
331
+ modeBox.checked = isTemplateDocument(editor.presentation);
308
332
  say(messageOf(error), true);
309
333
  }
310
334
  });
@@ -335,13 +359,13 @@ export function createTemplatePanel(container, options) {
335
359
  say("Give the new variable a name.", true);
336
360
  return;
337
361
  }
338
- const variableId = suggestVariableId(editor.document, name);
362
+ const variableId = suggestVariableId(editor.presentation, name);
339
363
  const kind = newKind.value;
340
364
  const declaration = { type: kind, label: name };
341
365
  const sample = newSample.value.trim();
342
366
  if (sample) {
343
367
  // A template keeps the sample as an example (the slot stays unfilled); a normal deck needs a real value.
344
- const field = isTemplateDocument(editor.document) ? "example" : "value";
368
+ const field = isTemplateDocument(editor.presentation) ? "example" : "value";
345
369
  declaration[field] = kind === "number" && Number.isFinite(Number(sample)) ? Number(sample) : kind === "list" ? sample.split(/\r?\n|,/).map((entry) => entry.trim()).filter(Boolean) : sample;
346
370
  }
347
371
  try {
@@ -55,25 +55,23 @@ export interface TemplatePreview {
55
55
  complete: boolean;
56
56
  }
57
57
 
58
- /** True when the core this editor runs on can resolve template variables. */
59
- export declare function templatesAvailable(): boolean;
60
- export declare function isTemplateDocument(document: unknown): boolean;
58
+ export declare function isTemplateDocument(presentation: unknown): boolean;
61
59
  /** True when the document is a template or declares a variable that is not a color. */
62
- export declare function hasTemplateVariables(document: unknown): boolean;
60
+ export declare function hasTemplateVariables(presentation: unknown): boolean;
63
61
  export declare function valueToFieldText(kind: TemplateVariableKind, value: unknown): string;
64
62
  /** Parse a form control's text. A blank control clears the value. */
65
63
  export declare function fieldTextToValue(kind: TemplateVariableKind, text: string): { ok: true; value: unknown } | { ok: false; message: string };
66
- export declare function listTemplateFields(document: unknown, values?: Record<string, unknown>): TemplateField[];
67
- export declare function templateStatus(document: unknown, values?: Record<string, unknown>): TemplateStatus;
64
+ export declare function listTemplateFields(presentation: unknown, values?: Record<string, unknown>): TemplateField[];
65
+ export declare function templateStatus(presentation: unknown, values?: Record<string, unknown>): TemplateStatus;
68
66
  /** The concrete deck for the values with each unfilled variable's example. Never throws for missing values. */
69
- export declare function previewTemplate(document: unknown, values?: Record<string, unknown>): TemplatePreview;
67
+ export declare function previewTemplate(presentation: unknown, values?: Record<string, unknown>): TemplatePreview;
70
68
 
71
69
  export interface TemplateFillOptions {
72
70
  /** Extra session meta for the fill edit. */
73
71
  meta?: Record<string, unknown>;
74
72
  }
75
73
  export interface TemplateFillApplyResult {
76
- document: unknown;
74
+ presentation: unknown;
77
75
  patches: unknown[];
78
76
  inversePatches: unknown[];
79
77
  validation: unknown;
@@ -103,9 +101,22 @@ export interface TemplateFill {
103
101
  }
104
102
  export declare function createTemplateFill(editor: EditorSession, options?: TemplateFillOptions): TemplateFill;
105
103
 
106
- /** `{{id}}`, or `{{id|format}}`. */
104
+ /** The token for a declared variable id or a built-in name such as `speaker.name`. */
107
105
  export declare function variableToken(id: string, format?: string): string;
108
- export declare function suggestVariableId(document: unknown, label?: string): string;
106
+ /** One built-in variable of a document (core `listBuiltinVariables`). */
107
+ export interface BuiltinInfo {
108
+ /** Dotted name, as written inside `{{...}}`. */
109
+ name: string;
110
+ kind: "text" | "image" | "list";
111
+ label: string;
112
+ /** The source value, when the document has one. */
113
+ value?: unknown;
114
+ available: boolean;
115
+ uses: { id: string; path: string; form: "token" | "reference" }[];
116
+ }
117
+ /** The presentation's built-in variables, read-only. */
118
+ export declare function listBuiltins(presentation: unknown): BuiltinInfo[];
119
+ export declare function suggestVariableId(presentation: unknown, label?: string): string;
109
120
  /** Declare a variable as one undoable edit. */
110
121
  export declare function declareVariable(editor: EditorSession, id: string, declaration: string | Record<string, unknown>, meta?: Record<string, unknown>): unknown;
111
122
  /** Mark the document as a template, or as a normal deck with `false`, as one undoable edit. */
@@ -123,4 +134,4 @@ export interface InsertVariableTokenOptions {
123
134
  meta?: Record<string, unknown>;
124
135
  }
125
136
  /** Insert a variable token into a text field as one undoable edit. */
126
- export declare function insertVariableToken(editor: EditorSession, path: string, id: string, options?: InsertVariableTokenOptions): { token: string; document: unknown; patches: unknown[]; inversePatches: unknown[]; validation: unknown };
137
+ export declare function insertVariableToken(editor: EditorSession, path: string, id: string, options?: InsertVariableTokenOptions): { token: string; presentation: unknown; patches: unknown[]; inversePatches: unknown[]; validation: unknown };