Example: Editor Panels
A “panel”, as supported by the @codemirror/view package,
is a UI element shown above or below the editor. They will sit inside
the editor's vertical space for editors with fixed height. When the
editor is partially scrolled out of view, panels will be positioned to
stay in view.
This example shows how to add panels to your editor.
Opening and Closing Panels
The set of panels to show at a given time is determined by the value
of the showPanel
facet. To track the current
state of our panel, we define this state field,
with an effect to turn it on or off.
import {showPanel, Panel} from "@codemirror/view"
import {StateField, StateEffect} from "@codemirror/state"
const toggleHelp = StateEffect.define<boolean>()
const helpPanelState = StateField.define<boolean>({
create: () => false,
update(value, tr) {
for (let e of tr.effects) if (e.is(toggleHelp)) value = e.value
return value
},
provide: f => showPanel.from(f, on => on ? createHelpPanel : null)
})
The provide
option wires this field up to the showPanel
facet. The
createHelpPanel
function is defined like this:
import {EditorView} from "@codemirror/view"
function createHelpPanel(view: EditorView) {
let dom = document.createElement("div")
dom.textContent = "F1: Toggle the help panel"
dom.className = "cm-help-panel"
return {top: true, dom}
}
It's not a very useful panel. The object it returns
can, apart from providing the panel's DOM structure, configure whether
the panel should be at the top or bottom of the editor.
Next we define a key binding that makes F1 toggle
the field on and off.
const helpKeymap = [{
key: "F1",
run(view) {
view.dispatch({
effects: toggleHelp.of(!view.state.field(helpPanelState))
})
return true
}
}]
And tie everything together in the helpPanel
function, which creates
the extension that enables the field, the key binding, and a simple
styling for the panel.
import {keymap} from "@codemirror/view"
const helpTheme = EditorView.baseTheme({
".cm-help-panel": {
padding: "5px 10px",
backgroundColor: "#fffa8f",
fontFamily: "monospace"
}
})
export function helpPanel() {
return [helpPanelState, keymap.of(helpKeymap), helpTheme]
}
Dynamic Panel Content
It is often necessary to keep the content of a panel in sync with the
rest of the editor. For this purpose, the object
returned by a panel constructor may have an
update
method that, much like the update
method in view plugins, gets called every time the editor view
updates.
Here we'll build a little extension that sets up a word-counting panel.
First we need a (very crude, entirely Unicode-unaware) function that
counts the words in a document.
import {Text} from "@codemirror/state"
function countWords(doc: Text) {
let count = 0, iter = doc.iter()
while (!iter.next().done) {
let inWord = false
for (let i = 0; i < iter.value.length; i++) {
let word = /\w/.test(iter.value[i])
if (word && !inWord) count++
inWord = word
}
}
return `Word count: ${count}`
}
Next, a panel constructor building a panel
that re-counts the words every time the document changes.
import {EditorView, Panel} from "@codemirror/view"
function wordCountPanel(view: EditorView): Panel {
let dom = document.createElement("div")
dom.textContent = countWords(view.state.doc)
return {
dom,
update(update) {
if (update.docChanged)
dom.textContent = countWords(update.state.doc)
}
}
}
And finally, a function that build the extension that enables the
panel in an editor.
import {showPanel} from "@codemirror/view"
export function wordCounter() {
return showPanel.of(wordCountPanel)
}