Hooks

Connect React controls to PPTX editor state and commands with hooks.

Call editor-state hooks inside PptxEditor.Root. The packaged <PptxEditor> provides that root for its children.

usePptxSource and useFonts prepare input for the root, so you can call them in the parent component.

Connect a command button

This button runs a toolbar command and shows why the command cannot run:

import { useEditorCommand, useTranslation } from '@pptx-editor.dev/react';
import { translateKeyOrText } from '@pptx-editor.dev/core/editor';

export function BoldButton() {
  const bold = useEditorCommand('text.bold');
  const { t } = useTranslation();
  const reason = bold.disabledReason;
  return (
    <button
      type="button"
      aria-disabled={!bold.isEnabled || undefined}
      aria-pressed={bold.isActive}
      aria-label={t('toolbar.bold')}
      title={reason ? translateKeyOrText(t, reason) : t('toolbar.bold')}
      onMouseDown={(event) => event.preventDefault()}
      onClick={() => bold.execute()}
    >
      B
    </button>
  );
}

useEditorCommand(slot: ChromeSlotId): UseEditorCommandReturn takes a slot identifier from the toolbar slot reference.

MemberTypeDescription
execute(value?)booleanRuns the slot's command, with a value for value slots. Returns true when the engine ran it, and false on a refusal.
isEnabledbooleanWhether the command can run now.
isActivebooleanWhether a toggle control shows as pressed.
disabledReasonstring | nullA translation key, or the engine's own text, when the command cannot run.
valuestring | number | undefinedThe value a value slot shows, such as a font size or a family.

The types in these signatures, such as ChromeSlotId, CommandResult, EditorSnapshot, and PptxEditorInstance, are exported from @pptx-editor.dev/react. You do not need an import from @pptx-editor.dev/core to type your handlers.

Read state

useEditorState(selector, isEqual?) returns one slice of the editor snapshot. The component renders again only when the slice changes:

import { useEditorState } from '@pptx-editor.dev/react';

export function SlideCounter() {
  const current = useEditorState((s) => s.currentSlide);
  const total = useEditorState((s) => s.slides.length);
  return (
    <span>
      {current + 1} / {total}
    </span>
  );
}

Pass a comparator for an object slice. Common fields include status, slides, currentSlide, selection, format, mode, canUndo, canRedo, presentationView, and lastRejection.

HookReturnsNotes
useEditorState(selector, isEqual?)TThe selected snapshot value. isEqual defaults to Object.is.
usePptxEditor()PptxEditorInstance | nullnull before the editor mounts and outside a root.
useEditorColorMode(){ colorMode: ResolvedColorMode }'light' or 'dark', after 'system' resolves.
useTranslation(){ t: Translate }The translator for the root's locale. See Language.

usePptxEditor

Use the instance for actions and one-time reads:

import { usePptxEditor } from '@pptx-editor.dev/react';

export function SaveButton({ upload }: { upload: (bytes: Uint8Array) => Promise<void> }) {
  const editor = usePptxEditor();
  return (
    <button
      disabled={!editor}
      onClick={() => {
        const saved = editor?.save();
        if (saved) void upload(saved);
      }}
    >
      Save
    </button>
  );
}

A direct snapshot() call does not subscribe the component. Use useEditorState for values that you render.

Choose a hook

Formatting

HookReturns
useFontFormat()format: the font of the selection, or null. isEnabled. apply(update) writes the fields as one undo step.
useParagraphFormat()format: the paragraph formatting of the selection, or null. isEnabled. apply(update) writes the fields as one undo step.
useBulletFormat()format: the bullet format, null for no list, or undefined when the paragraphs differ. isEnabled. apply(format) writes it, and apply(null) turns the list off.
useEditorValueCommand(slot)A typed value slot. See Apply a value.

Slides and panes

HookReturns
useSlideLayouts()The deck's slide layouts (SlideLayoutSummary[]), in gallery order. See Build a layout picker.
useNotesPane()open, setOpen(open), and toggle() for the notes pane of the root.
useSlideshow()controller (or null while no show runs), start('start' | 'current'), and stop().

Dialogs, menus, and notices

HookReturns
useEditorDialog(key)isOpen, isAvailable, open(returnFocusTo?), and close() for a dialog of the popups registry. See Open a dialog from your control.
usePageSetupDialog(), useBackgroundDialog(), useParagraphDialog(), useFontDialog(), useBulletsAndNumberingDialog(), useColumnsDialog(), useImageAltTextDialog(), useImagePropertiesDialog()The draft of an open dialog: values, errors, isEnabled, setValue(name, value), apply(), and cancel(). useBackgroundDialog() adds reset(). Call them inside the dialog. See Dialog parts and draft state.
useContextMenuRequest()The open canvas context menu request, or null.
useContextMenuTarget()The element the open context menu opened on, or null.
useOpenFailure({ t? })failure, message, kept, and dismiss() for a file that did not open. t translates message. See Show a banner or a toast.
useHyperlinkPopup(), useHyperlinkPopupInstance(active?)The link popup's state and actions, with no UI. See Build your own link panel.

Fonts

HookReturns
useFonts(source, ...fragments) or useFonts(...origins)A font resolver with a stable identity, for the fonts prop. See Fonts.
useFontFamily()UseFontFamilyReturn: value, theme, options, groups, isCurrent(row), setValue(family), isEnabled, and disabledReason. Use it for your own font picker. See Build the list from useFontFamily.

Open a presentation

usePptxSource(source, { fonts?, fetchOptions? }) loads bytes from a URL and resolves fonts. It cancels stale fetches when the source changes or the component unmounts:

import { PptxEditor, usePptxSource } from '@pptx-editor.dev/react';
import { packagedFonts } from '@docx-editor.dev/fonts';

export function Deck({ url }: { url: string }) {
  const { document, fonts, error } = usePptxSource(url, { fonts: packagedFonts() });
  if (error) return <p>{error.message}</p>;
  return <PptxEditor document={document} fonts={fonts} />;
}
MemberTypeDescription
documentUint8Array | undefinedThe bytes, for the document prop.
fontsFontConfiguration | FontResolver | undefinedThe value for the fonts prop.
errorError | nullWhy the presentation did not open. A font failure never shows here.
isLoadingbooleanTrue until the presentation arrives or fails, and for up to 2 seconds while eager fonts load.

Eager fonts (a value, a promise, or a zero-argument loader) hold the deck back until they settle, so the slides lay out once. They hold it for at most 2 seconds. Fonts that settle later still apply, and the slides lay out again. An on-demand resolver (packagedFonts(), googleFonts()) needs the parsed deck, so the bytes go through at once, and fonts is one resolver for the life of the hook.

A string source is always a URL, fetched as you wrote it. Check a URL from user input first. When no bytes arrive for 10 seconds, the download stops and error is a TimeoutError. A slow download that keeps sending bytes finishes.

The download also stops when the file is longer than 528 MiB, the longest file the editor opens. The hook checks the Content-Length header before the body arrives, and the byte count while it downloads. In both cases, error is a RangeError.

Build your own chrome

Use these hooks to connect your controls to the editor.

Zoom

useZoom(): UseZoomResult reads and sets the slide zoom. This control replaces the zoom pill:

import { useZoom } from '@pptx-editor.dev/react';

export function ZoomControl() {
  const { zoom, isFit, fit, zoomIn, zoomOut, canZoomIn, canZoomOut } = useZoom();
  return (
    <div role="group" aria-label="Zoom">
      <button onClick={zoomOut} disabled={!canZoomOut} aria-label="Zoom out">
        -
      </button>
      <span>{Math.round(zoom * 100)}%</span>
      <button onClick={zoomIn} disabled={!canZoomIn} aria-label="Zoom in">
        +
      </button>
      <button onClick={fit} aria-pressed={isFit}>
        Fit
      </button>
    </div>
  );
}
MemberTypeDescription
zoomnumberThe zoom in force. 1 is 100 %. In fit mode, it is the zoom that fits the slide.
isFitbooleanWhether the slide fits the window and follows its size.
setZoom(zoom)voidSets a fixed zoom from 10 % to 400 %, and leaves fit mode.
fit()voidFits the slide to the window.
reset()voidSets a fixed 100 %.
zoomIn(), zoomOut()voidMove one step of levels from the zoom in force.
canZoomIn, canZoomOutbooleanWhether a step is possible.
levelsreadonly number[]The steps that the zoom buttons use.

Use fit() to fit the whole slide. Use setZoom() for a fixed zoom level.

Events

useEditorEvent(event, handler): void calls handler each time the event occurs, while the component is mounted:

import { useEditorEvent } from '@pptx-editor.dev/react';

export function RefusalToast({ show }: { show: (text: string) => void }) {
  useEditorEvent('rejection', (rejection) => show(rejection.reason));
  return null;
}
EventHandlerOccurs when
change(snapshot) => voidAn edit applies, an undo or redo runs, or a load finishes or fails.
selectionChange(snapshot) => voidThe shape or text selection, the current slide, or the selected slides change.
rejection(rejection, snapshot) => voidThe editor refuses an edit.
openFailure(failure, snapshot) => voidA file does not open.

The events come from the editor's one subscription. Outside the adapters, onEditorEvent(editor, event, handler) from @pptx-editor.dev/core/editor gives the same events and returns the unsubscribe function. The PptxEditorEvents type lists the events.

Other hooks

HookReturnsNotes
useEditorSnapshot(editor)numberA counter that changes on each editor change. Prefer useEditorState, which renders again only when its slice changes.
useScopeClassName()'' | 'pptx-editor ' | 'pptx-editor dark 'The scope classes for the root element of your own part, so the --doc-* tokens apply. See Style your controls.
useEditorHostDocument()Document | nullThe document that the slide is mounted in: an iframe's when you mount the editor there, else the page's. Use it in place of the global document for listeners and focus. null during server rendering.

Find, caret, and undo steps

HookReturnsNotes
useDocumentSearch()UseDocumentSearchResultA find session with no UI, for your own find bar: query, setQuery, matchCase, wholeWord, matches, activeIndex, activeMatch, goTo(index), next(), previous(), find(query, { selectFirst }), and clear(). Every hook on one editor shares the session.
useEditorCaret()EditorCaret | nullThe caret while text is edited: nodeId, paragraph, offset, and cell in a table. It changes only when the caret moves.
useHistoryGroup({ kind })UseHistoryGroupReturnOne undo step for one gesture on your own control. Put ref on the control, and pass options() to each command of the gesture. kind is range, repeat, keyboard, or native-color.
import { useEditorValueCommand, useHistoryGroup } from '@pptx-editor.dev/react';

export function SizeSlider() {
  const group = useHistoryGroup({ kind: 'range' });
  const size = useEditorValueCommand('font.size');
  return (
    <input
      type="range"
      aria-label="Font size"
      min={8}
      max={96}
      ref={group.ref}
      onChange={(e) => size.execute(Number(e.target.value), group.options())}
    />
  );
}

Labels

HookReturnsNotes
useChromeTranslate(overrides?)ChromeTranslateThe t of the nearest compound, else the root's locale. overrides is a Map that wins for its keys.
useToolbarContext()ToolbarContextValue{ t }: the host's t on the nearest compound, or undefined.
useToolbarLabel()(key) => stringThe label for a key: the compound's t, else the locale catalog.
useToolbarLabelFor(t)(key) => stringThe same, for a resolver that you pass.

Toolbar, Menu, ContextMenu, SlideRail, StatusBar, FindBar, OpenFailure, FontNotice, and EditNotice take a t prop. With it, the compound and its parts take every label from your function. The dialogs take their labels from the root's locale.

Search selection

Find shows the active match as the slide selection. useDocumentSearch does not expose a named text highlight layer.

Apply a value

useEditorValueCommand(slot) reads the value of a value slot and applies a new one, with the slot's type from ToolbarValueMap. This slider sets the font size:

import { useEditorValueCommand } from '@pptx-editor.dev/react';

export function FontSizeSlider() {
  const size = useEditorValueCommand('font.size');
  return (
    <label>
      {size.value === null ? 'Mixed' : `${size.value} pt`}
      <input
        type="range"
        min={8}
        max={96}
        value={size.value ?? 18}
        disabled={!size.isEnabled}
        onChange={(event) => {
          const result = size.execute(Number(event.target.value));
          if (!result.ok) console.info(result.reason);
        }}
      />
    </label>
  );
}

font.size takes points, or { step: 1 } and { step: -1 } for one step of the size list. null means mixed or unavailable.

MemberTypeDescription
execute(value, options?)CommandResultRuns the command. A refusal gives ok: false and the reason, such as disabled.invalidValue.
valueS | nullThe value the slot shows, or null when mixed or unavailable.
optionsreadonly V[]The slot's fixed choices. A slot with a free value has none.
isEnabled, disabledReasonboolean, string | nullAs useEditorCommand gives them.

The two hooks return different results from execute: useEditorCommand returns a boolean, because a toolbar button only needs to know if the command ran, and useEditorValueCommand returns the CommandResult, because a value control shows why the engine refused a value.

To make the values of one slider drag one undo step, pass { historyGroup } from editor.beginHistoryGroup(). See Group a slider or a stepper.

Limits

  • Call hooks inside PptxEditor.Root. Outside it, usePptxEditor() returns null.
  • canUndo reports undo availability, not unsaved changes. Track saves yourself.
  • Dialog draft hooks read a draft. They do not open the dialog; use useEditorDialog(key) for that.
  • disabledReason can be a translation key. Show it with translateKeyOrText(t, reason) from @pptx-editor.dev/core/editor.

See also