fill-stateLast updated on
Last updated on
Track form completion progress with FillState
Query the fill state of a DraftForm to determine what's been filled, what's still needed, and what to ask next. FillState evaluates conditional visibility, required expressions, and dependency chains to give an accurate picture of form progress.
Examples
Basic usage
const draft = form.fill({
fields: { tenantName: 'John Doe' }
})
const state = draft.getFillState()
state.summary.completionPercent // e.g. 25
state.summary.requiredRemaining // e.g. 3
state.next // { kind: 'party', key: 'tenant', required: true, order: 0 }Progressive fill loop
let draft = form.fill()
while (true) {
const state = draft.getFillState()
if (state.summary.requiredRemaining === 0) break
const target = state.next
if (!target) break
const value = await collectValueFromUser(target)
draft = draft.update({
[target.kind === 'party' ? 'parties' : target.kind === 'annex' ? 'annexes' : 'fields']: {
[target.key]: value
}
})
}
// Full readiness is a separate check from draft acceptance.
if (!draft.isValid()) throw new Error('Complete the remaining required values')Including optional targets
By default, candidates only includes required targets. Pass includeOptional: true to include optional fields:
const state = draft.getFillState({ includeOptional: true })
// state.candidates now includes both required and optional targets
// Required targets are listed first (requiredFirst defaults to true)Checking blocked items
Items with conditional visibility that depends on unfilled fields appear in blocked:
const state = draft.getFillState()
for (const item of state.blocked) {
console.log(`${item.key} is hidden, waiting on: ${item.blockedBy.join(', ')}`)
}
// e.g. "spouseName is hidden, waiting on: maritalStatus"Checking rules
Each failed rule comes back as a RuleValidationResult, the same object draft.validateRules() returns, so you can tell which rule failed:
const state = draft.getFillState()
for (const error of state.rules.errors) {
console.log(`${error.ruleId}: ${error.message}`)
}
// e.g. "depositWithinLimit: Pet deposit cannot exceed $500"Checking logic issues
An expression that fails to evaluate, such as a computed value that divides by zero, is not a rule result. It is reported in issues, and it blocks completion:
for (const issue of state.issues) {
console.log(`${issue.path.join('.')}: ${issue.message}`)
}
// e.g. "defs.share: Division by zero"When a visibility or requiredness condition fails, the form's logic cannot be resolved. The rules are not run, rules.valid is false with no errors, and issues says why.
API
FillState
Returned by draft.getFillState().
FillStateSummary
RuleValidationResult
One rule's result. rules.errors and rules.warnings hold only failed rules.
EvaluationIssue
FillTarget
A candidate target that can be filled next.
FillItemState
Extended fill target with runtime state. Used in openRequired, openOptional, blocked, and done arrays.
FillTargetKind
type FillTargetKind = 'field' | 'party' | 'annex'FillTargetOptions
Options for getFillState, getNextFillTarget, and getAvailableFillTargets.
FillOptions
Options for fill and safeFill on FormInstance.
SafeFillResult
Returned by safeFill and safeUpdate.
type SafeFillResult<F extends Form> =
| { success: true; data: DraftForm<F> }
| { success: false; error: Error }How visibility and blocking work
FillState evaluates conditional visible and required expressions on fields, parties, and annexes using the current data. Items are categorized as:
- openRequired — visible, required, not yet filled. These are what the user needs to answer.
- openOptional — visible, optional, not yet filled. Available but not blocking completion.
- blocked — not visible because a dependency hasn't been filled yet. The
blockedByarray lists the transitive chain of unfilled prerequisites: if A gates B and B gates C, C'sblockedByincludes both A and B. Blocked items unblock automatically when their dependencies are filled. - done — already filled, regardless of visibility.
Each item also carries a status ordinal (hidden < optional < required) that folds visible and required together; required implies visible, so a "hidden but required" item cannot exist.
The candidates array contains all open (visible, unfilled) targets in dependency (DAG) order: prerequisites come before the items they gate, and declaration order breaks ties within a rank. When requiredFirst is true (the default), required targets appear before optional ones. next is the first candidate, so following next repeatedly walks the form in an order where nothing is asked before what it depends on.