@openpresentation/opf-editor 0.24.0 → 0.24.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.
@@ -0,0 +1,25 @@
1
+ /** The renderer's `toSvg(presentation, slides, options)`: one SVG string for a slide number, or an array for a list of slide numbers (1-based). */
2
+ export type ThumbnailRenderer = (presentation: any, slides: number | number[], options: any) => string | string[];
3
+
4
+ export interface ThumbnailCacheOptions {
5
+ /** The renderer's `toSvg` from `@openpresentation/opf-render/svg`. */
6
+ toSvg: ThumbnailRenderer;
7
+ /** The render options for every thumbnail (`catalogs`, `fonts`, `text: "system"`, `trace: false`, ...). The `fonts` handle is not part of the cache context. */
8
+ getOptions: () => Record<string, any>;
9
+ }
10
+
11
+ export interface ThumbnailCache {
12
+ /**
13
+ * Render the thumbnails that are not cached yet, in one `toSvg` call so the renderer resolves the deck once, and drop entries no slide uses.
14
+ * Pass `indices` (0-based) to render only those slides, for example the visible ones; without it every missing slide renders.
15
+ * A change of deck metadata, section context, catalogs or render settings clears the whole cache, because inherited furniture changes.
16
+ */
17
+ prepare(presentation: any, indices?: number[]): void;
18
+ /** The SVG markup (or the text "Preview unavailable") of one slide. Entries are keyed by the slide's index, the slide count and the slide's own content. */
19
+ html(presentation: any, index: number): string;
20
+ /** Forget everything, for example after fonts load. */
21
+ clear(): void;
22
+ }
23
+
24
+ /** A cache of slide thumbnails that renders only the slides whose content changed. */
25
+ export declare function createThumbnailCache(options: ThumbnailCacheOptions): ThumbnailCache;
@@ -0,0 +1,50 @@
1
+ // Slide thumbnail cache (promoted from the playground example by RR-98). The host owns the font gate: it runs before this synchronous render and
2
+ // clears the cache when faces load. Selecting every missing slide in one call makes the renderer resolve/compose the deck once, including hidden
3
+ // slides. `prepare(deck, indices)` renders only the listed slides (a lazy strip draws the visible ones first); without `indices` it renders every
4
+ // missing slide, as before.
5
+ export function createThumbnailCache({toSvg, getOptions}) {
6
+ const cache = new Map();
7
+ let context;
8
+ let preparedDeck;
9
+ const key = (deck, index) => JSON.stringify([index, deck.slides.length, deck.slides[index]]);
10
+ const contextOf = (deck, options) => {
11
+ const {slides, ...metadata} = deck;
12
+ const {fonts: _fonts, ...settings} = options;
13
+ // Deck metadata and preceding sections can change inherited furniture, variables, data and design on another slide.
14
+ return JSON.stringify([metadata, slides.map(slide => slide.section), settings]);
15
+ };
16
+ function prepare(deck, indices) {
17
+ const options = getOptions();
18
+ const nextContext = contextOf(deck, options);
19
+ if (nextContext !== context) cache.clear();
20
+ context = nextContext;
21
+ preparedDeck = deck;
22
+ const keys = deck.slides.map((_, index) => key(deck, index));
23
+ const wanted = indices === undefined ? keys.map((_, index) => index) : [...new Set(indices)].filter(index => Number.isInteger(index) && index >= 0 && index < keys.length);
24
+ const missing = wanted.filter(index => !cache.has(keys[index]));
25
+ if (missing.length) {
26
+ try {
27
+ const images = toSvg(deck, missing.map(index => index + 1), options);
28
+ missing.forEach((index, position) => cache.set(keys[index], images[position]));
29
+ } catch {
30
+ // A paint error can belong to just one slide (for example an invalid XML character). Retain the old per-slide
31
+ // "Preview unavailable" behavior in this exceptional path instead of hiding every otherwise valid thumbnail.
32
+ for (const index of missing) {
33
+ try { cache.set(keys[index], toSvg(deck, index + 1, options)); }
34
+ catch { cache.set(keys[index], 'Preview unavailable'); }
35
+ }
36
+ }
37
+ }
38
+ const live = new Set(keys);
39
+ for (const value of cache.keys()) if (!live.has(value)) cache.delete(value);
40
+ }
41
+ return {
42
+ prepare,
43
+ html(deck, index) {
44
+ const value = key(deck, index);
45
+ if (deck !== preparedDeck || !cache.has(value)) prepare(deck);
46
+ return cache.get(value);
47
+ },
48
+ clear: () => { cache.clear(); preparedDeck = undefined; },
49
+ };
50
+ }
@@ -0,0 +1,50 @@
1
+ import type { EditorSession } from "./index.js";
2
+ import type { ThumbnailCache } from "./thumbnail-cache.js";
3
+
4
+ export interface ThumbnailStripLabels {
5
+ /** The accessible name of the `nav`. Default: "Slides". */
6
+ navigation?: string;
7
+ /** The title shown for a slide without one. Default: "Untitled". */
8
+ untitled?: string;
9
+ /** Read after the title of a hidden slide. Default: "hidden". */
10
+ hidden?: string;
11
+ /** Shown in place of a thumbnail the renderer could not draw. Default: "Preview unavailable". */
12
+ unavailable?: string;
13
+ }
14
+ export interface ThumbnailStripOptions {
15
+ /** The editor session. Required. */
16
+ editor: EditorSession;
17
+ /** The renderer's fonts handle, the one the canvas and the export use. Thumbnails wait for the faces the document needs. */
18
+ fonts?: unknown;
19
+ /** Extra `toSvg` options, for example `catalogs`. Thumbnails always draw with `text: "system"` and `trace: false`. */
20
+ renderOptions?: Record<string, any>;
21
+ /** The current slide (0-based). Default 0. */
22
+ slideIndex?: number;
23
+ /** The user chose a slide (click, Enter or Space). Call `setSlide` yourself if you want the strip to follow. Not called by `setSlide`. */
24
+ onSelect?: (index: number) => void;
25
+ /** `"comfortable"` (default) or `"touch"`: larger gaps, 40px-high items and 104px thumbnails. */
26
+ density?: "comfortable" | "touch";
27
+ /** Thumbnail width in pixels. Default 128 (104 for touch). */
28
+ size?: number;
29
+ /** `"horizontal"` (default) scrolls sideways; `"vertical"` scrolls down. */
30
+ orientation?: "horizontal" | "vertical";
31
+ labels?: ThumbnailStripLabels;
32
+ /** A thumbnail cache to share with other views. Default: the strip's own. */
33
+ cache?: ThumbnailCache;
34
+ /** False to draw every thumbnail at once instead of those near the viewport. Default true; also off without IntersectionObserver. */
35
+ lazy?: boolean;
36
+ /** False to stop following the editor on its own; call `refresh()` yourself. Default true. */
37
+ autoRefresh?: boolean;
38
+ /** The document's faces could not be loaded, so no thumbnail was drawn. */
39
+ onError?: (error: unknown) => void;
40
+ }
41
+ export interface ThumbnailStrip {
42
+ readonly element: HTMLElement;
43
+ getSlide(): number;
44
+ /** Mark a slide as current (`aria-current`), scroll it into view and draw it. Does not call `onSelect`. */
45
+ setSlide(index: number): void;
46
+ /** Redraw from the session now. Only slides whose content changed render again. `{ clear: true }` also drops the cache, for example after the host loaded fonts. */
47
+ refresh(options?: { clear?: boolean }): void;
48
+ destroy(): void;
49
+ }
50
+ export declare function createThumbnailStrip(container: HTMLElement, options: ThumbnailStripOptions): ThumbnailStrip;
@@ -0,0 +1,310 @@
1
+ // Thumbnail strip (RR-98): a `nav` of real slide renders, each with a mono index and the slide title; the current one has `aria-current` and an
2
+ // accent ring. Thumbnails are drawn by the same renderer, catalogs and fonts handle the canvas uses, through the thumbnail cache: a slide whose
3
+ // content did not change is never rendered again, and the strip is lazy (items near the viewport render first, through IntersectionObserver when
4
+ // the browser has it). Plain DOM, no framework, no network.
5
+ import { mergeCatalogs } from "./index.js";
6
+ import { toSvg } from "@openpresentation/opf-render/svg";
7
+ import { whenFontsReady } from "./font-gate.js";
8
+ import { slideTitle } from "./slides.js";
9
+ import { createThumbnailCache } from "./thumbnail-cache.js";
10
+
11
+ const DEFAULT_LABELS = {
12
+ navigation: "Slides",
13
+ untitled: "Untitled",
14
+ hidden: "hidden",
15
+ unavailable: "Preview unavailable",
16
+ };
17
+ const pad = (value, count) => String(value).padStart(Math.max(2, String(count).length), "0");
18
+
19
+ /**
20
+ * Mount the thumbnail strip into `container`.
21
+ *
22
+ * Options: `editor` (required session), `fonts` (the renderer's fonts handle the canvas uses), `renderOptions` (extra `toSvg` options, such as
23
+ * `catalogs`; the thumbnails always draw with `text: "system"` and `trace: false`), `slideIndex`, `onSelect(index)` (the user chose a slide),
24
+ * `density` (`"comfortable"` or `"touch"`), `size` (thumbnail width in pixels; default 128, or 104 for touch), `orientation` (`"horizontal"`,
25
+ * the default, or `"vertical"`), `labels` (`navigation`, `untitled`, `hidden`, `unavailable`), `cache` (a thumbnail cache to share; default:
26
+ * its own), `lazy` (default true), `autoRefresh` (default true: follow the editor's changes on its own) and `onError(error)`.
27
+ *
28
+ * Returns `{ element, getSlide(), setSlide(index), refresh({ clear }), destroy() }`.
29
+ */
30
+ export function createThumbnailStrip(container, options = {}) {
31
+ if (!container?.ownerDocument) throw new TypeError("A DOM container is required.");
32
+ const { editor } = options;
33
+ if (!editor || typeof editor.subscribe !== "function") throw new TypeError("createThumbnailStrip needs an editor session.");
34
+ const doc = container.ownerDocument;
35
+ const win = doc.defaultView;
36
+ const labels = { ...DEFAULT_LABELS, ...(options.labels ?? {}) };
37
+ const density = options.density === "touch" ? "touch" : "comfortable";
38
+ const orientation = options.orientation === "vertical" ? "vertical" : "horizontal";
39
+ const size = Number(options.size) > 0 ? Number(options.size) : density === "touch" ? 104 : 128;
40
+ const lazy = options.lazy !== false && typeof win?.IntersectionObserver === "function";
41
+
42
+ // The renderer draws with the session's registered catalogs (plus any `renderOptions.catalogs`): the same list the canvas and core resolve with.
43
+ const resolveOptions = () => ({ variables: false, ...(options.renderOptions ?? {}), catalogs: mergeCatalogs(editor.catalogs, options.renderOptions?.catalogs) });
44
+ const cache = options.cache ?? createThumbnailCache({
45
+ toSvg,
46
+ getOptions: () => ({ ...resolveOptions(), fonts: options.fonts, text: "system", trace: false }),
47
+ });
48
+
49
+ let active = 0;
50
+ let focusIndex = 0;
51
+ let deck = editor.presentation;
52
+ let disposed = false;
53
+ let scheduled;
54
+ let renderToken = 0;
55
+ let observer;
56
+ const visible = new Set();
57
+ const items = [];
58
+
59
+ const element = doc.createElement("nav");
60
+ element.className = "opf-thumbs";
61
+ element.dataset.orientation = orientation;
62
+ element.dataset.density = density;
63
+ element.setAttribute("aria-label", labels.navigation);
64
+ element.style.setProperty("--opf-thumbs-w", `${size}px`);
65
+ const list = doc.createElement("ol");
66
+ list.className = "opf-thumbs-list";
67
+ element.append(list);
68
+ container.append(element);
69
+
70
+ const clamp = (value, total) => (total === 0 ? 0 : Math.max(0, Math.min(total - 1, value)));
71
+ active = clamp(Math.trunc(Number(options.slideIndex) || 0), deck.slides.length);
72
+ focusIndex = active;
73
+
74
+ function makeItem(index) {
75
+ const li = doc.createElement("li");
76
+ li.className = "opf-thumbs-slot";
77
+ const button = doc.createElement("button");
78
+ button.type = "button";
79
+ button.className = "opf-thumbs-item";
80
+ button.dataset.index = String(index);
81
+ const frame = doc.createElement("span");
82
+ frame.className = "opf-thumbs-frame";
83
+ frame.dataset.state = "pending";
84
+ frame.setAttribute("aria-hidden", "true");
85
+ const label = doc.createElement("span");
86
+ label.className = "opf-thumbs-label";
87
+ const indexNode = doc.createElement("span");
88
+ indexNode.className = "opf-thumbs-index";
89
+ const titleNode = doc.createElement("span");
90
+ titleNode.className = "opf-thumbs-title";
91
+ const note = doc.createElement("span");
92
+ note.className = "opf-thumbs-note";
93
+ label.append(indexNode, " ", titleNode, note);
94
+ button.append(frame, label);
95
+ li.append(button);
96
+ return { li, button, frame, indexNode, titleNode, note, index, html: undefined };
97
+ }
98
+
99
+ /** Grow or shrink to `count` slots, and write each slot's index, title and state. Cheap: no rendering. */
100
+ function syncItems() {
101
+ const slides = deck.slides;
102
+ while (items.length < slides.length) {
103
+ const item = makeItem(items.length);
104
+ items.push(item);
105
+ list.append(item.li);
106
+ observer?.observe(item.button);
107
+ }
108
+ while (items.length > slides.length) {
109
+ const item = items.pop();
110
+ observer?.unobserve(item.button);
111
+ visible.delete(item.index);
112
+ item.li.remove();
113
+ }
114
+ active = clamp(active, slides.length);
115
+ focusIndex = clamp(focusIndex, slides.length);
116
+ slides.forEach((slide, index) => {
117
+ const item = items[index];
118
+ const title = slideTitle(slide);
119
+ item.indexNode.textContent = pad(index + 1, slides.length);
120
+ item.titleNode.textContent = title || labels.untitled;
121
+ item.titleNode.dataset.untitled = String(!title);
122
+ item.note.textContent = slide.hidden === true ? ` ${labels.hidden}` : "";
123
+ item.button.dataset.hidden = String(slide.hidden === true);
124
+ });
125
+ paintActive();
126
+ }
127
+ function paintActive() {
128
+ items.forEach((item, index) => {
129
+ if (index === active) item.button.setAttribute("aria-current", "true");
130
+ else item.button.removeAttribute("aria-current");
131
+ item.button.tabIndex = index === focusIndex ? 0 : -1;
132
+ });
133
+ }
134
+
135
+ function setThumbnail(item, html) {
136
+ if (item.html === html) return;
137
+ item.html = html;
138
+ if (typeof html === "string" && /^\s*<svg[\s>]/.test(html)) {
139
+ item.frame.innerHTML = html;
140
+ item.frame.dataset.state = "ready";
141
+ item.frame.querySelectorAll("[tabindex]").forEach((node) => node.removeAttribute("tabindex"));
142
+ // Follow the deck's slide shape (16:9, 4:3, ...) once a real render shows it.
143
+ const box = item.frame.querySelector("svg")?.viewBox?.baseVal;
144
+ if (box?.width > 0 && box?.height > 0) element.style.setProperty("--opf-thumbs-ratio", `${box.width} / ${box.height}`);
145
+ } else {
146
+ item.frame.textContent = labels.unavailable;
147
+ item.frame.dataset.state = "error";
148
+ }
149
+ }
150
+ const markFrames = (state) => items.forEach((item) => { if (item.frame.dataset.state !== "ready") item.frame.dataset.state = state; });
151
+
152
+ /** The slots to draw now: the active one and every slot near the viewport (all of them without IntersectionObserver). */
153
+ function wanted() {
154
+ if (!lazy) return items.map((_, index) => index);
155
+ const set = new Set(visible);
156
+ if (items.length) set.add(active);
157
+ return [...set].filter((index) => index < items.length).sort((a, b) => a - b);
158
+ }
159
+ function draw() {
160
+ const indices = wanted();
161
+ if (!indices.length) return;
162
+ const snapshot = deck;
163
+ cache.prepare(snapshot, indices);
164
+ for (const index of indices) setThumbnail(items[index], cache.html(snapshot, index));
165
+ }
166
+ /** Gate on the fonts the document needs (the canvas gate), then draw. Unchanged slides come from the cache; nothing else is rendered. */
167
+ function paintThumbnails() {
168
+ const token = ++renderToken;
169
+ let loaded = false;
170
+ try {
171
+ return whenFontsReady(options.fonts, deck, {
172
+ renderOptions: resolveOptions(),
173
+ isCurrent: () => !disposed && token === renderToken,
174
+ loading: () => { loaded = true; markFrames("loading"); },
175
+ // Faces that loaded after thumbnails were drawn change how text measures: forget them, as the playground does.
176
+ ready: () => { if (loaded) cache.clear(); draw(); },
177
+ failed: (error) => { markFrames("error"); options.onError?.(error); },
178
+ });
179
+ } catch (error) {
180
+ options.onError?.(error);
181
+ }
182
+ }
183
+
184
+ function scrollToActive() {
185
+ const item = items[active]?.li;
186
+ if (!item || !element.clientWidth) return;
187
+ const horizontal = orientation === "horizontal";
188
+ const start = horizontal ? item.offsetLeft : item.offsetTop;
189
+ const length = horizontal ? item.offsetWidth : item.offsetHeight;
190
+ const view = horizontal ? element.clientWidth : element.clientHeight;
191
+ const current = horizontal ? element.scrollLeft : element.scrollTop;
192
+ const edge = 8;
193
+ let target = current;
194
+ if (start - edge < current) target = Math.max(0, start - edge);
195
+ else if (start + length + edge > current + view) target = start + length + edge - view;
196
+ if (target !== current) element[horizontal ? "scrollLeft" : "scrollTop"] = target;
197
+ }
198
+
199
+ function select(index) {
200
+ if (index !== active) {
201
+ active = index;
202
+ if (!element.contains(doc.activeElement)) focusIndex = index;
203
+ paintActive();
204
+ scrollToActive();
205
+ paintThumbnails();
206
+ }
207
+ options.onSelect?.(index);
208
+ }
209
+ list.addEventListener("click", (event) => {
210
+ const button = event.target.closest?.(".opf-thumbs-item");
211
+ if (button && list.contains(button)) select(Number(button.dataset.index));
212
+ });
213
+ list.addEventListener("focusin", (event) => {
214
+ const button = event.target.closest?.(".opf-thumbs-item");
215
+ if (!button) return;
216
+ focusIndex = Number(button.dataset.index);
217
+ paintActive();
218
+ });
219
+ // Arrow keys move focus along the strip (Enter or Space chooses); Home and End jump to the ends.
220
+ list.addEventListener("keydown", (event) => {
221
+ if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.isComposing) return;
222
+ const button = event.target.closest?.(".opf-thumbs-item");
223
+ if (!button) return;
224
+ const rtl = orientation === "horizontal" && win.getComputedStyle(element).direction === "rtl";
225
+ const keys = orientation === "horizontal" ? { ArrowLeft: rtl ? 1 : -1, ArrowRight: rtl ? -1 : 1 } : { ArrowUp: -1, ArrowDown: 1 };
226
+ const from = Number(button.dataset.index);
227
+ let target;
228
+ if (event.key in keys) target = from + keys[event.key];
229
+ else if (event.key === "Home") target = 0;
230
+ else if (event.key === "End") target = items.length - 1;
231
+ else return;
232
+ event.preventDefault();
233
+ target = clamp(target, items.length);
234
+ items[target]?.button.focus();
235
+ });
236
+
237
+ if (lazy) {
238
+ const margin = orientation === "horizontal" ? "0px 240px 0px 240px" : "240px 0px 240px 0px";
239
+ observer = new win.IntersectionObserver((entries) => {
240
+ let added = false;
241
+ for (const entry of entries) {
242
+ const index = Number(entry.target.dataset.index);
243
+ if (entry.isIntersecting) { if (!visible.has(index)) { visible.add(index); added = true; } }
244
+ else visible.delete(index);
245
+ }
246
+ if (added && !disposed) paintThumbnails();
247
+ }, { root: element, rootMargin: margin });
248
+ }
249
+
250
+ syncItems();
251
+ paintThumbnails();
252
+ scrollToActive();
253
+
254
+ function cancelScheduled() {
255
+ if (scheduled === undefined) return;
256
+ (win.cancelAnimationFrame ?? clearTimeout).call(win, scheduled);
257
+ scheduled = undefined;
258
+ }
259
+ function refresh({ clear = false } = {}) {
260
+ if (disposed) return;
261
+ cancelScheduled();
262
+ if (clear) cache.clear();
263
+ deck = editor.presentation;
264
+ const before = items.length;
265
+ syncItems();
266
+ paintThumbnails();
267
+ if (items.length !== before) scrollToActive();
268
+ }
269
+ // Several session events in one frame (typing, a patch with many operations) draw once.
270
+ const unsubscribe = options.autoRefresh === false ? undefined : editor.subscribe(() => {
271
+ if (scheduled !== undefined) return;
272
+ scheduled = (win.requestAnimationFrame ?? ((callback) => setTimeout(callback, 16))).call(win, () => { scheduled = undefined; refresh(); });
273
+ });
274
+
275
+ return {
276
+ element,
277
+ /** The slide shown as current. */
278
+ getSlide: () => active,
279
+ /** Mark another slide as current, scroll it into view and draw it. Does not call `onSelect`. */
280
+ setSlide(value) {
281
+ if (disposed) return;
282
+ // The host often calls this as soon as it added a slide, before the strip's own frame-delayed refresh has seen it.
283
+ const total = editor.snapshot?.().presentation.slides.length ?? items.length;
284
+ if (total !== items.length) {
285
+ active = clamp(Math.trunc(Number(value) || 0), total);
286
+ focusIndex = active;
287
+ refresh();
288
+ scrollToActive();
289
+ return;
290
+ }
291
+ const next = clamp(Math.trunc(Number(value) || 0), items.length);
292
+ if (next === active) { scrollToActive(); return; }
293
+ active = next;
294
+ if (!element.contains(doc.activeElement)) focusIndex = next;
295
+ paintActive();
296
+ scrollToActive();
297
+ paintThumbnails();
298
+ },
299
+ /** Redraw from the session now. `{ clear: true }` also drops the cache, for example after the host loaded fonts. */
300
+ refresh,
301
+ destroy() {
302
+ if (disposed) return;
303
+ disposed = true;
304
+ cancelScheduled();
305
+ unsubscribe?.();
306
+ observer?.disconnect();
307
+ element.remove();
308
+ },
309
+ };
310
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/opf-editor",
3
- "version": "0.24.0",
3
+ "version": "0.24.2",
4
4
  "description": "Embeddable local OPF editor primitives built on the deterministic renderer.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -38,6 +38,10 @@
38
38
  "default": "./dist/svelte.js"
39
39
  },
40
40
  "./package.json": "./package.json",
41
+ "./styles.css": {
42
+ "style": "./dist/styles.css",
43
+ "default": "./dist/styles.css"
44
+ },
41
45
  "./canvas": {
42
46
  "types": "./dist/canvas.d.ts",
43
47
  "default": "./dist/canvas.js"
@@ -221,6 +225,34 @@
221
225
  "./annotations": {
222
226
  "types": "./dist/annotations.d.ts",
223
227
  "default": "./dist/annotations.js"
228
+ },
229
+ "./source-pane": {
230
+ "types": "./dist/source-pane.d.ts",
231
+ "default": "./dist/source-pane.js"
232
+ },
233
+ "./export-menu": {
234
+ "types": "./dist/export-menu.d.ts",
235
+ "default": "./dist/export-menu.js"
236
+ },
237
+ "./agent-handoff": {
238
+ "types": "./dist/agent-handoff.d.ts",
239
+ "default": "./dist/agent-handoff.js"
240
+ },
241
+ "./agent-handoff-dialog": {
242
+ "types": "./dist/agent-handoff-dialog.d.ts",
243
+ "default": "./dist/agent-handoff-dialog.js"
244
+ },
245
+ "./slide-navigator": {
246
+ "types": "./dist/slide-navigator.d.ts",
247
+ "default": "./dist/slide-navigator.js"
248
+ },
249
+ "./thumbnail-strip": {
250
+ "types": "./dist/thumbnail-strip.d.ts",
251
+ "default": "./dist/thumbnail-strip.js"
252
+ },
253
+ "./thumbnail-cache": {
254
+ "types": "./dist/thumbnail-cache.d.ts",
255
+ "default": "./dist/thumbnail-cache.js"
224
256
  }
225
257
  },
226
258
  "types": "./dist/index.d.ts",
@@ -238,7 +270,7 @@
238
270
  "check:font-hotlinks": "node --test scripts/check-font-hotlinks.test.mjs && node scripts/check-font-hotlinks.mjs",
239
271
  "check:font-hotlinks:built": "node scripts/check-font-hotlinks.mjs --built dist artifacts/playground",
240
272
  "build:playground": "node scripts/build-playground.mjs",
241
- "test:playground": "npm run build:playground && npm run check:font-hotlinks:built && node test/playground-pptx.mjs && node test/playground-script-fonts.mjs artifacts/playground-script-fonts && node test/playground-lazy-fonts.mjs artifacts/playground-lazy-fonts && node test/playground-base-fonts.mjs artifacts/playground-base-fonts && node test/playground-source.mjs && node test/design-controls-browser.mjs && node test/design-gaps-browser.mjs && node test/chart-options-browser.mjs && node test/persistence-browser.mjs && node test/slides-browser.mjs && node test/layout-templates-browser.mjs && node test/template-panel-browser.mjs && node test/numbering-panel-browser.mjs && node test/playground-ensure-fonts.mjs artifacts/playground-ensure-fonts && node test/canvas-fonts-browser.mjs artifacts/canvas-fonts && node test/find-replace-browser.mjs && node test/image-crop-browser.mjs && node test/mobile-browser.mjs && node test/review-panel-browser.mjs && node test/review-hook-browser.mjs && node test/playground-download.mjs artifacts/playground-download",
273
+ "test:playground": "npm run build:playground && npm run check:font-hotlinks:built && node test/playground-pptx.mjs && node test/playground-script-fonts.mjs artifacts/playground-script-fonts && node test/playground-lazy-fonts.mjs artifacts/playground-lazy-fonts && node test/playground-base-fonts.mjs artifacts/playground-base-fonts && node test/playground-source.mjs && node test/design-controls-browser.mjs && node test/design-gaps-browser.mjs && node test/chart-options-browser.mjs && node test/persistence-browser.mjs && node test/slides-browser.mjs && node test/slide-navigator-browser.mjs && node test/layout-templates-browser.mjs && node test/template-panel-browser.mjs && node test/numbering-panel-browser.mjs && node test/playground-ensure-fonts.mjs artifacts/playground-ensure-fonts && node test/canvas-fonts-browser.mjs artifacts/canvas-fonts && node test/find-replace-browser.mjs && node test/image-crop-browser.mjs && node test/mobile-browser.mjs && node test/review-panel-browser.mjs && node test/review-hook-browser.mjs && node test/source-pane-browser.mjs && node test/playground-download.mjs artifacts/playground-download && node test/export-menu-browser.mjs && node test/agent-handoff-browser.mjs",
242
274
  "test:code-browser": "node test/code-browser.mjs",
243
275
  "test:selection-browser": "node test/selection-browser.mjs",
244
276
  "test:click-entry-browser": "node test/click-entry-browser.mjs",
@@ -254,31 +286,36 @@
254
286
  "test:data-grid-datasets-browser": "node test/data-grid-datasets-browser.mjs",
255
287
  "test:data-import-dataset-browser": "node test/data-import-dataset-browser.mjs",
256
288
  "test:slides-browser": "node test/slides-browser.mjs",
289
+ "test:slide-navigator-browser": "node test/slide-navigator-browser.mjs",
257
290
  "test:persistence-browser": "node test/persistence-browser.mjs",
258
291
  "test:find-browser": "node test/find-replace-browser.mjs",
259
292
  "test:crop-browser": "node test/image-crop-browser.mjs",
260
293
  "test:mobile-browser": "node test/mobile-browser.mjs",
261
294
  "test:review-panel-browser": "node test/review-panel-browser.mjs",
262
295
  "test:review-hook-browser": "node test/review-hook-browser.mjs",
296
+ "test:source-pane-browser": "node test/source-pane-browser.mjs",
263
297
  "test:export-license-browser": "node test/export-license-browser.mjs",
264
298
  "test:chart-options-browser": "node test/chart-options-browser.mjs",
265
- "test:numbering-panel-browser": "node test/numbering-panel-browser.mjs"
299
+ "test:numbering-panel-browser": "node test/numbering-panel-browser.mjs",
300
+ "test:export-menu-browser": "node test/export-menu-browser.mjs",
301
+ "test:agent-handoff-browser": "node test/agent-handoff-browser.mjs"
266
302
  },
267
303
  "dependencies": {
268
304
  "@codemirror/autocomplete": "6.20.3",
269
305
  "@codemirror/commands": "6.11.1",
270
306
  "@codemirror/lang-json": "6.0.2",
307
+ "@codemirror/lang-yaml": "6.1.3",
271
308
  "@codemirror/language": "6.13.1",
272
309
  "@codemirror/lint": "6.9.7",
273
310
  "@codemirror/search": "6.7.2",
274
311
  "@codemirror/state": "6.7.6",
275
312
  "@codemirror/view": "6.43.14",
276
313
  "@lezer/highlight": "1.2.5",
277
- "@openpresentation/opf": "^0.24.0",
314
+ "@openpresentation/opf": "^0.24.2",
278
315
  "jsonc-parser": "3.3.1"
279
316
  },
280
317
  "peerDependencies": {
281
- "@openpresentation/opf-render": "^0.24.1"
318
+ "@openpresentation/opf-render": "^0.24.3"
282
319
  },
283
320
  "peerDependenciesMeta": {
284
321
  "@openpresentation/opf-render": {
@@ -331,8 +368,8 @@
331
368
  "@expo-google-fonts/roboto-mono": "0.4.2",
332
369
  "@expo-google-fonts/stix-two-math": "0.4.0",
333
370
  "@expo-google-fonts/tinos": "0.4.2",
334
- "@openpresentation/opf-pptx": "^0.24.0",
335
- "@openpresentation/opf-render": "^0.24.1",
371
+ "@openpresentation/opf-pptx": "^0.24.1",
372
+ "@openpresentation/opf-render": "^0.24.3",
336
373
  "@resvg/resvg-js": "2.6.2",
337
374
  "axe-core": "4.14.0",
338
375
  "esbuild": "0.28.2",