Store
Presentation store API reference.
The store is the single source of truth for all presentation state: loading, navigation, zoom, edits, and undo history. It's compatible with React's useSyncExternalStore.
Creating a store
Uncontrolled (default)
Presentation.Root creates an internal store automatically. This is the simplest setup:
<Presentation.Root file={file}>
{/* descendants can use usePresentation(), useSlide(), useZoom(), useHistory() */}
</Presentation.Root>Descendants can still drive that internal store: usePresentationStore() returns it, so a toolbar can call edit(), undo(), or save() without you owning the instance.
import { usePresentationStore } from "@diceui/pptx";
function SaveButton() {
const store = usePresentationStore();
return <button onClick={() => store.save()}>Save</button>;
}It returns a stable reference and subscribes to nothing, so use usePresentation, useSlide, useZoom, or useHistory for reactive reads rather than store.getState().
Controlled
Use useCreatePresentationStore when the store has to exist before the tree does, for example to load a file from an event handler that lives outside it:
import { useCreatePresentationStore } from "@diceui/pptx";
const store = useCreatePresentationStore();
// then pass it to the provider
<Presentation.Provider store={store}>
<Presentation.Root>...</Presentation.Root>
</Presentation.Provider>;State
interface PresentationState {
status: "idle" | "loading" | "ready" | "error";
presentation: PresentationData | null;
activeSlideId: string | null;
zoom: number; // default: 1
isAutoFit: boolean; // whether zoom tracks the viewport size
progress: number; // 0–100, meaningful while status === "loading"
error: Error | null;
revision: number; // bumped on every edit/undo/redo
}revision is important: presentation is mutated in place by edits, so its object identity never changes. Subscribe to revision to react to content changes.
Methods
load(input, options?)
store.load(file, {
defaultSlideIndex?: number | ((slides) => number), // default: 0
defaultZoom?: number, // initial zoom; auto-fit overrides it on mount
readOnly?: boolean, // default: true
lazy?: boolean, // default: true (parse slides on demand)
embedFonts?: boolean, // default: true (decode embedded fonts before ready)
})Parses a File, Blob, ArrayBuffer, or Uint8Array. Returns a promise that resolves to PresentationData. A superseded load rejects with DOMException("AbortError") (safe to ignore).
reset()
Clears the store back to the initial idle state and aborts any in-flight load.
Navigation
store.goTo(slideId: string) // by stable slide id
store.goToIndex(index: number) // 0-based, clamped to valid range
store.next()
store.prev()
store.getActiveSlide() // SlideData | null
store.canGoNext()
store.canGoPrev()Zoom
store.setZoom(n: number) // clamped to [0.1, 4]; releases auto-fit
store.zoomIn(step?: number) // default step: 0.25; releases auto-fit
store.zoomOut(step?: number)
store.setAutoFit(isAutoFit: boolean) // fit now and on every resize
store.fitTo(width, height, padding?) // one-shot fit for a known boxMeasuring is a viewport concern, deciding is the store's. state.isAutoFit says whether zoom is tracking the container, and a mounted <Presentation.Viewport autoFit> refits itself whenever it is on. setZoom, zoomIn, and zoomOut turn it off so the level someone picked survives the next resize; setAutoFit(true) turns it back on and refits immediately. fitTo is the escape hatch for a box you measured yourself, and leaves the mode alone.
isAutoFit describes the viewport rather than the deck, so it survives load() and reset().
The zoomChange event reports what produced the change, which is how a control tells a resize-driven fit apart from a level someone asked for:
store.on("zoomChange", ({ zoom, previousZoom, reason }) => {
// reason: "zoom" | "fit" | "load" | "reset"
});Editing
Requires the deck to have been loaded with { readOnly: false }.
await store.edit(op: EditOperation): Promise<EditResult>
store.undo(): boolean
await store.redo(): Promise<boolean>
store.canUndo(): boolean
store.canRedo(): boolean
store.isDirty(): boolean
await store.save(options?): Promise<Uint8Array>
store.getSlideRevision(slideId: string): numberEvents
store.on(event, handler) is the low-level channel the Root/Viewport callback props wrap. It returns an unsubscribe function.
store.on("slideChange", ({ slideId, index, reason }) => {});
store.on("statusChange", ({ status, previousStatus }) => {});
store.on("edit", ({ operation, source }) => {});
store.on("historyChange", ({ canUndo, canRedo, isDirty }) => {});
store.on("zoomChange", ({ zoom, previousZoom, reason }) => {});Edit operations
All operations accept slideId and nodeId (both strings).
setTextRun
Replace a single run's text content. Preserves all other run formatting.
await store.edit({
type: "setTextRun",
slideId,
nodeId,
paragraphIndex: 0,
runIndex: 0,
text: "New text",
});setTextBody
Replace all paragraphs and runs in a shape.
await store.edit({
type: "setTextBody",
slideId,
nodeId,
paragraphs: [
{
runs: [{ text: "Line one" }],
},
{
runs: [{ text: "Line two" }],
},
],
});setNodeTransform
Move or resize a shape. All values are in slide pixels.
await store.edit({
type: "setNodeTransform",
slideId,
nodeId,
x: 100,
y: 50,
width: 400,
height: 200,
rotation: 0, // degrees, optional
});setSolidFill
Change a shape's solid fill color.
await store.edit({
type: "setSolidFill",
slideId,
nodeId,
color: "#3b82f6", // hex
});deleteNode
Remove a shape from a slide.
await store.edit({ type: "deleteNode", slideId, nodeId });moveSlide
Reorder slides.
await store.edit({ type: "moveSlide", slideId, toIndex: 2 });duplicateSlide
Clone a slide. The duplicate is inserted immediately after the source.
await store.edit({ type: "duplicateSlide", slideId });deleteSlide
Remove a slide.
await store.edit({ type: "deleteSlide", slideId });batch
Group multiple operations into a single undoable step.
await store.edit({
type: "batch",
ops: [
{ type: "deleteNode", slideId, nodeId: "2" },
{ type: "deleteNode", slideId, nodeId: "3" },
],
});Hooks
import {
useHistory, // { canUndo, canRedo, isDirty, undo, redo }
usePresentation, // { presentation, status, progress, error }
usePresentationStore, // the ambient store, for imperative calls
useSlide, // { slide, slideId, index }
useZoom, // { zoom, isAutoFit, setZoom, zoomIn, zoomOut, setAutoFit, fitTo }
} from "@diceui/pptx";All hooks must be used inside Presentation.Root or Presentation.Provider.
useHistory
import { useHistory } from "@diceui/pptx";
function HistoryButtons() {
const { canUndo, canRedo, isDirty, undo, redo } = useHistory();
return (
<>
<button disabled={!canUndo} onClick={undo}>
Undo
</button>
<button disabled={!canRedo} onClick={() => void redo()}>
Redo
</button>
{isDirty && <span>Unsaved changes</span>}
</>
);
}Don't wire this with store.subscribe. Undo history isn't in store state, so a save that clears isDirty wouldn't re-render. Outside React, use store.on("historyChange").