Document model and editing
Understand v2 documents, atomic commands, undo/redo, brands and customer-edition merges.
reopt designUpdated
1. DeckDocument v2
A document has version=2, id, revision, width, height and title. slideOrder controls order, while slides and nodes live in ID-indexed maps. Viewport zoom is separate from document coordinates.
- slides / slideOrder
- Slide backgrounds, notes, transitions, animations, child IDs and display order.
- nodes
- Text, shape, path, image, group, chart, table, media and custom scene nodes with extension JSON and snapshots.
- masters
- Master slides referenced by other slides.
- assets
- Media, vector and font sources with URLs, sizes and embedding metadata.
- brand
- Colors, fonts, text styles, spacing, chart and motion rules.
- derivation
- Source ID, revision and baseline snapshot for customer editions.
| Item | Behavior and usage |
|---|---|
| slides / slideOrder | Slide backgrounds, notes, transitions, animations, child IDs and display order. |
| nodes | Text, shape, path, image, group, chart, table, media and custom scene nodes with extension JSON and snapshots. |
| masters | Master slides referenced by other slides. |
| assets | Media, vector and font sources with URLs, sizes and embedding metadata. |
| brand | Colors, fonts, text styles, spacing, chart and motion rules. |
| derivation | Source ID, revision and baseline snapshot for customer editions. |
Custom elements: charts and data tables
Register DeckExtension objects in extensions to enable the Custom elements menu. optChartsExtension() supports all 31 data visualizations and four chart shells, with type search, tabular data editing, matrix/tree/graph JSON configuration and per-type validation. Existing bar/line/area charts remain compatible and can upgrade to advanced settings. Apply stores original data together with a PNG including HTML tables and legends. PDF and PowerPoint use static chart images.
optDataGridExtension() uses the actual DataGrid for cell editing, row inclusion/highlighting and column visibility. Paste tab-separated spreadsheet data with headers and apply number, percent or currency formats per column. Store up to 500 rows and 24 columns; only the selected page is inserted. Long content scales to fit, so fewer rows and columns improve readability. All source data stays editable; duplicate the element to show another page. PDF/PPTX use static images.
JSON and .deck files preserve source data. Register the same extension ID and version to reopen Edit data & style. Canvas, presentation and export display the snapshot even without the extension. Presentations are static; custom elements become images in PDF/PPTX. Implement createData, parseData, Preview, Inspector and snapshot for your own extension. The host handles version migrations.
"use client";
// Install @reopt-ai/opt-charts and @reopt-ai/opt-datagrid alongside opt-deck.
import { DeckEditor } from "@reopt-ai/opt-deck";
import { optChartsExtension } from "@reopt-ai/opt-deck/opt-charts";
import { optDataGridExtension } from "@reopt-ai/opt-deck/opt-datagrid";
import "@reopt-ai/opt-deck/styles.css";
const extensions = [optChartsExtension(), optDataGridExtension()];
export function ChartPresentation() {
return <DeckEditor extensions={extensions} locale="ko" />;
}2. Atomic transactions
Group related edits in one SceneTransaction. A stale baseRevision or invalid node reference/hierarchy rejects the transaction. applySceneTransaction returns a validated new document without mutating its input.
The pure function does not keep undo history. Use DeckStore.dispatch or useDeck.dispatch when history is required, and give the transaction a meaningful label.
import {
createDeckDocument,
createNode,
applySceneTransaction,
} from "@reopt-ai/opt-deck/server";
const document = createDeckDocument({ design: "product", title: "Launch" });
const slideId = document.slideOrder[0];
const node = createNode(document, "text", "launch-heading");
const next = applySceneTransaction(document, {
baseRevision: document.revision,
label: "Add heading",
operations: [
{ type: "insert-node", parentId: slideId, node },
{ type: "update-node", id: node.id, patch: { x: 120, y: 100 } },
],
});
// next is a new validated document; document is unchanged.
export { next };3. useDeck and history
useDeck owns an independent store for custom command UI. getSnapshot returns the latest document; document is its reactive render snapshot. Revisions increase through undo/redo, so they are not permanent identifiers for earlier content.
DeckEditor manages its own store. Choose whether history belongs to a custom useDeck controller, as below, or to the default editor. External value replacement resets the editor history.
"use client";
import { useDeck } from "@reopt-ai/opt-deck";
import { DeckViewer } from "@reopt-ai/opt-deck/viewer";
export function HostControlledPresentation() {
const deck = useDeck({ historyLimit: 100 });
function rename() {
const current = deck.getSnapshot();
deck.dispatch({
baseRevision: current.revision,
label: "Rename slide",
operations: [{
type: "update-slide",
id: current.slideOrder[0],
patch: { name: "Customer introduction" },
}],
});
}
return (
<div>
<button onClick={rename}>Rename first slide</button>
<button onClick={deck.undo} disabled={!deck.canUndo}>Undo</button>
<DeckViewer document={deck.document} />
</div>
);
}4. Interaction and shortcuts
Select canvas objects to move, resize and rotate; hold Shift for multiple selection. Double-click text, or press Enter after selecting it, to edit directly. While typing, input behavior takes priority over canvas shortcuts. Focus the canvas and press Shift+F10 or the menu key to open its actions. Pickers and menus support arrow keys, Enter and Escape. Numeric drafts commit on Enter or blur as one undoable change; Escape cancels the draft. Enter in a numeric field does not submit an embedding host form.
- ⌘ / Ctrl + Z · Shift + Z
- Undo / redo.
- ⌘ / Ctrl + D · G · Shift + G
- Duplicate / group / ungroup.
- ⌘ / Ctrl + C · X · V
- Copy / cut / paste selected elements.
- ⌘ / Ctrl + K
- Open command search.
- V · H · T · R · P
- Selection / hand / text / shape / vector tools.
- Arrow · Shift + Arrow
- Move selection by 1 / 10 document units.
- Space + drag · 1
- Pan the canvas / fit to screen.
| Item | Behavior and usage |
|---|---|
| ⌘ / Ctrl + Z · Shift + Z | Undo / redo. |
| ⌘ / Ctrl + D · G · Shift + G | Duplicate / group / ungroup. |
| ⌘ / Ctrl + C · X · V | Copy / cut / paste selected elements. |
| ⌘ / Ctrl + K | Open command search. |
| V · H · T · R · P | Selection / hand / text / shape / vector tools. |
| Arrow · Shift + Arrow | Move selection by 1 / 10 document units. |
| Space + drag · 1 | Pan the canvas / fit to screen. |
5. Brands, masters and reusable components
Brand color and font changes update connected styles. Masters provide shared slide elements; reusable components maintain a source and instances. Pull source changes while preserving local overrides, or detach an instance.
Inspect family, weight, style and embedding permissions in font management. Missing fonts and substitution in receiving apps can change exported layout.
6. Compare and merge customer editions
createDeckVariant creates a separate document with its source baseline. compareVariantSource reports additions, removals, changes and conflicts. Pass only user-approved changes to mergeVariantSource.
Even nonconflicting changes may require related parent/child updates. Structurally incomplete sets fail atomically; show the error and adjust the accepted set. The host owns source lookup and persistence.
import {
createDeckDocument, createDeckVariant,
compareVariantSource, mergeVariantSource, applySceneTransaction,
} from "@reopt-ai/opt-deck/server";
const source = createDeckDocument({ title: "Company introduction" });
const customer = createDeckVariant(source, "customer-a", "Customer A");
const updatedSource = applySceneTransaction(source, {
baseRevision: source.revision,
label: "Update source notes",
operations: [{ type: "update-slide", id: source.slideOrder[0],
patch: { notes: "Updated presenter guidance" } }],
});
const changes = compareVariantSource(customer, updatedSource);
// Present changes to the user before choosing the accepted set.
const accepted = changes.filter((change) => !change.conflict);
const merged = mergeVariantSource(customer, updatedSource, accepted);
export { changes, merged };