Form โ
A headless form controller โ there is no mono-form element. You author a native <form> and place controls wherever you like; controlMonoForm owns the value, the validation, the cross-field reactions and the props pushed onto each control.
Controls opt in with two props: :control-form (the controller) and key-form (which field it is). That's the whole binding โ no :model-value, no @mno-change, because the form owns the value and writes it into the control.
Control โ
controlMonoForm owns the value + validation; each mono-input / mono-select / โฆ opts in with :control-form + key-form, and the live items() readout is yours to use.
Basic โ
Values, rules and the live items() readout. Rules run live (every keystroke, on mno-input) or on change (commit/blur, on mno-change); set the default with validation.type and override it per rule with timing. form.validate() runs every rule regardless โ that's the submit sweep. Only the first failing rule is reported per field, since the controls render a single message.
Cross-field watchers โ
A field's watcher runs for every change in the form: its own (peerKey === selfKey) and every other field's (peerKey = the field that changed). That fan-out is what lets one field react to another with no separate peer registry. event and peerEvent carry the originating DOM CustomEvent, and are undefined after a programmatic setValue.
External refs and reactivity โ
external takes anything ref-like (a Vue ref qualifies), read from watchers as external.<name>; call form.refresh() when it changes. Pass a state ref and the form writes a fresh snapshot into it on every change, so a template re-renders with no manual subscription โ form.subscribe(fn) is the lower-level primitive.
Prop precedence โ
inputs[key].props (static) โ the setProp() method (from watchers or outside) โ your template bindings. The static props always wins, so a watcher can't override a prop the config also declares. Same key name and role as controlMonoTable({ props }).
Visibility โ
Every supported control takes :visible (default true) and :visible-type, so a conditional field is a line of form config instead of a v-if in the template โ and the control stays registered with the controller while hidden.
:visible-type="'none'" (the default) applies display: none, removing the control from layout so it leaves no gap. :visible-type="'invisible'" applies visibility: hidden, keeping its space so nothing around it shifts. Both are written on the host element, which is what lets none release its cell in a grid or flex form.
ts
form.setProp({ key: 'TaxId', props: { visible: false } })Two things to know. The prop is purely visual โ a hidden field keeps its value and its rules still run, so a hidden required field will still fail form.validate(); drop the rule (or skip the key) yourself if that isn't what you want. And because a static inputs[key].props always wins (see above), a field you intend to toggle must not declare visible there.
Under SSR, a mono-shadow-* control that is server-rendered with visible: false ships visible and hides once it hydrates โ Lit SSR doesn't run the lifecycle that writes the style. Toggling at runtime, which is what this prop is for, is unaffected.
Supported controls โ
mono-input, mono-textarea, mono-select, mono-tag-input, mono-date, mono-file-upload and mono-dropdown-table support the full set โ value, props and the rendered validation message.
mono-checkbox, mono-radio and mono-switch are wired for value and props only: they have no validation-state / validation-message props today, so setValidation on those keys updates form.items() but has nothing to render on the control. Read it from items() and render your own message until those props exist.
Types โ
controlMonoForm is also exported as monoForm, and :control-form is also accepted as :data-form โ the older spellings still work.
Import
import { MonoFormOptions, MonoFormInput, MonoFormWatcherCtx, MonoFormItem, MonoFormController } from 'mono-helper'| Prop | Value | Default | Description |
|---|---|---|---|
controlMonoForm(options) | |||
validation | { type?: MonoFormTiming | undefined; } | โ | Default rule timing for the whole form. Defaults to `'change'`. |
inputs | Record<string, MonoFormInput<keyof MonoFormComponentProps, unknown>> | โ | โ |
external | Record<string, unknown> | โ | Ref-like values readable from watchers as `external.<name>`. |
state | MonoFormRefLike<Record<string, MonoFormItem<unknown>> | undefined> | โ | A ref the form writes a fresh snapshot into on every change, so a Vue template can read `state.Name.validate.message` and re-render without any manual subscription. |
inputs[key] | |||
component | C | โ | Narrows the TS type of `props`. Optional โ omit for a loose record. |
props | MonoFormProps<C> | โ | Static props for this field's control that ALWAYS win โ over the `setProp()` method and over template bindings. Same name and role as `monoDataGrid({ props })`. |
value | V | โ | Initial value for the field. |
validates | MonoFormRule<V>[] | โ | Rules for this field, checked in order โ the FIRST failure is reported, since a control renders one message. Each entry is a built-in `type`, a `type: 'custom'` function, or a `schema` (which wins over both). |
watcher | (ctx: MonoFormWatcherCtx) => void | โ | Runs when THIS field changes (`peerKey === selfKey`) and whenever any OTHER field changes (`peerKey` = that field) โ which is what lets one field react to another without a separate peer-watcher registry. |
watcher(ctx) | |||
selfKey | string | โ | The field this watcher belongs to. |
peerKey | string | โ | The field that actually changed (equals `selfKey` for a self change). |
currentValue | unknown | โ | Value of `selfKey`. |
oldValue | unknown | โ | โ |
peerCurrentValue | unknown | โ | Value of `peerKey` โ the field that changed. |
peerOldValue | unknown | โ | โ |
event | Event | โ | The DOM `CustomEvent` (`mno-input` / `mno-change`) that last touched `selfKey`. `undefined` when this field hasn't been edited by hand yet, or when its value was written programmatically via `setValue` / `setValues`. |
peerEvent | Event | โ | The DOM `CustomEvent` that caused THIS run โ the one that changed `peerKey`. Same object as `event` on a self change. `undefined` for a programmatic write. |
values | Record<string, unknown> | โ | Every field's current value. |
external | Record<string, unknown> | โ | Unwrapped `external` refs. |
setProp | <C extends MonoFormComponent = keyof MonoFormComponentProps>(arg: MonoFormSetProp<C>) => void | โ | Push props onto any field's control. NOTE: a static `inputs[key].props` declares the same thing and ALWAYS wins over this one. |
setValidation | (arg: MonoFormSetValidation) => void | โ | Mark any field valid/invalid โ the message renders on that control. |
setValue | (key: string, value: unknown) => void | โ | Set another field's value (runs its rules + fans out again). |
items()[key] | |||
key | string | โ | The field name โ the same string used as `key-form` on the control. |
currentValue | V | โ | The value right now. |
oldValue | V | โ | The value before the most recent change. |
validate | MonoFormValidation | โ | Current verdict โ from the field's rules, or from `setValidation`. |
touched | boolean | โ | Whether the field has been committed at least once (drives nothing yet; useful to consumers). |
props | Record<string, unknown> | โ | The merged props currently pushed onto the element. |
controller | |||
items | () => Record<string, MonoFormItem<unknown>> | โ | Live state for every field (stable object, mutated in place). |
values | () => Record<string, unknown> | โ | Just the values, `{ Name: 'Denis', Age: 18 }`. |
setValue | (key: string, value: unknown) => void | โ | โ |
setValues | (values: Record<string, unknown>) => void | โ | Bulk-set values, e.g. seeding an edit form from a fetched record. |
validate | () => Promise<boolean> | โ | Run EVERY rule on every field regardless of timing. Resolves to overall validity. |
isValid | () => boolean | โ | Whether every field currently passes. |
reset | () => void | โ | Clear values + validation back to the declared defaults. |
setProp | <C extends MonoFormComponent = keyof MonoFormComponentProps>(arg: MonoFormSetProp<C>) => void | โ | Push props onto a field's control. `inputs[key].props` still wins over this. |
setValidation | (arg: MonoFormSetValidation) => void | โ | Mark any field valid/invalid, e.g. from a server response. |
refresh | () => void | โ | Re-read `external` refs and re-run watchers. |
bindRef | (ref: MonoFormRefLike<Record<string, MonoFormItem<unknown>> | undefined>) => () => void | โ | Write snapshots into `ref` on every change (same as the `state` option). |
subscribe | (cb: () => void) => () => void | โ | โ |
dispose | () => void | โ | โ |