Universal OfficeBeta

Events

<universal-write> communicates with the host app through CustomEvents. All use the uw- prefix; the payload is in event.detail.

editor.addEventListener('uw-ready', (e) => {
  console.log('offline?', e.detail.offline, 'plugins:', e.detail.plugins);
});

Lifecycle and document

EventdetailFires when
uw-ready{ offline: boolean, plugins: string[] }Component initialized: fonts loaded and document ready.
uw-change—The document is about to change — it fires before the edit is applied, so read the new state on the next frame (e.g. requestAnimationFrame). Use for autosave. Updating fields and the table of contents does not fire it; for “has it changed?” use isDirty() / uw-dirty-change.
uw-side-resize{ start: number, end: number }The width (px) of the host's side panels (slot="side-start" / slot="side") changed — 0 when empty or hidden.
uw-theme-change{ theme: 'light' | 'dark' }The UI theme in effect (effectiveTheme) changed: the color-scheme inherited from the page, the OS (with light dark) or the theme attribute.
uw-dirty-change{ dirty: boolean }isDirty() changed: the first change after loading or after setDirty(false) (true), and setDirty(false), a new document or the bars’ Save (false).
uw-formatting-policy-change{ policy: FormattingPolicy | null }formattingPolicy or the formatting-policy attribute changed (the bars are rebuilt). See Formatting policy.
uw-file-drop{ files: File[], clientX, clientY }Files were dropped on the element, before the editor handles them. Cancelable — preventDefault() = the host handles them (e.g. replace the document after asking). Fires also with nofiledrop. See Dropped files.
uw-protected{ active: boolean, hasFields: boolean }Protected View turned on/off.
uw-blocked{ reason: 'protected' }Header/footer editing was refused because the document is in Protected View.
uw-font-embedding{ embedded: string[], skipped: { family, reason }[] }After saving a .docx with font embedding on; reason is 'common', 'license' or 'unavailable'.

Interface

EventdetailFires when
uw-zoom{ zoom: number }The zoom changed (shortcut, gesture, or setZoom).
uw-styles-change—The style sheet changed (create, modify, rename, delete or undo/redo of a style).
uw-cellmode{ active: boolean }The caret entered or left a table cell.
uw-tabletool{ tool: 'draw' | 'eraser' | 'painter' | null }Draw Table, Eraser or Border Painter turned on or off.
uw-borderpenBorderSpecThe border pen (style, width, color) changed.
uw-sort-error{ reason: 'merged', message }Sort refused: the table has merged cells (Word’s message).
uw-hfmode{ active: boolean, kind?: 'header' | 'footer' }Entered/left header or footer editing.
uw-hfhover{ kind?: 'header' | 'footer', x?: number, y?: number }Mouse over the header/footer region (to show a hint).
uw-tbmode{ active: boolean }Entered/left text-box editing.
uw-cellmode{ active: boolean }Entered/left table-cell editing.
uw-notemode{ active: boolean, kind?: 'footnote' | 'endnote', id?: number }Entered/left a footnote or endnote.

Text input

EventdetailFires when
uw-before-input{ text: string } (cancelable)Before text is inserted. preventDefault() cancels the insertion — this is how plugins intercept triggers like @.
uw-input{ text: string }After text is inserted.

Comments

EventdetailFires when
uw-comments-changed—A comment was added or removed.
uw-track-changes{ on }Track Changes was turned on or off.
uw-markup{ mode }Display for Review or Show Markup changed.
uw-revisions-changed—Revisions were accepted or rejected.
uw-comment-click{ ids: number[] }Click on a commented range in the document.

Plugins

EventdetailFires when
uw-plugin-added{ id: string }Plugin installed on this instance (use() or UniversalWriteElement.register()).
uw-plugin-removed{ id: string }Plugin removed from this instance (unuse() or UniversalWriteElement.unregister()).
uw-custom-inline-inserted{ type: string, data: any, fallbackText: string }Custom inline inserted into the document.

Recipe: reactive status bar

const status = document.getElementById('status');

editor.addEventListener('uw-change', update);
editor.addEventListener('uw-zoom', update);

function update() {
  const st = editor.stats();
  const zoom = Math.round(editor.getZoom() * 100);
  status.textContent =
    `Page ${st.page} of ${st.pages} · ${st.words} words · ${zoom}%`;
}

Recipe: save only when changed

saveButton.disabled = !editor.isDirty();
editor.addEventListener('uw-dirty-change', (e) => {
  saveButton.disabled = !e.detail.dirty;
});

saveButton.onclick = async () => {
  if (!editor.isDirty()) return;
  const bytes = await editor.save('docx');
  editor.setDirty(false);   // edits made during the upload mark it again
  try { await upload(bytes); } catch (err) { editor.setDirty(true); throw err; }
};

Recipe: "Enable Editing" bar

editor.addEventListener('uw-protected', (e) => {
  banner.hidden = !e.detail.active;
});

bannerButton.onclick = () => editor.enableEditing();