Block capability contract
The types below come from packages/editor/src/core/capabilities.ts and are exported from @skaile/forge-editor/core. For how to declare a capability and use it, see Block capabilities, abilities and actions.
BlockCapability
A Feature declares capabilities in EditorFeature.blockCapabilities. defineEditor merges them with the built-in ones into preset.capabilities and throws for a duplicate id or an id of a built-in capability.
| Field | Type | Meaning |
|---|---|---|
id | string | Unique across the preset. A Block type's capabilities entry and a BlockAction.requires or DeriveMethod.requires entry name it. |
appliesTo | (node, ctx: CapabilityContext) => boolean | Whether node has the capability. Memoised per node type and Block type id, so it may read only the node type (spec and flags), ctx.blockTypeId and the Block type's config (ctx.configOf). |
perNode | boolean? | appliesTo reads attributes of the node itself. It then runs for every node, so keep it a constant-time attribute check. |
configSchema | ZodType? | Validates the config a Block type gives. Absent: the capability takes no config, and a Block type that gives one is refused. |
read | (node, ctx) => unknown? | What the Block holds for the capability (Source child, Basket id, Bibliographic record), or null. Runs only for Blocks that have the capability; not memoised. |
storage | { metaKeys?, childTypes? }? | Where the data lives: keys of the meta attribute, node type names of the children. Descriptive only. |
A capability never installs schema, extensions, node views or settings.
CapabilityContext
| Field | Meaning |
|---|---|
blockTypeId | The persisted attrs.blockType of the node; null for a node without one. |
blockTypeOf(node) | The Block type of node among the editor's Block types; null for a node without one, an unknown id, or an editor without Block types. |
configOf(node, capability) | The config the Block type of node gives capability; undefined when the type does not list it or lists it without config. |
listsCapability(ctx.blockTypeOf(node), id) is the usual appliesTo of a capability a Block type opts into.
Registry and functions
| Export | Does |
|---|---|
BUILT_IN_BLOCK_CAPABILITIES | the structural capabilities every editor has (container, title) |
createCapabilityRegistry(capabilities) | a CapabilityRegistry: capabilities (built-in first, then the Features' in feature order), has(id), get(id) |
createCapabilityIndex(registry, blockTypes) | the per-editor CapabilityIndex: of(node), read(node, id), configOf(node, id). It depends on the editor's Block types, so editors do not share it. |
capabilitiesOf(editor, node) | ids of the capabilities node has |
capabilitiesAt(editor, pos) | the same for the Block at pos; empty where no node starts |
capabilityConfigOf(editor, node, id) | the config the Block type gives the capability, or undefined |
capabilityDataOf(editor, node, id) | what read returns, or null |
validateBlockTypes(registry, blockTypes) | throws when a Block type id is defined twice, a listed capability is unknown or listed twice, or a config fails its configSchema. capabilityIssues(registry, type) is the same check for one Block type, and returns the problems instead of throwing. defineEditor runs validateBlockTypes for the Block types of the Features when an editor is created. |
blockTypeConfigOf(type, id) | the config type gives the capability, or undefined (also without a type) |
listsCapability(type, id) | whether type lists the capability; false without a type |
typeIdOf(node) (the Typed block module of @skaile/forge-editor/core) gives the attrs.blockType of a node ('' for a node without one); findBlockType(blockTypes, node) is the Block type lookup. BlockTypeCapability ({ id, config? }) and BlockTypeData ({ id, capabilities }) are what the registry knows of a Block type; the public BlockType extends BlockTypeData (Block type contract).
BlockAction.requires
BlockAction.requires?: readonly string[] lists capability ids. The action is hidden on a Block that lacks one of them. blockActionsOf(preset, ctx) throws when an id is declared by no Feature; defineEditor throws the same for a derive method. It replaces node-name checks in isVisible. blockTypes?: readonly string[] limits an action to named Block types and is for actions that are truly specific to a type. ability is a separate gate: requires is what the Block is, ability what its place in the document allows (abilitiesAt(editor, pos, node)); it hides the action but refuses no edit (Abilities).
Built-in capabilities
container and title are built in. The composites feature declares the others (compositeBlockCapabilities()), so a Block type file that is parsed with the default registry may name all of them. A Block type that lists a capability its node cannot hold (for example source on a type without a container body) has no effect.
| Id | Config (configSchema) | Applies to | read returns |
|---|---|---|---|
container | { accepts?: string[] | 'all' }: the Block types accepted as nested Typed blocks | nodes that hold Blocks of their own (doc, a Typed block) | none |
title | none | nodes whose content starts with chrome (the title row of a Typed block) | none |
properties | { definitions: PropertyDefinition[] } | a node with the meta attribute, if its Block type lists it | the meta values of the keys the definitions name |
source | { media: ('image' | 'audio' | 'video' | 'file')[] } (at least one) | a container node, if its Block type lists it | the Source media block, or null while the body holds none |
bibliography | none | a node with meta, if its Block type lists it | the CSL-JSON item (meta.bib), or null |
basket | none | a node with meta, if its Block type lists it | the Basket id (meta.basket), or null |
titleHidden | none | a node with meta, if its Block type lists it | whether the title is hidden (meta.titleHidden) |
ai-brief | { purpose, instructions?, instructionsField?, contentTemplate? } | any node whose Block type lists it | none |
references | none | a node with meta, if its Block type lists it or one of its Property definitions has the type reference | none |
import | { media?: ('image' | 'audio' | 'video' | 'file')[] } | a container node, if its Block type lists it | none |
export | none | a container node, if its Block type lists it | none |
derived | none | a node with meta whose meta.derivation is a Derivation (perNode; any Typed block can be derived, whatever its Block type lists) | the Derivation |
Storage of the built-ins that keep data: properties and derived use meta (derived: the key derivation), bibliography uses meta.bib, basket uses meta.basket, titleHidden uses meta.titleHidden, source keeps its media block in the body (childTypes image, audio, video, file), references uses reference nodes.
titleHidden gates the eye button of the chrome („Titel ausblenden“ / „Titel einblenden“, Vue and React CompositeChrome): a Typed block of a Block type that does not list it shows no button, so a host that shows or edits the title elsewhere can leave it out. The flag itself (meta.titleHidden) is a reserved key that every Block type keeps, and setCompositeTitleHidden works for every type; every built-in Block type lists the capability.
ai-brief is what the Block type tells the model: purpose goes into every AI request's block model; instructions, instructionsField and contentTemplate are read only by the AI fill (Block types).