Placeholders
A placeholder is a fill-in prompt in a template, such as “[Insert study drug name]”: Word’s
content-control placeholder text. It is the core placeholder node, so every stesuraSchema()
has it and no setup is needed.
It behaves as one unit, as in Word:
- Click or an arrow key selects the whole placeholder; the next arrow moves past it.
- Typing with it selected replaces it with what you type, in the placeholder’s own formatting
plus the configured
filledMarks. One undo brings the placeholder back. - Paste with it selected replaces it with the pasted content, which gets
filledMarks. - Backspace right after it (or Delete right before it) selects it; the next press deletes it.
- Double-click or Enter opens a floating editor for its text, tag and title. Enter or Escape closes it and leaves the placeholder selected. Emptying the text deletes the placeholder. Word edits a placeholder’s text in place, and only in Design Mode; this is a deliberate deviation.
Mod-Alt-p(Ctrl+Alt+P) inserts one. The Insert tab’s Placeholder menu inserts one and walks the unfilled ones (Next / Previous, with a count).- Right-click: Edit placeholder text, Remove placeholder, keep text, Delete placeholder.
The menu’s key is
placeholder.
The node
| Attr | Type | |
|---|---|---|
id | string | null | Set by the uniqueId plugin; addresses one instance. |
tag | string | null | Machine key for programmatic fill; may repeat. DOCX w:tag. |
title | string | null | Human label, shown as a tooltip. DOCX w:alias. |
PlaceholderAttrs types them. The node is an inline atom with content: "text*": text runs with
their marks, nothing else (no hard breaks, no nested placeholders). Its node view has no
contentDOM, so the caret never enters it, as with inline math. It renders in
var(--stesura-placeholder-color, #808080), Word’s Placeholder Text grey; a colour mark on the
text wins. The hover and selection chrome is background and outline only, so the printed width is
the edited width, and print mounts get none.
Commands
import {
fillPlaceholders,
insertPlaceholder,
removePlaceholder,
selectPlaceholder,
setPlaceholderAttrs,
} from "@stesura/core/commands";
insertPlaceholder({ text: "[Study drug]", attrs: { tag: "drug" } })(state, dispatch);
fillPlaceholders(new Map([["drug", "Aspirin"]]))(state, dispatch);insertPlaceholder({ text?, attrs? })wraps a text selection in one paragraph (marks kept), or inserts at the caret withtext, else the configureddefaultContent, elseDEFAULT_PLACEHOLDER_TEXT(“Click or tap here to enter text.”; editor-react passes the localised one). It selects the new placeholder. Refused in code blocks, on or in a placeholder, across blocks, and for a selection holding anything but text.removePlaceholder({ pos?, keepText? })deletes the placeholder atpos(default: the selected one), or withkeepTextunwraps it to its text.setPlaceholderAttrs(pos, { tag?, title? }); an empty string clears one.fillPlaceholders(values)fills bytagfrom a map, or through a function of aPlaceholderInfo(match onid,titleortext), in one transaction. A value replaces the placeholder with the same marks as typing over it;""removes it;nullleaves it. With track changes on, the fill is tracked.selectPlaceholder(1 | -1)selects the next or previous placeholder in the body, wrapping. Header, footer and note bodies are counted byfindPlaceholdersbut not navigated.
The floating editor’s state and commands come from @stesura/core/plugins:
placeholderPlugin (in customPlugins; opt out with placeholderPlugin: false),
getPlaceholderEditorState, openPlaceholderEditorTarget, openPlaceholderEditor,
openPlaceholderEditorAt(pos), closePlaceholderEditor, setPlaceholderText(pos, text),
placeholderEnterCmd (bound to Enter), typeOverPlaceholder(text) (what typing over runs) and
unwrapOpenPlaceholders(slice) (the paste rule: a placeholder a slice cuts open becomes its
text). placeholderPluginKey holds the state, a PlaceholderEditorState.
setPlaceholderText writes the minimal step, as math’s source editor does, and skips text struck
by track changes: liveTextOf(node) is the text without it.
Helpers
From @stesura/core/helpers: findPlaceholders(doc, { includeHidden? }) (every placeholder, as
PlaceholderInfo), getSelectedPlaceholder(state), placeholderAt($pos), isPlaceholder(node),
placeholderTextMarks(node) (its first run’s marks), filledMarksFor(state, node),
fillMarksFor(state, node, parent) (what replacing text gets), getPlaceholderConfig(state) and
createPlaceholderNode(schema, content) (from a PlaceholderContent, marks as
PlaceholderMarkJSON).
Configuration
config.placeholders (PlaceholderConfig), config-time; a sub-editor uses its mount’s:
const config = {
placeholders: {
// Content for a new placeholder with no text of its own.
defaultContent: ({ parent, pos, state }) => ({ text: "[Enter text]", attrs: { title: "Field" } }),
// Marks for text typed, pasted or filled over a placeholder.
filledMarks: [{ type: "highlight", attrs: { color: "#FFFF00" } }],
},
};Mark JSON is validated against the schema; unknown marks and marks the paragraph disallows are dropped.
Elsewhere
- DOCX: imported from content controls showing their placeholder (
w:showingPlcHdr), and exported as an inline plain-text control withw:temporary. - Markdown: written as the prompt text, with a warning.
- Track changes: typing over a placeholder strikes it and inserts the text as one suggestion; reject brings it back.
- Comments: a comment on part of a placeholder anchors around all of it.