Skip to content

Keyboard Handling

Markput handles text input, deletion, paste, overlay insertion, block editing, and mark commands through core-owned raw positions.

  1. React/Vue render adapter-owned token shells and text surfaces.
  2. The adapter registers the root with store.host.container and child structure through store.refs.control(path?) (for non-editable controls inside a token) and store.refs.children(ownerPath) (for nested __slot__ child sequence hosts).
  3. Keyboard handlers convert the browser selection to a raw serialized range through store.selection.readRaw() or store.selection.rawPositionFromBoundary().
  4. Edits call store.value.replace() and optionally write store.selection.range() to set the post-edit caret.
  5. SelectionController applies selection.range to the DOM after the next render.

Production code should not infer token identity from DOM child order or public data attributes.

Inline text input uses the current raw selection:

store.value.replace(selection.range, text)
store.selection.range({start: selection.range.start + text.length, end: selection.range.start + text.length})

Controlled editors emit onChange first and update the accepted value after the matching prop echo.

Collapsed Backspace/Delete uses raw position boundaries. If the adjacent token is a mark, core deletes the whole mark range. If the adjacent token is text, core deletes the relevant character or selected raw range.

Use useMark() for mark-specific actions:

import {useMark} from '@markput/react'
function RemovableMention() {
const mark = useMark()
return (
<button type="button" onClick={() => mark.remove()}>
@{mark.value}
</button>
)
}

To update a mark, call mark.update():

mark.update({value: 'alice'})
mark.update({meta: {kind: 'clear'}})

The hook no longer exposes a DOM ref. Focus moves through registered token shells and text surfaces owned by the adapters.

Overlay trigger probing uses the current raw caret position (caret.selection()). During input, core probes the caret range which is updated synchronously with value edits.

Attach custom handlers to the container through slotProps.container, but let Markput own text mutation:

<MarkedInput
slotProps={{
container: {
onKeyDown(event) {
if (event.key === 'Escape') {
// custom behavior
}
},
},
}}
/>

If a handler changes editor text, route it through component state (value/onChange) or a mark command. Do not mutate parsed tokens directly.