# useFieldsetValidationKit

[← Back to Composables README](https://github.com/NantHealth/featherk/blob/integration/packages/composables/README.md)

Form orchestration composable for delayed fieldset validation UX.

`useFieldsetValidationKit` composes `useFieldsetTouchTracker` and provides a kit-oriented API for:

- delayed validation gate (`submitted || touched`)
- field-level delayed validity wrappers
- first-invalid focus targeting
- reset behavior that avoids immediate re-trigger from reset-related focus transitions

> `focusFirstInvalidField` is currently included as a convenience helper. It is
> form-scoped rather than fieldset-scoped, so it does not cleanly belong to this
> kit and is expected to move to a separate utility in a future release.

## Prerequisites

- Vue 3 Composition API

## Quick Start

```vue
<script setup lang="ts">
import { computed, ref } from "vue";
import { useFieldsetValidationKit } from "@featherk/composables/form";

const fieldsetRef = ref<HTMLFieldSetElement | null>(null);
const formRef = ref<HTMLFormElement | null>(null);

const submitted = ref(false);

const firstName = ref("");
const city = ref("");

const firstNameValidRule = computed(() => firstName.value.trim().length >= 2);
const cityValidRule = computed(() => city.value.trim().length >= 2);

const isFieldsetValid = computed(
  () => firstNameValidRule.value && cityValidRule.value,
);

const kit = useFieldsetValidationKit(fieldsetRef, {
  submitted,
  isValid: () => isFieldsetValid.value,
});

const { touched, fieldsetValidate, onFocusout, reset, focusFirstInvalidField } =
  kit;

const fieldValidations = kit.createFieldValidationsMap({
  firstName: () => firstNameValidRule.value,
  city: () => cityValidRule.value,
});

// fieldValidations.firstName and fieldValidations.city are ComputedRef<boolean>
// values that stay true until fieldset validation is active.

function onSubmit() {
  submitted.value = true;
  if (!isFieldsetValid.value) {
    focusFirstInvalidField(formRef);
  }
}

function onReset() {
  submitted.value = false;
  reset();
}
</script>

<template>
  <form ref="formRef" @submit.prevent="onSubmit" @reset.prevent="onReset">
    <fieldset ref="fieldsetRef" @focusout="onFocusout">
      <!-- controls bind to fieldsetValidate + fieldValidations -->
    </fieldset>
  </form>

  <p>Touched: {{ touched }}</p>
  <p>Validate: {{ fieldsetValidate }}</p>
</template>
```

## What createFieldValidationsMap Returns

You pass one object argument whose properties are the field rules:

```ts
const rules = {
  firstName: () => firstNameValidRule.value,
  city: () => cityValidRule.value,
};
```

And the kit returns the same shape, but each field becomes a delayed
`ComputedRef<boolean>`:

```ts
const fieldValidations = kit.createFieldValidationsMap(rules);

fieldValidations.firstName.value;
fieldValidations.city.value;
```

This is important because it lets you define field rules once, keep the field
names intact, and apply the same delayed-validation gate consistently across
the whole fieldset.

## API

### useFieldsetValidationKit(fieldsetRef, options)

Creates delayed-validation behavior for a fieldset.

#### Options

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `submitted` | `MaybeRefOrGetter<boolean>` | Yes | Consumer-owned submit state. Never mutated by the kit. |
| `isValid` | `() => boolean` | Yes | Returns the entire fieldset's current true validity. |

#### Returns

| Property | Type | Description |
|----------|------|-------------|
| `touched` | `Ref<boolean>` | Becomes `true` once focus leaves the fieldset. |
| `fieldsetValidate` | `ComputedRef<boolean>` | Delayed validation gate: `submitted || touched`. |
| `onFocusout` | `(event: FocusEvent) => void` | Bind to fieldset `focusout`. Handles touch tracking. |
| `reset` | `() => void` | Clears touch state and suppresses one immediate post-reset focusout. |
| `createFieldValidity` | `(isValid: MaybeRefOrGetter<boolean>) => ComputedRef<boolean>` | Wraps a field rule with delayed-validation gate behavior. |
| `createFieldValidationsMap` | `<T>(isValidMap: T) => { [K in keyof T]: ComputedRef<boolean> }` | Accepts one object argument of named field rules and returns the same shape with delayed `ComputedRef<boolean>` values. |
| `focusFirstInvalidField` | `(formRef: MaybeRefOrGetter<HTMLFormElement \| null>) => void` | Convenience helper that focuses the first invalid Kendo control in the form using `.k-invalid` selectors. This is expected to move out of the kit in a future release. |

## Behavior Notes

- Validation visibility is inactive until either:
  - consumer sets `submitted = true`, or
  - focus leaves the fieldset and marks `touched = true`.
- `createFieldValidity` and `createFieldValidationsMap` return `true` while gated off, then expose real rule results once gated on.
- `reset()` intentionally suppresses one immediate post-reset focusout to avoid reset-click focus churn re-arming validation immediately.
- `reset()` also resets the underlying touch tracker to keep kit and tracker state synchronized.
- `focusFirstInvalidField()` is available for now as a convenience, but it is a
  form-level concern and not a natural fieldset-kit responsibility.

## Recommended Usage

For app forms, prefer this composable over `useFieldsetTouchTracker` directly.

Use the touch tracker directly only when you need custom orchestration not covered by this kit.
