Value Sources
A value source gives an entry one value: static, or computed by a JavaScript expression. Three forms:
ValueSource = { expr } | { literal } // props
ValueSourceAssignment = ValueSource & { set } // seed steps
CallbackValueSourceAssignment = ValueSource & { set?, confirm?, debounce? } // callback stepsA callback step may drop set and run only for its effect; a seed step
cannot. Entry fields covers which field takes which form.
ValueSource
expr takes any JavaScript expression.
{ "literal": "Hello" } // any JSON value, used as-is
{ "expr": "scopes.root.title" } // a JavaScript expression, evaluated against the scopesValueSourceAssignment
A ValueSource plus set: evaluate, then write the field set names,
scopes.<scope>.<field>. Never deeper, never a number: scopes.root.rows.0.qty
is rejected. Edit a row through its scope, or replace the array
(why).
{ "set": "scopes.root.count", "literal": 0 }
{ "set": "scopes.root.itemCount", "expr": "scopes.cart.items.length" }
{ "set": "scopes.root.count", "expr": "currentValue + 1" } // read-modify-writecurrentValue is the value now at the set path, the one about to be
replaced, so a read-modify-write never names the path twice. Only set defines
it, so a bare props, hidden,
loading or each expression cannot use it:
// ❌ no `set` here — throws: "currentValue" is not available in expressions
{ "hidden": "!currentValue" }A callbacks step overwrites. A seed step is first-writer-wins: it
assigns only if the path is still undefined:
{ "key": "filters", "component": "Card", "children": ["status-input"],
"seed": [{ "set": "scopes.root.status", "literal": "open" }] }
{ "key": "status-input", "component": "Input",
"seed": [{ "set": "scopes.root.status", "literal": "all" }],
"props": { "expr": "({ value: scopes.root.status })" } }The input renders open: the parent seeds first, so the child’s seed is a
silent no-op. See lifecycle.
CallbackValueSourceAssignment
confirm gates the step behind a confirmation message:
{ // …
"callbacks": { "onClick": [
{ "expr": "deleteUser({ id: scopes.row.id })",
"confirm": "Delete this user? This cannot be undone." },
{ "set": "scopes.root.users", "expr": "listUsers()" }
] } }The first step has no set, so the delete’s return value is discarded.
Declining runs neither step. A confirmed step is also an
ordering barrier: everything before it settles first, and
nothing after it starts until it passes.
debounce delays the step, and every step after it, until the callback has been
quiet for 300 ms. A newer call replaces the pending run, so only the last one
fires, with its own evt. Steps before it run at once.
{ // …
"callbacks": { "onChange": [
{ "set": "scopes.root.q", "expr": "evt.value" },
{ "set": "scopes.root.rows", "expr": "searchRows({ q: scopes.root.q })",
"debounce": true }
] } }q is written on every keystroke; the search runs once typing pauses.
Unmounting the element cancels a pending run.