Editing
Move, resize, and edit text in presentations.
Editing requires two things: loading the presentation with readOnly={false}, and placing Presentation.Selection inside Presentation.Slide.
<Presentation.Root file={file} readOnly={false}>
<Presentation.Viewport autoFit>
<Presentation.Slide>
<Presentation.Selection />
</Presentation.Slide>
</Presentation.Viewport>
</Presentation.Root>What you can do
- Move shapes by dragging them
- Resize shapes by dragging their handles (hold Shift on a corner handle to lock aspect ratio)
- Edit text inline: single-click a text box, double-click or start typing on any shape
- Multi-select with Shift/Ctrl+click, Ctrl+A, or drag a marquee on empty canvas
- Undo / redo with Ctrl+Z / Ctrl+Shift+Z (or Ctrl+Y)
- Delete selected shapes with Delete or Backspace
- Nudge with arrow keys (1 px; Shift for 10 px)
Handling edit events
Each edit callback receives (status, error?) for undo/redo (where status is "success" | "empty") and (nodeId, error?) for node events. Check error to show a toast or log failures:
<Presentation.Selection
onUndo={(status) => {
if (status === "empty") toast.error("Nothing to undo");
}}
onRedo={(status, error) => {
if (error) toast.error(error instanceof Error ? error.message : "Redo failed");
else if (status === "empty") toast.error("Nothing to redo");
}}
onNodeTransform={(nodeId, error) => {
if (error) toast.error("Could not move shape");
}}
onNodeDelete={(nodeId, error) => {
if (error) toast.error("Could not delete shape");
}}
onTextChange={(nodeId, error) => {
if (error) toast.error("Text edit failed");
}}
/>Programmatic edits
Use store.edit() to apply changes from outside the UI (for example, from a toolbar button). Edit operations have full undo/redo support.
import { useCreatePresentationStore } from "@diceui/pptx";
const store = useCreatePresentationStore();
// Change a shape's fill color
await store.edit({
type: "setSolidFill",
slideId,
nodeId,
color: "#ef4444",
});
// Move a shape
await store.edit({
type: "setNodeTransform",
slideId,
nodeId,
x: 100,
y: 200,
width: 400,
height: 300,
});
// Edit text
await store.edit({
type: "setTextBody",
slideId,
nodeId,
paragraphs: [
{
runs: [{ text: "Hello world" }],
},
],
});Batch operations
Group multiple edits into a single undoable step with "batch":
await store.edit({
type: "batch",
ops: [
{ type: "deleteNode", slideId, nodeId: "2" },
{ type: "deleteNode", slideId, nodeId: "3" },
],
});Saving
Call store.save() to get the edited presentation as a Uint8Array, then trigger a download:
async function onSave() {
const bytes = await store.save();
const blob = new Blob([bytes], {
type: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "edited.pptx";
a.click();
URL.revokeObjectURL(url);
}The saved file round-trips all untouched parts byte-for-byte. Only slides that were edited are re-serialized.
Undo / redo stack
The store maintains a linear undo/redo history:
store.canUndo(); // true if there is something to undo
store.canRedo(); // true if there is something to redo
store.undo(); // reverts the last edit; navigates to the affected slide
store.redo(); // re-applies the last undone editCross-slide edits navigate to the affected slide automatically on undo/redo.