@openpresentation/opf-editor 0.26.10 → 0.27.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.
@@ -0,0 +1,388 @@
1
+ import type { Catalog } from "@openpresentation/opf";
2
+ import type { ToSvgOptions } from "@openpresentation/opf-render";
3
+ import type { CanvasEditor, CanvasEditorOptions, EditorFonts } from "./canvas.js";
4
+ import type { DesignControls, DesignControlsOptions } from "./design-controls.js";
5
+ import type { EditorEvent, EditorSession } from "./index.js";
6
+ import type { ExportMenu, ExportMenuOptions } from "./export-menu.js";
7
+ import type { FindPanel, FindPanelOptions } from "./find-panel.js";
8
+ import type { LayoutStudio } from "./workspace-layouts.js";
9
+ import type { AutosaveStatus, Persistence, PersistenceOptions } from "./persistence.js";
10
+ import type { Presenter } from "./presenter.js";
11
+ import type { ReviewFinding, ReviewHook } from "./review.js";
12
+ import type { ReviewPanel, ReviewPanelOptions } from "./review-panel.js";
13
+ import type { SlideManager } from "./slide-manager.js";
14
+ import type { SourceFormat, SourceFile, SourcePane, SourcePaneOptions } from "./source-pane.js";
15
+
16
+ export type OpfEditorLayout = "full" | "canvas" | "source";
17
+ export type OpfEditorView = "slide" | "sorter" | "outline" | "layouts";
18
+ export type OpfEditorDensity = "comfortable" | "touch";
19
+
20
+ /** The short names of a theme, each one `--opf-ui-*` custom property. The full names are accepted too. */
21
+ export declare const THEME_TOKENS: Readonly<{
22
+ background: "--opf-ui-bg";
23
+ surface: "--opf-ui-surface";
24
+ muted: "--opf-ui-muted";
25
+ mutedAlt: "--opf-ui-muted-2";
26
+ text: "--opf-ui-fg";
27
+ textMuted: "--opf-ui-muted-fg";
28
+ accent: "--opf-ui-accent";
29
+ accentText: "--opf-ui-accent-fg";
30
+ accentStrong: "--opf-ui-accent-strong";
31
+ border: "--opf-ui-border";
32
+ borderStrong: "--opf-ui-border-strong";
33
+ focus: "--opf-ui-ring";
34
+ danger: "--opf-ui-danger";
35
+ warning: "--opf-ui-warn";
36
+ success: "--opf-ui-success";
37
+ codeBackground: "--opf-ui-code-bg";
38
+ codeBorder: "--opf-ui-code-border";
39
+ font: "--opf-ui-font";
40
+ mono: "--opf-ui-mono";
41
+ radiusSmall: "--opf-ui-radius-sm";
42
+ radius: "--opf-ui-radius";
43
+ radiusMedium: "--opf-ui-radius-md";
44
+ radiusLarge: "--opf-ui-radius-lg";
45
+ controlHeight: "--opf-ui-control-h";
46
+ shadow: "--opf-ui-shadow-pop";
47
+ motion: "--opf-ui-ease";
48
+ }>;
49
+ export type ThemeTokenName = keyof typeof THEME_TOKENS;
50
+ /** Token values by short name or by full custom-property name: `{ accent: "#0a7", "--opf-ui-border": "#ddd" }`. */
51
+ export type ThemeTokens = Partial<Record<ThemeTokenName, string>> & { [property: `--opf-ui-${string}`]: string | undefined };
52
+
53
+ export interface OpfEditorTheme {
54
+ /**
55
+ * `light` or `dark` forces the scheme for this editor, `auto` follows `prefers-color-scheme`, and `inherit` (the default) leaves it to the
56
+ * host page, which marks dark with `.dark` or `[data-theme="dark"]` on `<html>` (or any ancestor).
57
+ */
58
+ colorScheme?: "light" | "dark" | "auto" | "inherit";
59
+ /** Values for both schemes. */
60
+ tokens?: ThemeTokens;
61
+ /** Values that replace `tokens` (and the defaults) while the editor is dark. */
62
+ dark?: ThemeTokens;
63
+ }
64
+ export interface NormalizedTheme {
65
+ colorScheme: "light" | "dark" | "auto" | "inherit";
66
+ tokens: Record<string, string>;
67
+ dark: Record<string, string>;
68
+ }
69
+ /** Full custom-property names for a map of short or full token names. Throws on a name that is not in the contract. */
70
+ export declare function normalizeThemeTokens(map: ThemeTokens | undefined, label?: string): Record<string, string>;
71
+ export declare function normalizeTheme(theme: OpfEditorTheme | undefined): NormalizedTheme;
72
+ export interface ThemeController {
73
+ readonly theme: NormalizedTheme;
74
+ update(theme: OpfEditorTheme | undefined): NormalizedTheme;
75
+ destroy(): void;
76
+ }
77
+ /** Apply a theme to the editor mounted in `root`. `createOpfEditor` does this for `options.theme` and `handle.setTheme`. */
78
+ export declare function createThemeController(root: HTMLElement, theme?: OpfEditorTheme): ThemeController;
79
+
80
+ /** What a host's own acquire function returns: the handle and the way to give it back. */
81
+ export interface OpfEditorFontsLease {
82
+ fonts: EditorFonts;
83
+ release(): void;
84
+ }
85
+ /**
86
+ * The fonts the editor draws with: the renderer's handle, a lease (`{ fonts, release() }`), a promise of either, a function that returns any of those
87
+ * (called once), or `{ lease(), release(fonts) }`. A lease is given back exactly once on `destroy()`, also when the editor was destroyed before it arrived.
88
+ */
89
+ export type OpfEditorFontsOption =
90
+ | EditorFonts
91
+ | OpfEditorFontsLease
92
+ | Promise<EditorFonts | OpfEditorFontsLease>
93
+ | (() => EditorFonts | OpfEditorFontsLease | Promise<EditorFonts | OpfEditorFontsLease>)
94
+ | { lease(): EditorFonts | OpfEditorFontsLease | Promise<EditorFonts | OpfEditorFontsLease>; release?(fonts: EditorFonts): void };
95
+
96
+ /**
97
+ * The elements a host arranges itself. Each part mounts in its own slot; a part without one is not mounted. With `slots`, nothing is built.
98
+ * `overlay` is where dialogs (the presenter, the agent handoff) mount; it defaults to a node inside the mount element.
99
+ */
100
+ export interface OpfEditorSlots {
101
+ source?: HTMLElement;
102
+ canvas?: HTMLElement;
103
+ /** The element hidden while another view shows (default: the canvas element). */
104
+ slide?: HTMLElement;
105
+ /** A host for the canvas's property forms (default: a floating panel on the canvas). */
106
+ properties?: HTMLElement;
107
+ navigator?: HTMLElement;
108
+ thumbnails?: HTMLElement;
109
+ /** The slide list (a vertical navigator) and, apart, the element for its Duplicate, Delete and Hide buttons. */
110
+ slides?: HTMLElement;
111
+ slideToolbar?: HTMLElement;
112
+ sorter?: HTMLElement;
113
+ outline?: HTMLElement;
114
+ layouts?: HTMLElement;
115
+ /** The element that is hidden while its view is not shown, when it is not the element the view mounts in (`sorterView`, `outlineView`, `layoutsView`). */
116
+ sorterView?: HTMLElement;
117
+ outlineView?: HTMLElement;
118
+ layoutsView?: HTMLElement;
119
+ export?: HTMLElement;
120
+ present?: HTMLElement;
121
+ review?: HTMLElement;
122
+ design?: HTMLElement;
123
+ find?: HTMLElement;
124
+ /** The restore prompt and the autosave line of `persistence`. */
125
+ banner?: HTMLElement;
126
+ autosave?: HTMLElement;
127
+ /** The status line: the editor writes its messages here. */
128
+ status?: HTMLElement;
129
+ overlay?: HTMLElement;
130
+ }
131
+
132
+ export interface OpfEditorPanels {
133
+ /** The code editor (JSON, YAML, Markdown). */
134
+ source?: boolean;
135
+ /** The slide itself. Default true (false in a `"source"` layout). */
136
+ canvas?: boolean;
137
+ /** Previous, counter, next. */
138
+ navigator?: boolean;
139
+ thumbnails?: boolean;
140
+ /** Undo and redo buttons in the default layouts. */
141
+ history?: boolean;
142
+ find?: boolean;
143
+ /** The Present button, F5 and the full-screen presenter. */
144
+ present?: boolean;
145
+ export?: boolean;
146
+ review?: boolean;
147
+ /** The design controls (themes, fonts, backgrounds, table and chart options). Off by default; give `design.vocabularies` for the full set. */
148
+ design?: boolean;
149
+ /** The views: Layout Studio, the slide sorter and the outline. */
150
+ layouts?: boolean;
151
+ sorter?: boolean;
152
+ outline?: boolean;
153
+ /** The vertical slide list (slots only). */
154
+ slides?: boolean;
155
+ /** Autosave and the restore prompt; on when `persistence` is given. */
156
+ persistence?: boolean;
157
+ }
158
+
159
+ export interface OpfEditorSourceOptions {
160
+ format?: SourceFormat;
161
+ /** The file name without extension. */
162
+ name?: string;
163
+ /** The text of the document as the author wrote it, so its spelling is kept. A string `document` is used when this is not given. */
164
+ text?: string;
165
+ /** The Web Worker that runs the pane's text checks off the main thread: `true`, a `Worker`, or a function that returns one. Default: the main thread. */
166
+ worker?: SourcePaneOptions["worker"];
167
+ /** Core `validate` options for the verdict on the text (default the format and reference rules). */
168
+ checks?: { only?: readonly string[]; [key: string]: unknown };
169
+ outline?: SourcePaneOptions["outline"];
170
+ applyDelay?: number;
171
+ completions?: boolean;
172
+ label?: string;
173
+ catalogs?: SourcePaneOptions["catalogs"];
174
+ resolvePreview?: SourcePaneOptions["resolvePreview"];
175
+ onRename?(name: string): void;
176
+ /** Take the copied or downloaded source yourself (the defaults are the clipboard and a Blob download). */
177
+ onCopy?(file: SourceFile): void | Promise<void>;
178
+ onDownload?(file: SourceFile): void | Promise<void>;
179
+ }
180
+
181
+ export interface OpfEditorOptions {
182
+ /** The document to edit: an OPF object, or its JSON text (then the pane keeps its spelling). Creates the session. */
183
+ document?: unknown;
184
+ /** An existing session to edit instead of creating one. Register its catalogs on it. */
185
+ session?: EditorSession;
186
+ /** The host's catalogs (for example `[gallery]`): for the session the editor creates, and for every draw. */
187
+ catalogs?: readonly Catalog[];
188
+ /** The renderer's fonts handle. See `OpfEditorFontsOption`. A function or a lease is called once; `release` runs on `destroy()`. */
189
+ fonts?: OpfEditorFontsOption;
190
+ /** Extra `toSvg` options for every draw. */
191
+ renderOptions?: ToSvgOptions;
192
+ /** Turns an asset reference into a loadable address (the renderer's `imageResolver`), for every draw. */
193
+ imageResolver?: ToSvgOptions["imageResolver"];
194
+ /** Stores an uploaded image yourself and returns its reference (see `@openpresentation/opf-editor/assets`). Used by the design controls. */
195
+ onAddAsset?: DesignControlsOptions["onAddAsset"];
196
+ /** `"authored"` (default): the canvas shows `{{variables}}` as written. `"resolved"`: filled in, except in a template deck; a press on filled-in text opens its properties form. */
197
+ variables?: "authored" | "resolved";
198
+
199
+ /** `"full"` (default), `"canvas"` or `"source"`: the editor builds the arrangement. Ignored when `slots` is given. */
200
+ layout?: OpfEditorLayout;
201
+ slots?: OpfEditorSlots;
202
+ panels?: OpfEditorPanels;
203
+ /** The view shown first (default `"slide"`). */
204
+ view?: OpfEditorView;
205
+ /** The side tab shown first in the default layout. */
206
+ panel?: "review" | "design";
207
+ /** The slide shown first (0-based). */
208
+ slideIndex?: number;
209
+ /** `"contain"` sizes the slide to fit its frame (default in the built layouts); `false` leaves the size to the host. */
210
+ fit?: "contain" | false;
211
+
212
+ source?: OpfEditorSourceOptions;
213
+ canvas?: Omit<CanvasEditorOptions, "editor" | "fonts" | "renderOptions" | "slideIndex" | "presentation" | "catalogs" | "propertiesContainer">;
214
+ export?: Omit<ExportMenuOptions, "getDeck" | "fonts" | "catalogs" | "mode" | "density" | "beforeExport">;
215
+ review?: boolean | (Omit<ReviewPanelOptions, "editor" | "getSlideIndex" | "getValidateOptions" | "autoRefresh" | "onGoTo" | "onFocusField" | "onStatus" | "onChange" | "review"> & { hook?: ReviewHook });
216
+ find?: boolean | { onGoTo?: FindPanelOptions["onGoTo"] };
217
+ design?: boolean | Omit<DesignControlsOptions, "editor" | "getSlideIndex" | "getSelectedPath" | "onSelectPath" | "onStatus" | "catalogs" | "onAddAsset">;
218
+ /** Autosave to this browser and a restore prompt. `key` names the document. `false` or absent: nothing is stored. */
219
+ persistence?: false | (PersistenceOptions & { suffix?: string; fallback?: string });
220
+
221
+ /** Refuse every write that comes through the editor's own parts, and lock the slide. The host can still write to `handle.session` and call `setDocument`. */
222
+ readOnly?: boolean;
223
+ /** `"auto"` (default) follows `(pointer: coarse)`. */
224
+ density?: OpfEditorDensity | "auto";
225
+ /** `"auto"` (default) shows the sheet at 900px and under. */
226
+ exportMode?: "menu" | "sheet" | "auto";
227
+ theme?: OpfEditorTheme;
228
+ /**
229
+ * Text for the default layouts (`review`, `design`, `find`, `undo`, `redo`, `slideView`, `inspector`) and the accessible names of the slide row's
230
+ * two landmarks (`navigator`, default "Slide navigation"; `thumbnails`, default "Slides").
231
+ */
232
+ labels?: Partial<Record<"review" | "design" | "find" | "undo" | "redo" | "slideView" | "inspector" | "navigator" | "thumbnails", string>>;
233
+ /**
234
+ * Which slide stays on show when the document changes outside the canvas: `"id"` (default) follows the slide by its id (a reorder in the source
235
+ * pane keeps it), `"position"` keeps the position and clamps it to the deck.
236
+ */
237
+ slideRetention?: "id" | "position";
238
+ /** Ctrl/Cmd+Z and Ctrl/Cmd+Shift+Z outside text fields (default true). */
239
+ shortcuts?: boolean;
240
+
241
+ onChange?(detail: OpfEditorEvents["change"]): void;
242
+ onSelection?(detail: OpfEditorEvents["selection"]): void;
243
+ onError?(error: Error, detail: Omit<OpfEditorEvents["error"], "error">): void;
244
+ onReady?(detail: OpfEditorEvents["ready"]): void;
245
+ /** Listeners by event name; the same as calling `on` before anything happens. */
246
+ on?: { [Name in keyof OpfEditorEvents]?: (detail: OpfEditorEvents[Name]) => void };
247
+ }
248
+
249
+ export interface OpfEditorSourceState {
250
+ /** `valid`, `invalid` (the document has errors) or `syntax` (the text does not read as a document in `format`). */
251
+ status: { kind: "valid"; warnings: number } | { kind: "invalid"; errors: number; warnings: number } | { kind: "syntax"; format: SourceFormat; message: string };
252
+ format: SourceFormat;
253
+ text: string;
254
+ }
255
+
256
+ export interface OpfEditorEvents {
257
+ /** The parts are mounted (the fonts handle arrived); the first update, which may wait for the faces the document needs, has not run yet. */
258
+ mounted: { editor: OpfEditor };
259
+ /** The first update has run: the document has been checked against the fonts it needs, and the parts show it. */
260
+ ready: { editor: OpfEditor };
261
+ /** The document changed (an edit in the canvas or the source, undo, redo, a loaded document). Not fired for a catalogs change. */
262
+ change: { document: unknown; canUndo: boolean; canRedo: boolean; event: EditorEvent };
263
+ selection: { path: string; value: unknown; slideIndex: number; element: Element | undefined };
264
+ /** The slide changed (0-based). */
265
+ slide: { index: number; count: number };
266
+ view: { view: OpfEditorView; previous: OpfEditorView };
267
+ /** The text of the source pane was checked: the verdict about the text it shows. */
268
+ source: OpfEditorSourceState;
269
+ /** The pane has text it has not applied yet (`true`), or has caught up. */
270
+ checking: { checking: boolean };
271
+ fonts: { state: "loading"; pending?: string[] } | { state: "ready" } | { state: "error"; error: Error };
272
+ /** The slide was drawn at this size (the slide's own units). */
273
+ geometry: { width: number; height: number };
274
+ /** After every update once the fonts are ready: the document, the slide on show, its geometry and layout notes (computed when read). */
275
+ sync: { presentation: any; slideIndex: number; count: number; readonly geometry: any; readonly diagnostics: { path: string; message: string }[] };
276
+ /** The canvas drew a slide. */
277
+ render: { presentation: unknown; slideIndex: number; svg: SVGSVGElement; geometry: any; draft: boolean };
278
+ /** An inline edit in the canvas: a draft that is not in the document yet, its commit, or its cancel. */
279
+ draft: { presentation: unknown; path: string; value: unknown };
280
+ commit: { path: string; editor: EditorSession };
281
+ cancel: { path?: string };
282
+ status: { message: string };
283
+ error: { error: Error; message: string; [key: string]: unknown };
284
+ /** The source was copied or downloaded (or exported as JSON, YAML or Markdown): it is kept somewhere of the person's own. */
285
+ kept: { kind: "copy" | "download" };
286
+ rename: { name: string };
287
+ export: { blob: Blob; fileName: string; format: string; notes: string[] };
288
+ persistence: AutosaveStatus;
289
+ review: { error: number; warning: number; info: number; total: number; all: number; sources: string[]; unavailable: boolean };
290
+ /** A Review finding about the deck rather than a slide asked to be shown. */
291
+ "review-goto": { finding: ReviewFinding; target: unknown };
292
+ "review-focus": { fix: unknown; target: unknown };
293
+ panel: { panel: "review" | "design" };
294
+ destroy: Record<string, never>;
295
+ }
296
+
297
+ export interface OpfEditorSetDocumentOptions {
298
+ /** The document as written (its JSON, or a YAML or Markdown file's text). The source pane shows it as it is, and a JSON file that is not changed is downloaded with its own bytes (line endings included). */
299
+ text?: string;
300
+ /** Switch the source pane to this format first. */
301
+ format?: SourceFormat;
302
+ /** The file name the pane uses. */
303
+ name?: string;
304
+ /** Treat the loaded document as the starting point of autosave (default true): it is not stored or warned about until edited. */
305
+ rebase?: boolean;
306
+ }
307
+
308
+ export interface OpfEditor {
309
+ /** The mount element and the node the editor built inside it. */
310
+ readonly element: HTMLElement;
311
+ readonly root: HTMLElement;
312
+ /** Settles when the parts are mounted and the first update is done; rejects when the fonts could not be obtained. */
313
+ readonly ready: Promise<OpfEditor>;
314
+ readonly destroyed: boolean;
315
+ readonly mounted: boolean;
316
+ readonly session: EditorSession;
317
+ readonly canvas: CanvasEditor | undefined;
318
+ readonly sourcePane: SourcePane | undefined;
319
+ readonly presenter: Presenter | undefined;
320
+ readonly exportMenu: ExportMenu | undefined;
321
+ readonly review: ReviewPanel | undefined;
322
+ readonly find: (FindPanel & { openPanel(options?: { replace?: boolean; query?: string }): void }) | undefined;
323
+ readonly design: DesignControls | undefined;
324
+ readonly persistence: Persistence | undefined;
325
+ readonly slideManager: SlideManager | undefined;
326
+ readonly layoutStudio: LayoutStudio | undefined;
327
+ readonly fonts: EditorFonts | undefined;
328
+ readonly slideIndex: number;
329
+ /** The content last selected on the canvas. */
330
+ readonly selection: { path: string; value: unknown } | null;
331
+ readonly view: OpfEditorView;
332
+ readonly views: OpfEditorView[];
333
+ readonly readOnly: boolean;
334
+ /** False while the text in the source pane is not a valid deck: the slide shows the last valid one. */
335
+ readonly inSync: boolean;
336
+ /** The latest verdict on the text of the source pane (what the last `source` event carried), or `null` before the first. */
337
+ readonly sourceState: OpfEditorSourceState | null;
338
+ readonly lastError: Error | undefined;
339
+ readonly slots: OpfEditorSlots;
340
+ /** The document. Frozen and replaced on every change, so it works as a store snapshot. */
341
+ getDocument(): unknown;
342
+ /**
343
+ * Open a different document as one undoable change that forks none of the catalog records it embeds. With a source pane it shows `options.text`
344
+ * as written. A document the session refuses is listed in the pane as a draft and `false` is returned. Throws before `ready`.
345
+ */
346
+ setDocument(document: unknown, options?: OpfEditorSetDocumentOptions): boolean;
347
+ /** Switch the view. Commits an open canvas edit first; false when that failed or the view is not available. */
348
+ setView(view: OpfEditorView, options?: { focus?: boolean }): boolean;
349
+ /** Show slide `index` (0-based). Commits an open canvas edit first. */
350
+ setSlide(index: number): boolean;
351
+ /** Run the update again now: the parts redraw and `sync` is emitted once the fonts are ready. Resolves when it is done. */
352
+ refresh(): Promise<void>;
353
+ /** Resolves when the latest update has finished (fonts loaded, parts redrawn). */
354
+ whenIdle(): Promise<void>;
355
+ /** Show a slide and select the content at `path` on it. */
356
+ goTo(index: number, path?: string): boolean;
357
+ /** Select the content at `path` on the current slide (or the closest content that can be selected); returns the path selected. */
358
+ select(path: string, options?: { focus?: boolean }): string | null;
359
+ showPanel(panel: "review" | "design", focus?: boolean): boolean;
360
+ /** Start the full-screen presentation at the slide on show. False when it cannot start (invalid source, no slides, already running). */
361
+ present(): boolean;
362
+ undo(): boolean;
363
+ redo(): boolean;
364
+ /** Commit an open canvas edit. False when it could not be committed. */
365
+ commit(): boolean;
366
+ focus(target?: "source" | "canvas"): boolean;
367
+ openFind(options?: { replace?: boolean; query?: string }): boolean;
368
+ /** The text of the source pane with any pending edit applied, and whether it is a draft the document did not take. */
369
+ readSource(): { text: string; format: SourceFormat; draft: boolean };
370
+ setName(name: string): void;
371
+ getName(): string | undefined;
372
+ setReadOnly(readOnly: boolean): void;
373
+ setTheme(theme: OpfEditorTheme | undefined): NormalizedTheme;
374
+ setDensity(density: OpfEditorDensity | "auto"): void;
375
+ setExportMode(mode: "menu" | "sheet" | "auto"): void;
376
+ /** Write a message on the status line (and to `status` listeners). */
377
+ status(message: string): void;
378
+ /** Listen to an event. Returns a function that stops listening. */
379
+ on<Name extends keyof OpfEditorEvents>(name: Name, listener: (detail: OpfEditorEvents[Name]) => void): () => void;
380
+ off<Name extends keyof OpfEditorEvents>(name: Name, listener: (detail: OpfEditorEvents[Name]) => void): void;
381
+ /** Remove every part, listener, observer and the built DOM, release the fonts lease and the theme. Safe to call twice and before `ready`. */
382
+ destroy(): void;
383
+ }
384
+
385
+ /** Mount the editor in `element` (its content is replaced). */
386
+ export declare function createOpfEditor(element: HTMLElement, options?: OpfEditorOptions): OpfEditor;
387
+
388
+ export type { LayoutStudio } from "./workspace-layouts.js";