Skip to content

feat: headless toolbar#2657

Merged
caio-pizzol merged 6 commits intomainfrom
artem/headless-toolbar
Apr 2, 2026
Merged

feat: headless toolbar#2657
caio-pizzol merged 6 commits intomainfrom
artem/headless-toolbar

Conversation

@artem-harbour
Copy link
Copy Markdown
Contributor

@artem-harbour artem-harbour commented Mar 31, 2026

Linear ticket - SD-2274.

packages/super-editor/src/headless-toolbar - Headless toolbar.
packages/super-editor/src/headless-toolbar/README.md - Brief info.

examples/advanced/headless-toolbar - Integration example with demo toolbar.

Screenshot

Related PR (draft docs page): #2655

@artem-harbour artem-harbour self-assigned this Mar 31, 2026
@artem-harbour artem-harbour force-pushed the artem/headless-toolbar branch 2 times, most recently from 547c1bc to af61fc3 Compare March 31, 2026 14:20
"dev": "vite"
},
"dependencies": {
"superdoc": "../../../packages/superdoc",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: change to "latest", when deployed.

@linear
Copy link
Copy Markdown

linear bot commented Mar 31, 2026

@artem-harbour artem-harbour marked this pull request as ready for review March 31, 2026 14:41
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af61fc3b72

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

commands: {},
});

const headlessToolbarCommands = [
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harbournick @caio-pizzol A DX concern - devs might be looking for names/IDs from the Document API. Would it make sense to use API paths as toolbar IDs?

type PublicToolbarItemId =
  | 'format.bold'
  | 'format.strike'
  | 'tables.splitCell'
  | 'history.undo'
  // ...

This has its own challenges (like toolbar items with no API equivalent), but will keep things cohesive I think?

@artem-harbour artem-harbour force-pushed the artem/headless-toolbar branch 3 times, most recently from 0db926c to ee0494d Compare April 1, 2026 13:33
Copy link
Copy Markdown
Contributor

@caio-pizzol caio-pizzol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@artem-harbour solid foundation — built on top of it in #2669.

fixed a few things: undo/redo in header/footer, zoom/ruler not updating, color sync matching the built-in toolbar. execute is now required (no ?.), snapshot values match what you pass in, and execute('image') just works without needing editor internals.

added typed payloads so wrong arguments get caught at compile time. shipped a useHeadlessToolbar() hook for React and Vue. replaced the single Vue demo with 5 examples across different frameworks. added full docs with a command reference table.

all in #2669.

@artem-harbour
Copy link
Copy Markdown
Contributor Author

@caio-pizzol - resolved the conflicts, ready to merge.

artem-harbour and others added 2 commits April 2, 2026 17:41
* feat: headless toolbar

* fix(headless-toolbar): correctness fixes and DX improvements

Correctness:
- Use resolveStateEditor for undo/redo history depth (fixes header/footer)
- Remove early return gating on color/highlight annotation sync
- Subscribe to zoomChange event for immediate zoom state updates
- Refresh snapshot after execute() for superdoc-level commands
- Fix redundant documentMode self-comparison in isCommandDisabled

DX:
- Make execute() required on HeadlessToolbarController type
- Normalize font-size values with unit (e.g. '12pt' not '12')
- Preserve full font-family CSS value (e.g. 'Arial, sans-serif' not 'Arial')
- Normalize color values to lowercase
- Add execute('image') handler (file picker + insertion)
- Fix demo to use execute() consistently for all commands
- Fix demo font selects to use option.value not option.label
- Remove unused RegistryMode/mode abstraction
- Rewrite README with toolbar: null setup and command reference table

* feat(headless-toolbar): add multi-framework examples and DX improvements

Replace single Vue demo with 5 framework examples showcasing different
toolbar patterns:
- react-shadcn: classic top ribbon (Radix + Tailwind + Lucide)
- react-mui: floating bubble bar (MUI + Material Icons)
- vue-vuetify: sidebar panel (Vuetify 3 + MDI)
- svelte-shadcn: compact bottom bar (Svelte 5 + Tailwind + Lucide)
- vanilla: minimal top bar (plain HTML/CSS/JS + Lucide)

API improvements:
- execute() now auto-restores editor focus after commands
- Add DEFAULT_TEXT_COLOR_OPTIONS and DEFAULT_HIGHLIGHT_COLOR_OPTIONS
  constants

* fix(headless-toolbar): address review findings

- Color execute: run annotation sync unconditionally but return the
  mark command result (not always true)
- Image execute: add .catch() with console.error instead of silently
  swallowing errors
- MUI example: remove unused variables, guard exec against null
  controller on first render

* feat(headless-toolbar): add React hook and Vue composable

Ship useHeadlessToolbar() for React and Vue:

  import { useHeadlessToolbar } from 'superdoc/headless-toolbar/react';
  const { snapshot, execute } = useHeadlessToolbar(superdoc, commands);

Handles subscribe/unsubscribe, state updates, and cleanup
automatically. Eliminates the useState + useEffect + useRef
boilerplate that every React consumer would write.

Vue composable follows the same API with shallowRef reactivity
and onBeforeUnmount cleanup.

Update react-shadcn example to use the hook as proof.

* feat(headless-toolbar): add typed payloads and snapshot values

Add ToolbarPayloadMap and ToolbarValueMap type maps that give
compile-time safety to execute() and snapshot.commands[id].value:

  toolbar.execute('font-size', '14pt')  // ✓
  toolbar.execute('font-size', 14)      // ✗ type error
  toolbar.execute('bold', 'wrong')      // ✗ type error

  snapshot.commands['zoom']?.value      // type: number | undefined
  snapshot.commands['font-size']?.value  // type: string | undefined

No runtime changes — types only.

* fix(superdoc): make documentMode optional in Config type

documentMode defaults to 'editing' at runtime but the JSDoc typedef
marked it as required, causing TypeScript errors when constructing
SuperDoc without explicitly passing it.

* chore(examples): remove toolbar: null from all examples

* docs(headless-toolbar): add headless toolbar documentation

Restructure toolbar docs into a group with four pages:
- overview: decision page (built-in vs headless)
- built-in: existing toolbar docs (moved from toolbar.mdx)
- headless: full API reference with command table and typed examples
- examples: 5 framework showcases (React shadcn, React MUI, Vue
  Vuetify, Svelte, vanilla JS)

Add doctest support for headless toolbar code examples.
Update SuperDoc configuration docs for toolbar parameter.
Add redirect from /modules/toolbar to /modules/toolbar/overview.
Add superdoc/headless-toolbar to import allowlist.

* fix(headless-toolbar): include fallback fonts in Aptos constant

Aptos constant now includes fallback fonts ('Aptos, Arial, sans-serif')
matching what documents actually store. Without fallbacks, the snapshot
value wouldn't match the constant, breaking select components.

* fix(examples): register Vuetify components and directives

* refactor(examples): simplify Vue Vuetify sidebar toolbar layout

* fix(examples): add Tailwind v4 @reference directive for Svelte styles

* fix(examples): only set select value when it matches an option

* fix(headless-toolbar): address author review feedback

- Remove editorDestroy subscription — the event fires during teardown
  when the editor may be in an inconsistent state, causing the refresh
  cycle to read from a dying editor. editorCreate is sufficient.
- Remove auto-focus from execute() — the built-in toolbar has nuanced
  focus logic (pending marks, header/footer exclusion, dropdown
  detection) that a simple view.focus() doesn't replicate. Better
  handled as a follow-up with proper parity.
- Restore onMouseDown preventDefault in react-shadcn example since
  focus is no longer handled by execute().
- Update docs and README to remove focus handling claims.

---------

Co-authored-by: Artem Nistuley <artem@harbourshare.com>
@artem-harbour artem-harbour force-pushed the artem/headless-toolbar branch from f105add to 36f136e Compare April 2, 2026 14:41
caio-pizzol and others added 4 commits April 2, 2026 12:25
- Export ToolbarCommandStates, ToolbarExecuteFn, ToolbarPayloadMap,
  ToolbarValueMap from super-editor index (fixes consumer TS errors)
- Accept null/undefined in Vue composable to match React hook behavior
- Replace duplicated React/Vue hooks in superdoc with re-exports
- Add vite aliases for headless-toolbar sub-path resolution
- Remove unused ALIGN_ICONS, dead CSS in vanilla example
Add advanced-headless-toolbar job to ci-examples workflow, running
all 5 framework examples (react-shadcn, react-mui, vue-vuetify,
svelte-shadcn, vanilla) as a matrix strategy.
- Add @types/react as devDependency for headless-toolbar React hook
- Fix TS2345 in react.ts execute callback (unknown → any cast for
  typed payload passthrough)
@caio-pizzol caio-pizzol merged commit 58e292d into main Apr 2, 2026
57 checks passed
@caio-pizzol caio-pizzol deleted the artem/headless-toolbar branch April 2, 2026 16:14
@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot bot commented Apr 2, 2026

🎉 This PR is included in @superdoc-dev/react v1.0.0-next.13

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot bot commented Apr 2, 2026

🎉 This PR is included in template-builder v1.3.0-next.16

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot bot commented Apr 2, 2026

🎉 This PR is included in vscode-ext v1.1.0-next.59

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot bot commented Apr 2, 2026

🎉 This PR is included in esign v2.2.0-next.17

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot bot commented Apr 2, 2026

🎉 This PR is included in superdoc v1.24.0-next.56

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot bot commented Apr 2, 2026

🎉 This PR is included in superdoc-cli v0.5.0-next.56

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot bot commented Apr 2, 2026

🎉 This PR is included in superdoc-sdk v1.3.0-next.57

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants