> ## 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.

# useInputGuards

> Debounce a text input and rate-limit an expensive onCommit callback across typing, paste, Enter, and clear.

`useInputGuards` sits between a text input's raw events and an expensive `onCommit` callback — a network request, store update, or analytics event you don't want firing on every keystroke. The visible value stays fully responsive while the hook decides *when* a value is worth committing. The "guards" in the name are the protections it layers over that callback:

* **Debounce** — a burst of typing coalesces into a single commit with the final value.
* **Commit guard** — commits are rate-limited to at most one per `delay` ms, across *every* input method.
* **Held-key suppression** — repeated `keydown` from a held hotkey is dropped, so holding Enter or a paste shortcut doesn't fire repeatedly.
* **Deduplication** — the same value never commits twice in a row.
* **External sync** — when the `value` prop changes from outside, the input re-syncs and any stale in-flight commit is cancelled.

By default all four user actions (typing, Enter, paste, clear) can commit; the [`triggers`](#choosing-which-actions-commit) option lets you opt out of any of them.

## Installation

```shell theme={null}
npm install @servicetitan/anvil2-ext-common
```

## Usage

Call the hook with the controlled `value`, a `delay`, and your `onCommit` handler, then spread the returned `inputProps` onto the input. The hook owns the visible value and the commit timing; your component just reacts to commits.

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

function SearchBox({ query, onSearch }: { query: string; onSearch: (value: string) => void }) {
  const { inputProps } = useInputGuards({
    value: query,
    delay: 300,
    onCommit: onSearch,
  });

  return <input {...inputProps} />;
}
```

`inputProps` carries `value`, `onChange`, `onPaste`, and `onKeyDown`. Spread all of them — the paste and keydown handlers are what make the guards work; dropping them falls back to plain debounced typing.

## Configuration

| Prop       | Type                      | Description                                                                                          |
| ---------- | ------------------------- | ---------------------------------------------------------------------------------------------------- |
| `value`    | `string`                  | The controlled value. Changing it from outside re-syncs the visible input and cancels stale commits. |
| `delay`    | `number`                  | Debounce and commit-guard window, in milliseconds.                                                   |
| `onCommit` | `(value: string) => void` | Called with a value worth committing — debounced, rate-limited, and deduplicated.                    |
| `triggers` | `CommitTrigger[]`         | Which actions may commit. Omit (or pass empty) to enable all.                                        |

## Choosing which actions commit

Pass `triggers` to restrict which user actions fire a commit. When omitted, all are active.

```ts theme={null}
enum CommitTrigger {
  Typing = "typing", // debounced auto-commit after inactivity
  Enter = "enter",   // immediate commit on Enter
  Paste = "paste",   // immediate commit on paste
  Clear = "clear",   // immediate commit when cleared to ""
}
```

For example, to commit only on Enter, paste, and clear — never on a typing pause:

```tsx theme={null}
const { inputProps } = useInputGuards({
  value,
  delay: 300,
  onCommit: handleCommit,
  triggers: [CommitTrigger.Enter, CommitTrigger.Paste, CommitTrigger.Clear],
});
```

## Behavior reference

| Action / scenario       | What happens                                                                               |
| ----------------------- | ------------------------------------------------------------------------------------------ |
| Typing                  | Visible value updates instantly; `onCommit` fires after `delay` ms of inactivity.          |
| Paste                   | Inserted at the cursor (or over the selection); commits immediately through the guard.     |
| Enter                   | Commits immediately if the guard is idle, otherwise defers until the guard window expires. |
| Clear to empty          | `onCommit('')` fires immediately, bypassing the guard, and the guard is deactivated.       |
| Rapid commits           | First commits immediately; the next is deferred until `delay` ms have elapsed.             |
| Held hotkeys            | Repeated `keydown` is suppressed, so a held Enter or paste shortcut only fires once.       |
| Held arrow keys         | `ArrowLeft` / `ArrowRight` are exempt, so the cursor keeps moving as expected.             |
| Repeat value            | A value identical to the last committed one is never committed again.                      |
| External `value` change | Visible value re-syncs, stale in-flight commits are cancelled, and the guard resets.       |
| Unmount                 | All pending timers are cancelled; no trailing commit fires.                                |
