@openpresentation/opf-editor 0.24.3 → 0.25.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/opf-editor",
3
- "version": "0.24.3",
3
+ "version": "0.25.1",
4
4
  "description": "Embeddable local OPF editor primitives built on the deterministic renderer.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -190,9 +190,13 @@
190
190
  "types": "./dist/layout-templates.d.ts",
191
191
  "default": "./dist/layout-templates.js"
192
192
  },
193
- "./custom-layout-editor": {
194
- "types": "./dist/custom-layout-editor.d.ts",
195
- "default": "./dist/custom-layout-editor.js"
193
+ "./layout-editor": {
194
+ "types": "./dist/layout-editor.d.ts",
195
+ "default": "./dist/layout-editor.js"
196
+ },
197
+ "./layout-tree": {
198
+ "types": "./dist/layout-tree.d.ts",
199
+ "default": "./dist/layout-tree.js"
196
200
  },
197
201
  "./outline-view": {
198
202
  "types": "./dist/outline-view.d.ts",
@@ -278,7 +282,7 @@
278
282
  "check:font-hotlinks": "node --test scripts/check-font-hotlinks.test.mjs && node scripts/check-font-hotlinks.mjs",
279
283
  "check:font-hotlinks:built": "node scripts/check-font-hotlinks.mjs --built dist artifacts/playground",
280
284
  "build:playground": "node scripts/build-playground.mjs",
281
- "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 && node test/presenter-browser.mjs && node test/present-button-browser.mjs",
285
+ "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/layout-editor-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 && node test/presenter-browser.mjs && node test/present-button-browser.mjs",
282
286
  "test:code-browser": "node test/code-browser.mjs",
283
287
  "test:selection-browser": "node test/selection-browser.mjs",
284
288
  "test:click-entry-browser": "node test/click-entry-browser.mjs",
@@ -302,6 +306,7 @@
302
306
  "test:review-panel-browser": "node test/review-panel-browser.mjs",
303
307
  "test:review-hook-browser": "node test/review-hook-browser.mjs",
304
308
  "test:source-pane-browser": "node test/source-pane-browser.mjs",
309
+ "test:layout-editor-browser": "node test/layout-editor-browser.mjs",
305
310
  "test:export-license-browser": "node test/export-license-browser.mjs",
306
311
  "test:chart-options-browser": "node test/chart-options-browser.mjs",
307
312
  "test:numbering-panel-browser": "node test/numbering-panel-browser.mjs",
@@ -321,11 +326,11 @@
321
326
  "@codemirror/state": "6.7.6",
322
327
  "@codemirror/view": "6.43.14",
323
328
  "@lezer/highlight": "1.2.5",
324
- "@openpresentation/opf": "^0.24.2",
329
+ "@openpresentation/opf": "^0.25.0",
325
330
  "jsonc-parser": "3.3.1"
326
331
  },
327
332
  "peerDependencies": {
328
- "@openpresentation/opf-render": "^0.24.3"
333
+ "@openpresentation/opf-render": "^0.25.0"
329
334
  },
330
335
  "peerDependenciesMeta": {
331
336
  "@openpresentation/opf-render": {
@@ -333,7 +338,7 @@
333
338
  }
334
339
  },
335
340
  "devDependencies": {
336
- "@openpresentation/gallery": "^4.0.0",
341
+ "@openpresentation/gallery": "^5.0.0",
337
342
  "@expo-google-fonts/arimo": "0.4.3",
338
343
  "@expo-google-fonts/caladea": "0.4.2",
339
344
  "@expo-google-fonts/cousine": "0.4.3",
@@ -378,8 +383,8 @@
378
383
  "@expo-google-fonts/roboto-mono": "0.4.2",
379
384
  "@expo-google-fonts/stix-two-math": "0.4.0",
380
385
  "@expo-google-fonts/tinos": "0.4.2",
381
- "@openpresentation/opf-pptx": "^0.24.1",
382
- "@openpresentation/opf-render": "^0.24.3",
386
+ "@openpresentation/opf-pptx": "^0.25.0",
387
+ "@openpresentation/opf-render": "^0.25.0",
383
388
  "@resvg/resvg-js": "2.6.2",
384
389
  "axe-core": "4.14.0",
385
390
  "esbuild": "0.28.2",
@@ -1,7 +0,0 @@
1
- import type { EditorSession } from "./index.js";
2
-
3
- export declare function openCustomLayoutEditor(editor: EditorSession, options?: {
4
- document?: Document;
5
- onSave?: (layout: { id: string; record: Record<string, unknown> }) => void;
6
- onClose?: () => void;
7
- }): { dialog: HTMLDialogElement; close(): void };
@@ -1,105 +0,0 @@
1
- // A small, local grid authoring dialog for OPF 0.19 layout templates.
2
- import { LAYOUT_REGION_FLOWS, LAYOUT_REGION_KINDS, LAYOUT_REGION_ROLES, layoutFromGrid, saveCustomLayout } from "./layout-templates.js";
3
-
4
- const element = (doc, tag, text) => { const node = doc.createElement(tag); if (text !== undefined) node.textContent = text; return node; };
5
- const field = (doc, title, input) => { const label = element(doc, "label", title); label.className = "opf-grid-field"; label.append(input); return label; };
6
- const select = (doc, values, current) => {
7
- const input = element(doc, "select");
8
- for (const value of values) { const option = element(doc, "option", value); option.value = value; input.append(option); }
9
- input.value = current;
10
- return input;
11
- };
12
-
13
- /** Draw rectangular areas, set each region's accepted kinds, role and flow, then save one undoable custom record. */
14
- export function openCustomLayoutEditor(editor, options = {}) {
15
- const doc = options.document ?? globalThis.document;
16
- const dialog = element(doc, "dialog");
17
- dialog.className = "opf-sm-dialog opf-grid-dialog";
18
- const heading = element(doc, "h2", "Create a custom layout");
19
- heading.id = "opf-grid-title";
20
- dialog.setAttribute("aria-labelledby", heading.id);
21
- const error = element(doc, "p"); error.setAttribute("role", "alert"); error.className = "opf-sm-error";
22
- const id = element(doc, "input"); id.placeholder = "roadmap-split"; id.pattern = "[a-z][a-z0-9-]*"; id.required = true;
23
- const name = element(doc, "input"); name.placeholder = "Roadmap split"; name.required = true;
24
- const summary = element(doc, "input"); summary.placeholder = "When to use this layout";
25
- const width = element(doc, "input"); width.type = "number"; width.min = "1"; width.max = "12"; width.value = "3";
26
- const height = element(doc, "input"); height.type = "number"; height.min = "1"; height.max = "12"; height.value = "3";
27
- const paint = element(doc, "input"); paint.value = "content"; paint.placeholder = "content or . to erase";
28
- const grid = element(doc, "div"); grid.className = "opf-custom-layout-grid"; grid.setAttribute("aria-label", "Layout cells");
29
- const regionsPanel = element(doc, "div"); regionsPanel.className = "opf-grid-regions";
30
- const hint = element(doc, "p", "Choose an area name, then drag across cells to draw a rectangle. Use title for the heading and . to clear cells."); hint.className = "field-help";
31
- const body = element(doc, "div"); body.className = "opf-grid-body";
32
- body.append(field(doc, "Layout ID", id), field(doc, "Name", name), field(doc, "Summary", summary));
33
- const dimensions = element(doc, "div"); dimensions.className = "opf-grid-dimensions";
34
- dimensions.append(field(doc, "Columns", width), field(doc, "Rows", height), field(doc, "Paint area", paint));
35
- body.append(dimensions, hint, grid, regionsPanel, error);
36
- const cancel = element(doc, "button", "Cancel"); cancel.type = "button"; cancel.className = "secondary";
37
- const save = element(doc, "button", "Save layout"); save.type = "submit"; save.className = "primary";
38
- const actions = element(doc, "div"); actions.className = "opf-sm-actions"; actions.append(cancel, save);
39
- const form = element(doc, "form"); form.append(heading, body, actions); dialog.append(form);
40
- let cells = [["title", "title", "title"], ["content", "content", "content"], ["content", "content", "content"]];
41
- const settings = new Map();
42
- let down = null;
43
- const opener = doc.activeElement;
44
- const regionNames = () => [...new Set(cells.flat())].filter((value) => value !== "." && value !== "title" && value !== "subtitle");
45
- function resize() {
46
- const cols = Number(width.value), rows = Number(height.value);
47
- if (!Number.isInteger(cols) || !Number.isInteger(rows) || cols < 1 || cols > 12 || rows < 1 || rows > 12) return;
48
- cells = Array.from({ length: rows }, (_, y) => Array.from({ length: cols }, (_, x) => cells[y]?.[x] ?? "."));
49
- render();
50
- }
51
- function draw(from, to) {
52
- const value = paint.value.trim() || ".";
53
- if (value !== "." && !/^[a-z][a-z0-9-]*$/.test(value)) { error.textContent = "Use lowercase area names with letters, digits and hyphens."; return; }
54
- for (let y = Math.min(from.y, to.y); y <= Math.max(from.y, to.y); y++)
55
- for (let x = Math.min(from.x, to.x); x <= Math.max(from.x, to.x); x++) cells[y][x] = value;
56
- error.textContent = ""; render();
57
- grid.querySelector(`[data-x="${to.x}"][data-y="${to.y}"]`)?.focus();
58
- }
59
- function render() {
60
- grid.replaceChildren(); grid.style.gridTemplateColumns = `repeat(${cells[0].length}, minmax(38px, 1fr))`;
61
- cells.forEach((row, y) => row.forEach((area, x) => {
62
- const cell = element(doc, "button", area); cell.type = "button"; cell.className = "opf-grid-cell"; cell.dataset.area = area;
63
- cell.setAttribute("aria-label", `Row ${y + 1}, column ${x + 1}: ${area === "." ? "empty" : area}`);
64
- cell.onpointerdown = (event) => { if (event.button !== 0) return; down = { x, y }; cell.setPointerCapture?.(event.pointerId); };
65
- cell.onpointerup = (event) => { if (!down) return; const target = doc.elementFromPoint?.(event.clientX, event.clientY)?.closest?.(".opf-grid-cell"); const last = target ? { x: Number(target.dataset.x), y: Number(target.dataset.y) } : { x, y }; draw(down, last); down = null; };
66
- cell.dataset.x = String(x); cell.dataset.y = String(y);
67
- cell.onkeydown = (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); draw({ x, y }, { x, y }); } };
68
- grid.append(cell);
69
- }));
70
- regionsPanel.replaceChildren();
71
- for (const area of regionNames()) {
72
- const setting = settings.get(area) ?? { accepts: new Set(["text"]), role: "primary", flow: "auto" };
73
- settings.set(area, setting);
74
- const section = element(doc, "fieldset"); section.className = "opf-grid-region";
75
- section.append(element(doc, "legend", area));
76
- const kinds = element(doc, "div"); kinds.className = "opf-grid-kinds";
77
- for (const kind of LAYOUT_REGION_KINDS) {
78
- const check = element(doc, "input"); check.type = "checkbox"; check.value = kind; check.checked = setting.accepts.has(kind);
79
- check.onchange = () => { if (check.checked) setting.accepts.add(kind); else setting.accepts.delete(kind); };
80
- const label = element(doc, "label", kind); label.prepend(check); kinds.append(label);
81
- }
82
- const role = select(doc, LAYOUT_REGION_ROLES, setting.role); role.onchange = () => { setting.role = role.value; };
83
- const flow = select(doc, LAYOUT_REGION_FLOWS, setting.flow); flow.onchange = () => { setting.flow = flow.value; };
84
- section.append(element(doc, "span", "Accepts"), kinds, field(doc, "Role", role), field(doc, "Flow", flow)); regionsPanel.append(section);
85
- }
86
- }
87
- width.onchange = resize; height.onchange = resize;
88
- cancel.onclick = () => dialog.close();
89
- form.onsubmit = (event) => {
90
- event.preventDefault();
91
- try {
92
- const regions = Object.fromEntries(regionNames().map((area) => {
93
- const setting = settings.get(area);
94
- return [area, { accepts: [...setting.accepts], role: setting.role, flow: setting.flow }];
95
- }));
96
- const layout = layoutFromGrid({ id: id.value.trim(), name: name.value, summary: summary.value, cells, regions });
97
- saveCustomLayout(editor, layout);
98
- options.onSave?.(layout);
99
- dialog.close();
100
- } catch (cause) { error.textContent = cause?.issues?.[0]?.message ?? cause.message; }
101
- };
102
- dialog.onclose = () => { dialog.remove(); opener?.focus?.(); options.onClose?.(); };
103
- doc.body.append(dialog); render(); dialog.showModal(); id.focus();
104
- return { dialog, close: () => dialog.close() };
105
- }