Visible history and state restoration
FormBuilder records every schema mutation in a history isolated by builder-id. The History panel turns the internal undo/redo stack into a user-facing timeline.
The current schema, steps, columns and fields remain serializable. History is runtime in-memory data and is never added to exported JSON.
Control the panel
<script setup lang="ts">
const historyOpen = ref(false)
const historyDepth = ref(50)
</script>
<template>
<FormBuilder
builder-id="customer-form"
v-model:history-open="historyOpen"
v-model:history-depth="historyDepth"
/>
</template>Related props:
| Prop | Default | Purpose |
|---|---|---|
history-open | false | Controls whether the panel is open. |
history-depth | 50 | Maximum retained states, clamped between 2 and 500. |
history-shortcuts | true | Enables undo/redo keyboard shortcuts. |
Depth changes are applied immediately. Lowering the value removes the oldest states while preserving the current state.
Timeline and business labels
Each entry contains:
- a stable id;
- a sequence number;
- a timestamp;
- a typed business action;
- serializable context;
- an independent schema snapshot;
- an optional custom label and merge key.
The timeline can display labels such as:
- “Field added: email”;
- “Column moved: contact details”;
- “Step duplicated: Review”;
- “Schema imported”;
- “State #12 restored”.
Labels are resolved when rendered with the active locale. History therefore does not store a frozen translation.
Keyboard shortcuts
When history-shortcuts is true:
| Shortcut | Action |
|---|---|
Ctrl+Z or Cmd+Z | Undo. |
Ctrl+Shift+Z or Cmd+Shift+Z | Redo. |
Ctrl+Y | Redo. |
The handler ignores events originating from input, textarea, select, contenteditable editors or elements with role="textbox". A shortcut typed inside a field keeps editing that field instead of changing the builder schema.
When multiple builders share one page, the last builder activated by pointer or focus owns the shortcuts. This prevents one Ctrl+Z from changing several instances.
Undo, redo and branches
Undo moves the pointer to the previous state. Redo moves it to the next state.
When a change is made after an undo, the previous future states are removed and a new branch starts from the current state, as in a conventional editor.
Rapid edits of the same property use a mergeKey and are coalesced within a short window. Typing several characters in a field label therefore does not create one entry per keystroke.
Explicitly restore a state
The Restore this state action requires confirmation. Restoration does not silently move the pointer to an old entry: it appends a new history-restore entry to the timeline.
The restoration itself therefore remains undoable.
Public API
<script setup lang="ts">
import type {
FormBuilderPublicApi,
QFormBuilderHistoryChangeEvent,
QFormBuilderHistoryNavigationEvent,
} from '@vevedh/qform-builder-layer/types'
const builder = ref<FormBuilderPublicApi | null>(null)
function restoreFirstState() {
const history = builder.value?.getHistory()
const first = history?.entries[0]
if (first) builder.value?.restoreHistoryEntry(first.id)
}
function handleHistoryChange(event: QFormBuilderHistoryChangeEvent) {
console.log(event.builderId, event.snapshot.pointer)
}
function handleUndo(event: QFormBuilderHistoryNavigationEvent) {
console.log('Active state', event.entry.sequence)
}
</script>
<template>
<FormBuilder
ref="builder"
builder-id="history-api-demo"
@history-change="handleHistoryChange"
@history-undo="handleUndo"
/>
<QBtn label="Restore first state" @click="restoreFirstState" />
</template>Exposed methods:
getHistory(): QFormBuilderHistorySnapshot
undo(): QFormBuilderHistoryEntry | null
redo(): QFormBuilderHistoryEntry | null
restoreHistoryEntry(entryId: string): QFormBuilderHistoryEntry | null
clearHistory(): void
setHistoryDepth(depth: number): number
openHistory(): void
closeHistory(): voidEvents:
history-changewhenever entries, pointer or depth change;history-undoafter a successful undo;history-redoafter a successful redo;history-restoreafter explicit restoration.
Public helpers
The history subpath exposes the pure engine for tests and advanced integrations:
import {
appendQFormBuilderHistory,
createQFormBuilderHistoryState,
formatQFormBuilderHistoryLabel,
normalizeQFormBuilderHistoryDepth,
restoreQFormBuilderHistory,
} from '@vevedh/qform-builder-layer/history'Persistence and limits
History intentionally stays in memory:
- autosave does not write it to
localStorage; - schema export does not include it;
- it is not automatically sent to the NFZ
formsservice; - it disappears when the instance is permanently unmounted.
For durable auditing, persist business revisions on the server in a dedicated Feathers/NFZ service. Do not confuse editor convenience history with a regulatory audit trail.
Best practices
- Use a stable
builder-id. - Keep a depth between
30and100for most forms. - Disable
history-shortcutswhen the host application already owns global shortcuts. - Require explicit confirmation before restoring a timeline state.
- Never use client history as an authorization, persistence or audit-proof mechanism.