Repeatable sub-form bound to an array attribute (§7.3).
Note the ./title in the child rule: inside an item, ./-prefixed paths
are row-relative and everything else addresses the root form state.
| Builder | F.repeater(attribute: string, schema: readonly SchemaComponent[] = []): Repeater |
| Wire type key | repeater |
| Class chain | Repeater → Field |
| Detail projection | repeater-entry |
| Column counterpart | — (not tabulatable) |
Example
import { F, when } from '@adonia/core'
F.repeater('items', [
F.text('title').required(),
F.number('qty').min(1).required(),
F.text('note').visibleWhen(when('./title').isTruthy()),
])
.min(1)
.max(20)
.reorderable()
.collapsible()
.itemLabel('title')
.defaultItems(1)Props
Beyond the shared field DSL:
| Prop | Declared on | Behaviour |
|---|---|---|
min(count: number): this |
Repeater |
Minimum number of rows; compiles to the array’s minLength (§11.1). |
max(count: number): this |
Repeater |
Maximum number of rows; compiles to the array’s maxLength (§11.1). |
reorderable(): this |
Repeater |
Lets the user drag rows into a new order (order is the array order). |
collapsible(): this |
Repeater |
Lets the user fold individual rows away. |
itemLabel(source: string | ItemLabelFn): this |
Repeater |
Names each row. |
table(): this |
Repeater |
Compact layout: rows render as table lines rather than stacked cards. A rendering hint only — the state shape and the validator are identical either way. |
defaultItems(count: number): this |
Repeater |
Pre-populates count blank rows in create mode (protocol §7): the node’s default becomes an array of Repeater.blankItems, so a form that always has at least one row does not make the user press “add” first. |
Introspection
Read-only members this type adds — used by the compiler, the table derivation and tests rather than by a schema author.
| Member | Declared on | Behaviour |
|---|---|---|
nestedSchema(): readonly SchemaComponent[] |
Repeater |
The item sub-form (protocol §2 children of this node). Declared here rather than as a LayoutComponent’s children because this is a STATE boundary: the descriptor compiler emits the sub-form as children but stops collecting stateKeys, and collectFields never descends. |
blankItem(): RepeaterItem |
Repeater |
A fresh empty row: every child key present, holding its declared default or explicit null. |
labelFor(item: RepeaterItem, index: number): string |
Repeater |
The human name of one row, server-side: the itemLabel closure when declared, otherwise the named child key’s value, otherwise the ordinal. |
Validation
§11.1 mapping: vine.array(vine.object(childSchema)).minLength(min) .maxLength(max).
The item object comes from buildFieldObjectSchema — the very function
that compiles the resource’s own validator — so each child contributes
its OWN buildValidation and the §11.2 conditional-group machinery
(prune, bucketed vine.group, native requiredWhen) applies inside an
item exactly as it does at the top level. scoped tells those group
predicates that Vine hands them the ITEM, which is the ./ row scope.
Recursion is free: a child repeater’s vineSchema runs the same path,
which is why items.0.children.1.name validates and reports correctly
without a single nesting-aware branch anywhere.
Contributed by Repeater.vineSchema() in packages/core/src/schema/fields/repeater.ts. The shared modifiers (required, nullable, requiredWhen, unique, optionality) are applied on top by Field.buildValidation, and a rules() override replaces the base entirely — the validator compiler never branches on the field class.
Declared in
packages/core/src/schema/fields/repeater.ts—Repeater