> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Field state

> A2InputFieldState extends formstate's FieldState with warnings, disablers, and update-mode support.

`A2InputFieldState<T>` extends [`formstate`](https://formstate.github.io/)'s `FieldState<T>` and adds non-blocking warnings, dynamic disablers, soft validation, and value-confirmation helpers. It is the field state every `Form.*` wrapper binds to.

## Creating a field state

Pass an initial value to the constructor, then attach validators:

```tsx theme={null}
import { A2InputFieldState } from "@servicetitan/anvil2-ext-common";

const email = new A2InputFieldState("");
email.validators((value) => !value && "Email is required");
```

## Inherited from formstate

`A2InputFieldState` keeps the full `FieldState` surface:

| Member          | Description                                                     |
| --------------- | --------------------------------------------------------------- |
| `$`             | The validated value.                                            |
| `dirty`         | `true` once the value changes from its initial value.           |
| `error`         | The current error message, or `undefined`.                      |
| `hasError`      | `true` when the last validation found an error.                 |
| `onChange`      | Sets a new value (no-op while the field is `disabled`).         |
| `reset(value?)` | Restores the initial value (or `value`) and clears the warning. |
| `validate()`    | Runs validators and resolves with the result.                   |
| `validators()`  | Registers the field's validators.                               |
| `value`         | The current value bound to the input.                           |

## Added behavior

`A2InputFieldState` adds the following on top of `FieldState`:

| Member                         | Description                                                                                         |
| ------------------------------ | --------------------------------------------------------------------------------------------------- |
| `addValidator(key, validator)` | Adds a validator once per `key`, guarding against duplicate registration.                           |
| `disabled`                     | `true` when a disabler matched the current value.                                                   |
| `disablers(...validators)`     | Registers disabler validators. A truthy result disables the field.                                  |
| `disableUpdateMode()`          | Removes the `updateMode` sub-field.                                                                 |
| `enableUpdateMode(mode?)`      | Creates the `updateMode` sub-field (defaults to `UpdateMode.Keep`).                                 |
| `hardConfirmValue()`           | Marks the current value as the initial value and clears `dirty`.                                    |
| `hardSetValue(value)`          | Sets the value, bypassing the `disabled` guard, and confirms it as the new initial value.           |
| `hasValueChanged()`            | Returns `true` when the value differs from its initial value.                                       |
| `initValue`                    | The field's initial value, for comparing against the current value.                                 |
| `onChangeHandler`              | A `@servicetitan/form`-compatible handler: `(event, { value })`.                                    |
| `onChangeNativeHandler`        | A `@servicetitan/form`-compatible handler that reads `event.currentTarget.value`.                   |
| `onReset(handler)`             | Registers a handler that runs after `reset()`. Returns the field state for chaining.                |
| `seedValue(value)`             | Silently sets a clean baseline value (no `onChange`/`onDidChange`); for hydrating from loaded data. |
| `softValidate()`               | Runs disablers and warnings without committing a full validation.                                   |
| `updateMode`                   | An optional `FieldState<UpdateMode>` sub-field for value-update controls.                           |
| `warning`                      | The current non-blocking warning message, or `undefined`.                                           |
| `warnings(...validators)`      | Registers warning validators. Each returns a message string or a falsy value.                       |

Warnings and disablers are evaluated during `validate()` and `softValidate()`:

```tsx theme={null}
const quantity = new A2InputFieldState<number | null>(null);

quantity.validators((value) => value == null && "Quantity is required");
quantity.warnings((value) => (value ?? 0) > 100 && "That is a large quantity");
quantity.disablers((value) => value === 0 && "Zero is not allowed");

await quantity.validate();
// quantity.hasError, quantity.warning, and quantity.disabled now reflect the value.
```

## Selection field states

`SelectableOptionsFieldState<T, G>` (single) and `SelectableOptionsArrayFieldState<T, G>` (multiple) extend `A2InputFieldState` for option-based selection, such as `Form.RadioGroup` and `Form.CheckboxGroup`. Both hold an `options` list and validate the selected value against option-derived rules.

```tsx theme={null}
import { SelectableOptionsFieldState } from "@servicetitan/anvil2-ext-common";

type Option = { id: number; label: string; active: boolean };

const role = new SelectableOptionsFieldState<number, Option>(undefined as never, {
  validationRules: [
    { shouldTrack: (option) => !option.active, errorCode: "Role is inactive" },
  ],
});

role.setOptions([
  { id: 1, label: "Admin", active: true },
  { id: 2, label: "Legacy", active: false },
]);
```

Call `setOptions` whenever the available options change. When the selected value matches a tracked option, `validate()` reports the rule's `errorCode` as the error.
