Behaviors
Behaviors allow you to customize how users interact with the editor by hooking into events during the editing experience.
All behaviors follow this process:
- Listen for an event.
- Use a guard to decide if they should run or not.
- Trigger a set of actions to perform on the editor.
This pattern is influenced by Statecharts.
Behaviors are defined with the defineBehavior helper. Here’s an example event from the behavior guide:
defineBehavior({ on: 'insert.text', guard: ({snapshot, event}) => event.text === 'a', actions: [ ({snapshot, event}) => [ {type: 'execute', event: {type: 'insert.text', text: 'A'}}, ], ],})Revisiting the three step process above:
onlistens for the event.guardhandles the conditional.actionssends, or invokes, the desired actions.
Events
Section titled “Events”Whenever you enter text into the editor, activate a toolbar button, or anything else happens in the editor it sends an event.
There are three categories of Behavior Events:
- Native Events: Events that come from the browser or device directly.
- Synthetic Events: Editor-specific events that directly modify the editor state.
- Custom Events: Events that you create yourself.
The Behavior API uses events to trigger actions by listening for a specific event.
Guards
Section titled “Guards”A guard is a condition that helps the behavior determine if it should perform the actions.
The guard key expects a response or false. The example above shows a simple truthy guard. Here it is again:
guard: ({snapshot, event}) => event.text === 'a'Guards can also return parameters that you can access when firing an action.
guard: ({snapshot, event}) => { if (event.text === 'a') { return { secret: 'secret text' } } return false},actions: [ ({snapshot, event}, {secret}) => [ /* ... */ ]]Passing parameters allows you to reuse conditional behavior, such as selecting part of a string, without rewriting the logic.
Guards are optional. This means it’s possible to create an unconditional behavior that always runs when an event occurs. The “soft return” behavior build into the PTE is one example:
const softReturn = defineBehavior({ on: 'insert.soft break', actions: [() => [execute({type: 'insert.text', text: '\n'})]],})This behavior listens for soft break events and uses the insert.text action to insert a \n instead to prevent splitting text blocks with Shift+Enter. These unconditional behaviors are rare and you’ll mostly encounter conditional behaviors.
Actions
Section titled “Actions”Actions make things happen in the editor. This is where you change the standard behavior by modifying actions before they occur or by circumventing them completely. You’ve seen actions in some of the guard examples above.
The actions key expects an array of behavior action sets. As with guards, you have access to the event and snapshot.
So far we’ve only seen examples that invoke a single action, but you can send multiple actions or sets of actions from a single event.
Raise events within actions
Section titled “Raise events within actions”To send an event back into the editor, use the raise action type or the raise helper. This is useful for chaining behaviors and default events.
// Approach A: With the type set to 'raise'// Approach B: With the raise helperimport {defineBehavior, raise} from '@portabletext/editor/behaviors'
const raisedUppercaseA = defineBehavior({ on: 'insert.text', guard: ({snapshot, event}) => event.text === 'a', actions: [ ({snapshot, event}) => [ {type: 'raise', event: {type: 'insert.text', text: 'A'}}, ], ],})
const raisedUppercaseA = defineBehavior({ on: 'insert.text', guard: ({snapshot, event}) => event.text === 'a', actions: [({snapshot, event}) => [raise({type: 'insert.text', text: 'A'})]],})Claiming the native event
Section titled “Claiming the native event”When a Behavior’s guard matches an event that carries a native event (a keyboard.keydown, for example), the Behavior claims it: the editor calls preventDefault() and the browser’s default action is suppressed. The action sets can revise that claim. Every action set that returns actions casts a vote, and the last vote wins:
| The action set | Native default |
|---|---|
contains raise/execute |
prevented |
contains only forward |
passes down the chain |
contains only effects |
prevented |
| returns nothing, or throws | keeps the previous decision |
Within one set, raise and execute outrank forward: a set that both mutates and forwards still prevents the default.
forward does not mean “let the browser act”. It means “no opinion at this level”: the event moves on to the next Behavior in the chain, which makes its own decision, and the browser’s default only survives when no Behavior along the chain claims the event.
Multiple action sets are also an undo boundary: each set after the first starts a new undo step. @portabletext/plugin-input-rule uses this to let the typed text land as its own undo step before a second set transforms it, so undo peels the transformation off first.
Selectors
Section titled “Selectors”Selectors are pure functions that derive state from the editor snapshot (snapshot in the examples). A collection of selectors is included with the core library.
// import all selectorsimport * as selectors from '@portabletext/editor/selectors'// or individual onesimport {getFocusSpan, getFocusTextBlock} from '@portabletext/editor/selectors'The core selectors are useful helper functions for checking conditions of the editor, finding selected text, and more. You can manually do anything a selector can do by parsing the editor snapshot.
Behavior examples
Section titled “Behavior examples”Browse the existing behaviors, or check out the Behavior Recipes documentation for examples of real-world behaviors.