Primitives / State

history: undo and redo over any value

ts
import { history } from 'foldkit-primitives/state'

const EditHistory = history({ name: 'EditHistory', value: Schema.String, capacity: 50 })

const Editor = Bundle.compose({}).pipe(
  Bundle.withChild('doc', EditHistory, { args: { initial: '' } }),
)

The Model holds { past, present, future, group }. Push records a value and drops the redo future (an already empty future is kept as it was, so a view reading it is not redrawn per push); Undo and Redo move one step; GoTo { step } jumps to any kept value, counted from the oldest (0), so the present is at past.length; Clear empties both sides and keeps the present. The past holds at most capacity entries (default 100); a negative or fractional capacity throws at the factory. The factory attaches the Message union, so a placement dispatches EditHistory.Message.Push({ value }). canUndo and canRedo read the edges.

A Push may name a group: consecutive pushes of the same group are one step, so typing a word, or dragging a brush, undoes as a whole, with no clock.

The steps are also pure functions, History.start, push, undo, redo, goTo, and clear, for a parent that records an edit in the same transition that makes it. Two more exist only as functions: History.close(model) ends the current group, so the same group pushed again is a step of its own, and History.revert(model, group) takes back the step group is making with nothing left to redo, which is what a cancelled edit leaves. examples/foldkit/pixel-art keeps its grid this way, one group per stroke.

Locale and SelectionSet

Locale keeps one string, read from navigator.language at init with the configured default as fallback; SetLocale switches it. It does not follow later changes.

SelectionSet keeps string ids in first-selection order: Select keeps an existing id's position, Deselect removes, Toggle re-appends, ReplaceAll replaces (deduplicated), Clear empties. isSelected(model, id) reads membership. It stores ids only; it neither loads rows nor persists. For a selection a view's slots must reflect, with aria-selected and Shift ranges, see Selection in interaction.