Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 85 additions & 3 deletions apps/docs/__tests__/doctest.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import { test, describe, beforeAll, expect } from 'bun:test';
import { resolve } from 'node:path';
import { Editor, getStarterExtensions } from '../../../packages/superdoc/dist/super-editor.es.js';

// Headless toolbar exports — loaded dynamically because they may not
// exist in the dist until the feature branch is merged and rebuilt.
let createHeadlessToolbar: any;
let headlessToolbarConstants: any;
let headlessToolbarHelpers: any;
try {
const mod = await import('../../../packages/superdoc/dist/super-editor.es.js');
createHeadlessToolbar = mod.createHeadlessToolbar;
headlessToolbarConstants = mod.headlessToolbarConstants;
headlessToolbarHelpers = mod.headlessToolbarHelpers;
} catch {
// Not available yet — headless tests will be skipped
}
import { extractExamples } from './lib/extract.ts';
import { transformCode, applyStubs } from './lib/transform.ts';

Expand Down Expand Up @@ -44,6 +58,28 @@ function isApiError(err: unknown, code: string): boolean {
return false;
}

function createMockSuperdocHost(editor: Editor) {
const listeners = new Map<string, Set<Function>>();
return {
activeEditor: editor,
on(event: string, fn: Function) {
if (!listeners.has(event)) listeners.set(event, new Set());
listeners.get(event)!.add(fn);
},
off(event: string, fn: Function) {
listeners.get(event)?.delete(fn);
},
superdocStore: {
documents: [],
},
config: { rulers: false, documentMode: 'editing' },
getZoom: () => 100,
setZoom: () => {},
toggleRuler: () => {},
setDocumentMode: () => {},
};
}

const examples = extractExamples(docsRoot);

const byFile = new Map<string, typeof examples>();
Expand All @@ -69,9 +105,55 @@ for (const [file, fileExamples] of byFile) {
});

try {
editor.commands.selectAll();
const fn = new AsyncFunction('editor', code);
await fn(editor);
if (example.pattern === 'headless') {
if (!createHeadlessToolbar) return; // skip until dist includes headless toolbar
const superdoc = createMockSuperdocHost(editor);
const toolbar = createHeadlessToolbar({
superdoc,
commands: [
'bold',
'italic',
'underline',
'strikethrough',
'font-family',
'font-size',
'text-color',
'highlight-color',
'link',
'text-align',
'line-height',
'linked-style',
'bullet-list',
'numbered-list',
'indent-increase',
'indent-decrease',
'undo',
'redo',
'ruler',
'zoom',
'document-mode',
'clear-formatting',
'copy-format',
'image',
'track-changes-accept-selection',
'track-changes-reject-selection',
'table-insert',
],
});
const fn = new AsyncFunction(
'toolbar',
'headlessToolbarConstants',
'headlessToolbarHelpers',
'editor',
code,
);
await fn(toolbar, headlessToolbarConstants, headlessToolbarHelpers, editor);
toolbar.destroy();
} else {
editor.commands.selectAll();
const fn = new AsyncFunction('editor', code);
await fn(editor);
}
} catch (err) {
if (isApiError(err, code)) {
throw new Error(
Expand Down
7 changes: 5 additions & 2 deletions apps/docs/__tests__/lib/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface CodeExample {
file: string;
section: string;
code: string;
pattern: 'superdoc' | 'editor' | 'unknown';
pattern: 'superdoc' | 'editor' | 'headless' | 'unknown';
line: number;
}

Expand Down Expand Up @@ -60,7 +60,10 @@ function globMdx(dir: string): string[] {
return results;
}

function detectPattern(code: string): 'superdoc' | 'editor' | 'unknown' {
function detectPattern(code: string): 'superdoc' | 'editor' | 'headless' | 'unknown' {
if (code.includes("from 'superdoc/headless-toolbar'") || code.includes('createHeadlessToolbar')) {
return 'headless';
}
if (code.includes("from 'superdoc/super-editor'") || code.includes('Editor.open')) {
return 'editor';
}
Expand Down
18 changes: 18 additions & 0 deletions apps/docs/__tests__/lib/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { CodeExample } from './extract';
export function transformCode(example: CodeExample): string | null {
if (example.pattern === 'superdoc') return transformSuperdocPattern(example.code);
if (example.pattern === 'editor') return transformEditorPattern(example.code);
if (example.pattern === 'headless') return transformHeadlessPattern(example.code);
return null;
}

Expand Down Expand Up @@ -77,6 +78,23 @@ function transformEditorPattern(code: string): string | null {
return result || null;
}

function transformHeadlessPattern(code: string): string | null {
const lines = code.split('\n');

// Strip imports and SuperDoc/toolbar creation boilerplate, keep API usage
const filtered = lines.filter((line) => {
const trimmed = line.trim();
if (trimmed.startsWith('import ')) return false;
if (/(?:const|let)\s+superdoc\s*=/.test(trimmed)) return false;
if (/(?:const|let)\s+toolbar\s*=\s*createHeadlessToolbar/.test(trimmed)) return false;
if (/(?:const|let)\s+unsubscribe\s*=/.test(trimmed)) return false;
return true;
});

const result = filtered.join('\n').trim();
return result || null;
}

/** Extract handler body from superdoc.on() patterns that contain editor calls. */
function extractEventBody(code: string): string | null {
const lines = code.split('\n');
Expand Down
6 changes: 4 additions & 2 deletions apps/docs/core/superdoc/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ new SuperDoc({
Text label overrides
</ParamField>
<ParamField path="fonts" type="string[]">
Available font families. See [Toolbar fonts](/modules/toolbar#font-configuration) for details.
Available font families. See [Toolbar fonts](/modules/toolbar/built-in#font-configuration) for details.

<Info>Custom fonts from DOCX files will display in the toolbar but won't be selectable unless loaded in your app and added here.</Info>
</ParamField>
Expand Down Expand Up @@ -444,7 +444,9 @@ new SuperDoc({
</ParamField>

<ParamField path="toolbar" type="string">
CSS selector for the toolbar container (e.g. `'#toolbar'`). Shorthand for `modules.toolbar.selector`.
CSS selector for the built-in toolbar container (e.g. `'#toolbar'`). Shorthand for `modules.toolbar.selector`.

Omit this to skip the built-in toolbar — for example, when using the [headless toolbar](/modules/toolbar/headless) to build your own UI.
</ParamField>

## Advanced options
Expand Down
15 changes: 14 additions & 1 deletion apps/docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,16 @@
]
},
"modules/comments",
"modules/toolbar",
{
"group": "Toolbar",
"tag": "NEW",
"pages": [
"modules/toolbar/overview",
"modules/toolbar/built-in",
"modules/toolbar/headless",
"modules/toolbar/examples"
]
},
"modules/links",
"modules/context-menu",
"modules/pdf",
Expand Down Expand Up @@ -331,6 +340,10 @@
"dismissible": true
},
"redirects": [
{
"source": "/modules/toolbar",
"destination": "/modules/toolbar/overview"
},
{
"source": "/getting-started/ai-agents",
"destination": "/document-engine/ai-agents/llm-tools"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
---
title: Toolbar
title: Built-in Toolbar
sidebarTitle: Built-in
keywords: "word toolbar, document formatting controls, custom toolbar, editor ui components, formatting buttons"
---

The toolbar provides a customizable UI for document editing with support for custom buttons, responsive layouts, and role-based controls.
SuperDoc ships with a ready-made toolbar. Point it at a container, and you get formatting controls, font pickers, and document actions out of the box.

<Tip>
Want full control over the toolbar UI? Use the [headless toolbar](/modules/toolbar/headless) instead — build your own toolbar with any framework and component library.
</Tip>

## Quick start

Expand Down Expand Up @@ -599,7 +604,7 @@ const superdoc = new SuperDoc({

<Card
title="Custom Toolbar Example"
icon="github"
icon="external-link"
href="https://github.com/superdoc-dev/superdoc/tree/main/examples/features/custom-toolbar"
>
Runnable example: custom button groups, excluded items, and a custom clear-formatting button
Expand Down
Loading
Loading