# Combobox

Searchable single-select surface built from a button trigger, a CDK overlay,
and the shared [Command](../command/README.md) list primitives.

Use Combobox when users need type-to-filter selection from a medium-sized list
without leaving the current form, settings screen, or dashboard panel.

## Import

```ts
import { ComboboxComponent, type ComboboxOption } from '@edsis/component/combobox';
```

## Composition

The Angular structure maps the combobox idea onto the existing library stack:

```text
Combobox
├── button[role="combobox"]
└── CDK overlay
    └── Command
        ├── input[CommandInput]
        └── CommandList
            └── button[CommandItem]
```

This differs from the current shadcn Base UI page, which documents an input-first
combobox. The Angular library currently ships the common searchable single-select
pattern on top of Button + Command + CDK Overlay.

## Basic usage

Bind `[(value)]` when a signal or parent component should own the selected value.
Pass width or layout utilities through the host `class`; the trigger fills that
host width.

```ts
frameworkOptions: ComboboxOption<string>[] = [
  { value: 'angular', label: 'Angular' },
  { value: 'nextjs', label: 'Next.js' },
  { value: 'astro', label: 'Astro' },
];

selectedFramework = signal<string | null>(null);
```

```html
<Combobox
  class="w-80"
  [options]="frameworkOptions"
  [(value)]="selectedFramework"
  placeholder="Select framework"
  searchPlaceholder="Search frameworks..."
/>
```

## Common patterns

### Object-backed values

Option values can be full domain objects, not only strings.

```ts
type Country = {
  code: string;
  label: string;
  region: string;
  currency: string;
};

countryOptions: ComboboxOption<Country>[] = [
  {
    value: { code: 'ca', label: 'Canada', region: 'North America', currency: 'CAD' },
    label: 'Canada',
  },
  {
    value: { code: 'jp', label: 'Japan', region: 'Asia', currency: 'JPY' },
    label: 'Japan',
  },
];

selectedCountry = signal<Country | null>(countryOptions[0]?.value ?? null);
```

```html
<Combobox
  class="w-full max-w-sm"
  [options]="countryOptions"
  [(value)]="selectedCountry"
  placeholder="Select country"
  searchPlaceholder="Search countries..."
/>
```

### Disabled options and empty states

Mark options with `disabled: true` when they should stay visible but unavailable.
Tune `searchPlaceholder` and `emptyText` so large lists feel intentional.

```ts
feedOptions: ComboboxOption<string>[] = [
  { value: 'release-notes', label: 'Release notes' },
  { value: 'beta-api', label: 'Beta API access', disabled: true },
  { value: 'status-page', label: 'Status page' },
];
```

```html
<Combobox
  class="w-full max-w-sm"
  [options]="feedOptions"
  placeholder="Choose a feed"
  searchPlaceholder="Search feeds..."
  emptyText="No feeds matched your query."
/>
```

### Reactive forms

The component implements `ControlValueAccessor`, so it also works with Angular
reactive forms.

```ts
readonly form = new FormGroup({
  framework: new FormControl<string | null>(null),
});
```

```html
<form [formGroup]="form">
  <Combobox
    class="w-80"
    formControlName="framework"
    [options]="frameworkOptions"
    placeholder="Select framework"
    searchPlaceholder="Search frameworks..."
  />
</form>
```

## API reference

| Input or model      | Type                               | Default               |
| ------------------- | ---------------------------------- | --------------------- |
| `options`           | `ReadonlyArray<ComboboxOption<T>>` | `[]`                  |
| `value`             | `T \| null`                        | `null`                |
| `placeholder`       | `string`                           | `'Select…'`           |
| `searchPlaceholder` | `string`                           | `'Search…'`           |
| `emptyText`         | `string`                           | `'No results found.'` |
| `disabled`          | `boolean`                          | `false`               |
| `class`             | `string`                           | `''`                  |

Output: `valueChange: T | null`.

### `ComboboxOption<T>`

```ts
interface ComboboxOption<T = unknown> {
  value: T;
  label: string;
  disabled?: boolean;
}
```

## Styling and theming

- Pass `class` to the host element for width and layout. The trigger fills the host width.
- Trigger uses the shared `outline` button variant.
- Overlay panel class is `combobox-panel`.
- Overlay width tracks the trigger with `--combobox-trigger-width`.

## Accessibility

- Trigger exposes `role="combobox"`, `aria-expanded`, `aria-controls`, and `aria-haspopup="listbox"`.
- Panel content is powered by `Command`, which provides the filter input and listbox roles.
- Escape and outside click close the panel; focus returns to the trigger.
- Keep a visible label or surrounding explanatory copy when placeholder text alone is not enough context.

## Keyboard interactions

- Enter or Space opens the trigger because it is a native button.
- Arrow keys move between filtered command items once the search input is focused.
- Enter selects the active option, and Escape closes the surface.
- Tab leaves the combobox in normal DOM order.

## Angular notes

- `[(value)]` is the simplest signal-friendly binding for standalone components.
- Because the component implements `ControlValueAccessor`, it also works with reactive forms.
- Object-backed selections rely on strict equality. Reuse the same object instances from the bound `options` array.

## Source parity

The current shadcn combobox docs also cover grouped collections, popup triggers,
clear buttons, invalid styling, input add-ons, and multi-select chips. Those
variants are not exposed by `Combobox` yet.

This README documents the supported Angular surface today and calls out the
upstream shadcn page as a reference for future expansion, not as a promise that
every upstream example already exists in this package.
