Web Components Basics¶
The @sciflow/editor-start package registers two custom elements:
| Element | Purpose |
|---|---|
<sciflow-editor> |
The full ProseMirror-powered editor surface. |
<sciflow-formatbar> |
Optional toolbar that issues editor commands. |
This chapter covers loading the bundle, understanding attributes and events, wiring the toolbar, and applying theme tokens.
Loading the Bundle¶
<script type="module" src="/node_modules/@sciflow/editor-start/dist/bundle/sciflow-editor.js"></script>
Self-host or CDN
For production builds you can copy the emitted bundle to your own CDN. The file is an ES module and can be tree-shaken by modern bundlers.
Core Properties¶
| Property | Type | Description |
|---|---|---|
doc |
SciFlowDocJSON \| null |
Sets the full document snapshot, or pass { doc, files, references, selection, version } to update related data atomically. |
initialContent |
ProseMirrorNode \| null |
Alternative way to seed the document with a ProseMirror node. |
features |
Feature[] \| null |
List of editor features. Can include custom features with plugins. |
placeholder |
string |
Placeholder string shown when the document is empty. |
headingText |
string |
Display heading for the built-in default document template. |
shadowStyles |
string \| string[] \| CSSStyleSheet \| CSSStyleSheet[] |
CSS appended inside the shadow root after the built-in styles. Useful for ProseMirror tweaks. |
commands (getter) |
CommandRunner \| null |
High-level commands for editing operations. |
document (getter) |
ProseMirrorNode \| null |
Read-only document structure for traversal. |
positions (getter) |
PositionAPI |
Position utilities for coordinate mapping. |
plugins (getter) |
PluginAPI |
Plugin state management. |
dom (getter) |
DomAPI |
DOM utilities for events and positioning. |
Advanced: Direct ProseMirror Access¶
The editorView getter provides direct access to ProseMirror internals:
⚠️ This is an advanced escape hatch for use cases not covered by the stable APIs above. Direct ProseMirror access may change between major versions. Prefer the high-level APIs when possible.
Events¶
| Event | Fired when | Detail payload |
|---|---|---|
editor-ready |
Editor has mounted and commands/views become available. | { editor: SciFlowEditorElement } |
editor-change |
Document content changes. | { doc: SciFlowDocJSON, operations: Operation[], files: SnapshotFile[], references: SnapshotReference[] } |
editor-selection-change |
Selection/cursor changes. | { anchor: number, head: number } |
All events bubble across the shadow boundary so you can attach listeners to parent components or frameworks.
editor.addEventListener('editor-change', (event) => {
const { doc, files, references } = event.detail;
saveDraft({ doc, files, references });
});
Connecting the Format Bar¶
1. Declarative (for attribute)¶
The toolbar auto-discovers the editor with that id once both elements are in the DOM.
2. Programmatic¶
const toolbar = document.querySelector('sciflow-formatbar');
const editor = document.querySelector('sciflow-editor');
editor.addEventListener('editor-ready', () => {
toolbar.editor = editor;
}, { once: true });
Material Symbols required
The toolbar uses Material Symbols. Include the font in your <head>:
Issuing Commands¶
const runner = editor.commands;
if (runner) {
runner.commands.focus();
runner.commands.insertText('Hello, world!');
runner.flow().toggleMark('strong').insertText(' bold text').run();
}
Use runner.available() to check capabilities before toggling marks or nodes.
Lists & Blockquotes¶
The list and blockquote features expose familiar commands that mirror the ProseMirror examples. Once those features are enabled (they are toggled on in the demo), you can drive them like any other command:
const commands = editor.commands?.commands;
// Toggle bullet/ordered lists
commands?.toggleBulletList?.();
commands?.toggleOrderedList?.();
// Nest or lift list items
commands?.sinkListItem?.();
commands?.liftListItem?.();
// Wrap/unwrap blockquotes
commands?.toggleBlockquote?.();
Each command returns true on success, so you can wire them into toolbars or keyboard shortcuts as needed.
Styling & Layout¶
This section explains the three layers of styling you can control:
- Host element styles (outer container): size, margins, fonts, and CSS variables you set on
<sciflow-editor>. - Shadow DOM styles (inside the editor): ProseMirror content styles and decoration classes.
- Global theme styles (all SciFlow components): shared tokens applied across the editor, format bar, and sidebars.
If you are new to web components, think of the shadow DOM as a private “subtree” where most internal styles live. You can still customize it, but you do so via the APIs below rather than normal global CSS selectors.
CSS Custom Properties¶
The editor exposes CSS custom properties such as --sciflow-editor-border and --sciflow-editor-focus-ring. Override them on the host element to match your design system.
Shadow DOM Styles¶
Use these when you need to restyle the text area itself (headings, paragraphs, selections, or plugin decorations). These styles live inside the editor’s shadow DOM, so global CSS rules like .ProseMirror h1 { ... } won’t reach them unless you inject the CSS via the API below.
Both shadowStyles and setShadowStyles() accept strings, arrays of strings, or CSSStyleSheet objects. You can use either the property or the method:
Property assignment (applies asynchronously via Lit lifecycle):
editor.shadowStyles = [
'.ProseMirror { line-height: 1.6; }',
'.ProseMirror h1 { font-family: "Merriweather", serif; }',
];
Method call (applies immediately, recommended for dynamic updates):
editor.setShadowStyles([
'.ProseMirror { line-height: 1.6; }',
'.ProseMirror h1 { font-family: "Merriweather", serif; }',
]);
Dynamic injection (e.g., for ProseMirror decorations added via plugins):
// Initialize editor first
editor.doc = { doc: myDocument, files: [], references: [] };
// Later: add styles for decorations dynamically
editor.setShadowStyles([`
.my-annotation {
background: rgba(255, 200, 0, 0.3);
border-bottom: 2px solid orange;
}
.my-highlight {
background: yellow;
}
`]);
Both approaches work before or after initialization. Use setShadowStyles() when you need immediate application (e.g., after adding a plugin with decorations).
Global Theme Injection¶
Use this to define shared tokens across all SciFlow web components (editor, format bar, selection editor, reference list). This is the easiest way to keep colors and borders consistent across the entire UI.
To apply a theme across all components, call setSciFlowThemeStyles() once:
import { setSciFlowThemeStyles } from '@sciflow/editor-start';
setSciFlowThemeStyles(`
:host {
--sciflow-accent: #0ea5e9;
--sciflow-surface: #ffffff;
}
.reference-item {
border-color: var(--sciflow-accent);
}
`);
Layout¶
By default, <sciflow-editor> behaves like a block element and stretches to the width of its parent. You can control the size using normal CSS on the host element, for example:
sciflow-editor {
display: block;
max-width: 820px;
min-height: 60vh;
margin: 24px auto;
border: 1px solid var(--sciflow-editor-border, #e5e7eb);
}
If you place the editor inside a grid or flex layout, it will size like any other block-level element. Use the host styles for outer spacing and the shadow styles for the content inside the editor.
Keyboard focus
Call runner.commands.focus() after programmatic updates (e.g., clicking entries in your own outline) so screen readers and caret navigation keep working.
Custom Events & Integrations¶
The demo shows how to:
- Mirror the selection into a reference sidebar via
editor-selection-change. - Dispatch navigation requests (outline clicks →
commands.setSelection). - Drive custom insertions (
commands.insertFigure,commands.insertCitation).
Refer to the Customization Recipes for full walk-throughs.