# TerminalFormController

Source: /tuil/docs/reference/packages/form/api/terminal-form-controller
Locale: en

class exported by @mwillbanks/tuil-form.



{/* Generated by tooling/docs/generate-reference.ts. */}

## class [#class]

Public class exported by `@mwillbanks/tuil-form`.

```ts
export class TerminalFormController {
  readonly #fields = new Map<string, FormFieldRegistration>();
  readonly #fieldSubscriptions = new Map<string, () => void>();
  readonly #observers = new Set<() => void>();
  #submission?: AbortController;
  #version = 0;

  register(field: FormFieldRegistration): () => void {
    if (this.#fields.has(field.name)) {
      throw new Error(`Duplicate form field "${field.name}"`);
    }
    this.#fields.set(field.name, field);
    const unsubscribe = field.subscribe?.(() => this.#notify());
    if (unsubscribe) this.#fieldSubscriptions.set(field.name, unsubscribe);
    this.#notify();
    return () => {
      this.#fields.delete(field.name);
      this.#fieldSubscriptions.get(field.name)?.();
      this.#fieldSubscriptions.delete(field.name);
      this.#notify();
    };
  }

  subscribe(observer: () => void): () => void {
    this.#observers.add(observer);
    return () => this.#observers.delete(observer);
  }

  snapshot(): number {
    return this.#version;
  }

  get dirty(): boolean {
    return [...this.#fields.values()].some((field) => field.dirty());
  }

  values(
    options: { readonly redactSecrets?: boolean } = {},
  ): Readonly<Record<string, unknown>> {
    return Object.freeze(
      Object.fromEntries(
        [...this.#fields.values()].map((field) => [
          field.name,
          field.value(options.redactSecrets ?? true),
        ]),
      ),
    );
  }

  validationSummary(): readonly FormValidationError[] {
    return Object.freeze(
      [...this.#fields.values()].flatMap((field) =>
        (field.errors?.() ?? []).map((message) =>
          Object.freeze({ field: field.name, message }),
        ),
      ),
    );
  }

  async restore(values: Readonly<Record<string, unknown>>): Promise<void> {
    for (const [name, value] of Object.entries(values)) {
      await this.#fields.get(name)?.restore?.(value);
    }
  }

  async validate(
    trigger: ValidationTrigger = "submit",
    options: {
      readonly signal?: AbortSignal;
      readonly focus?: (name: string) => void;
    } = {},
  ): Promise<boolean> {
    let firstInvalid: string | undefined;
    for (const field of this.#fields.values()) {
      options.signal?.throwIfAborted();
      const state = await field.validate(trigger, options.signal);
      if (!state.valid && firstInvalid === undefined) firstInvalid = field.name;
    }
    this.#notify();
    if (firstInvalid) options.focus?.(firstInvalid);
    return firstInvalid === undefined;
  }

  async submit<T>(
    handler: (
      values: Readonly<Record<string, unknown>>,
      signal: AbortSignal,
    ) => T | Promise<T>,
    options:
      | AbortSignal
      | {
          readonly signal?: AbortSignal;
          readonly focus?: (name: string) => void;
        } = {},
  ): Promise<T | undefined> {
    this.#submission?.abort();
    const submission = new AbortController();
    this.#submission = submission;
    const signal = options instanceof AbortSignal ? options : options.signal;
    const focus = options instanceof AbortSignal ? undefined : options.focus;
    const submissionSignal = combinedSignal(submission.signal, signal);
    if (
      !(await this.validate("submit", {
        signal: submissionSignal,
        focus,
      }))
    ) {
      return undefined;
    }
    submissionSignal.throwIfAborted();
    return handler(this.values({ redactSecrets: false }), submissionSignal);
  }

  reset(): void {
    this.#submission?.abort();
    for (const field of this.#fields.values()) field.reset();
  }

  dispose(): void {
    this.#submission?.abort();
    for (const unsubscribe of this.#fieldSubscriptions.values()) unsubscribe();
    this.#fieldSubscriptions.clear();
    this.#fields.clear();
    this.#notify();
    this.#observers.clear();
  }

  #notify(): void {
    this.#version += 1;
    for (const observer of this.#observers) observer();
  }
}
```

## Members [#members]

| Member                | Type                                                                                                                                                                                                                                           | Required | Description                                                                                                                                                                                                                                                                           | Related types                                                                        |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `#fields`             | `Map<string, FormFieldRegistration>`                                                                                                                                                                                                           | Yes      | The `#fields` member uses the `Map<string, FormFieldRegistration>` contract.                                                                                                                                                                                                          | [`FormFieldRegistration`](/tuil/docs/reference/packages/form/api/form-field-registration) |
| `#fieldSubscriptions` | `Map<string, () => void>`                                                                                                                                                                                                                      | Yes      | The `#fieldSubscriptions` member uses the `Map<string, () => void>` contract.                                                                                                                                                                                                         | —                                                                                    |
| `#observers`          | `Set<() => void>`                                                                                                                                                                                                                              | Yes      | The `#observers` member uses the `Set<() => void>` contract.                                                                                                                                                                                                                          | —                                                                                    |
| `#submission`         | `AbortController \| undefined`                                                                                                                                                                                                                 | No       | The `#submission` member uses the `AbortController \| undefined` contract.                                                                                                                                                                                                            | —                                                                                    |
| `#version`            | `number`                                                                                                                                                                                                                                       | Yes      | The `#version` member uses the `number` contract.                                                                                                                                                                                                                                     | —                                                                                    |
| `register`            | `(field: FormFieldRegistration) => () => void`                                                                                                                                                                                                 | Yes      | The `register` member uses the `(field: FormFieldRegistration) => () => void` contract.                                                                                                                                                                                               | [`FormFieldRegistration`](/tuil/docs/reference/packages/form/api/form-field-registration) |
| `subscribe`           | `(observer: () => void) => () => void`                                                                                                                                                                                                         | Yes      | The `subscribe` member uses the `(observer: () => void) => () => void` contract.                                                                                                                                                                                                      | —                                                                                    |
| `snapshot`            | `() => number`                                                                                                                                                                                                                                 | Yes      | The `snapshot` member uses the `() => number` contract.                                                                                                                                                                                                                               | —                                                                                    |
| `dirty`               | `boolean`                                                                                                                                                                                                                                      | Yes      | The `dirty` member uses the `boolean` contract.                                                                                                                                                                                                                                       | —                                                                                    |
| `values`              | `(options?: &#123; readonly redactSecrets?: boolean; &#125;) => Readonly<Record<string, unknown>>`                                                                                                                                             | Yes      | The `values` member uses the `(options?: &#123; readonly redactSecrets?: boolean; &#125;) => Readonly<Record<string, unknown>>` contract.                                                                                                                                             | —                                                                                    |
| `validationSummary`   | `() => readonly FormValidationError[]`                                                                                                                                                                                                         | Yes      | The `validationSummary` member uses the `() => readonly FormValidationError[]` contract.                                                                                                                                                                                              | [`FormValidationError`](/tuil/docs/reference/packages/form/api/form-validation-error)     |
| `restore`             | `(values: Readonly<Record<string, unknown>>) => Promise<void>`                                                                                                                                                                                 | Yes      | The `restore` member uses the `(values: Readonly<Record<string, unknown>>) => Promise<void>` contract.                                                                                                                                                                                | —                                                                                    |
| `validate`            | `(trigger?: ValidationTrigger, options?: &#123; readonly signal?: AbortSignal; readonly focus?: (name: string) => void; &#125;) => Promise<boolean>`                                                                                           | Yes      | The `validate` member uses the `(trigger?: ValidationTrigger, options?: &#123; readonly signal?: AbortSignal; readonly focus?: (name: string) => void; &#125;) => Promise<boolean>` contract.                                                                                         | [`ValidationTrigger`](/tuil/docs/reference/packages/form/api/validation-trigger)          |
| `submit`              | `<T>(handler: (values: Readonly<Record<string, unknown>>, signal: AbortSignal) => T \| Promise<T>, options?: AbortSignal \| &#123; readonly signal?: AbortSignal; readonly focus?: (name: string) => void; &#125;) => Promise<T \| undefined>` | Yes      | The `submit` member uses the `<T>(handler: (values: Readonly<Record<string, unknown>>, signal: AbortSignal) => T \| Promise<T>, options?: AbortSignal \| &#123; readonly signal?: AbortSignal; readonly focus?: (name: string) => void; &#125;) => Promise<T \| undefined>` contract. | —                                                                                    |
| `reset`               | `() => void`                                                                                                                                                                                                                                   | Yes      | The `reset` member uses the `() => void` contract.                                                                                                                                                                                                                                    | —                                                                                    |
| `dispose`             | `() => void`                                                                                                                                                                                                                                   | Yes      | The `dispose` member uses the `() => void` contract.                                                                                                                                                                                                                                  | —                                                                                    |
| `#notify`             | `() => void`                                                                                                                                                                                                                                   | Yes      | The `#notify` member uses the `() => void` contract.                                                                                                                                                                                                                                  | —                                                                                    |

## Parameters [#parameters]

This declaration has no public members.

## Returns [#returns]

This declaration does not return a value.

## Throws [#throws]

No thrown errors are documented for this declaration.

## Related types [#related-types]

* [`FormFieldRegistration`](/tuil/docs/reference/packages/form/api/form-field-registration)
* [`FormValidationError`](/tuil/docs/reference/packages/form/api/form-validation-error)
* [`ValidationTrigger`](/tuil/docs/reference/packages/form/api/validation-trigger)

## Source [#source]

[View the secondary source reference](https://github.com/mwillbanks/tuil/blob/main/packages/form/src/index.tsx)

## Package [#package]

[@mwillbanks/tuil-form](/tuil/docs/reference/packages/form)
