# @isa/ui/input-controls

A comprehensive collection of form input components and directives for Angular applications supporting reactive forms, template-driven forms, and accessibility features.

## Overview

The Input Controls library provides a complete suite of reusable form components built on Angular's reactive forms API and Angular CDK. It includes text fields, checkboxes, dropdowns, chips, checklists, listboxes, and inline inputs - all designed to work seamlessly with Angular Forms while maintaining consistent styling and behavior across the ISA application.

## Table of Contents

- [Features](#features)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [Form Integration](#form-integration)
- [Accessibility](#accessibility)
- [Testing](#testing)
- [Architecture Notes](#architecture-notes)

## Features

- **Complete form control suite** - 8 component types covering common input scenarios
- **Reactive Forms integration** - ControlValueAccessor implementation for all form controls
- **Template-driven forms** - Full ngModel support for two-way binding
- **Keyboard navigation** - Arrow keys, Enter, Escape, and Tab support
- **Accessibility (a11y)** - ARIA attributes, screen reader support, keyboard navigation
- **Customizable appearance** - Multiple size and appearance variants for each control
- **Validation support** - Integration with Angular's built-in validators
- **CDK integration** - Built on Angular CDK primitives (listbox, a11y, overlay)
- **Signal-based inputs** - Modern Angular signals for reactive property management
- **Type-safe** - Full TypeScript support with generic type parameters

## Quick Start

### 1. Import Components

```typescript
import { Component } from '@angular/core';
import {
  CheckboxComponent,
  DropdownButtonComponent,
  DropdownFilterComponent,
  DropdownOptionComponent,
  TextFieldComponent,
  InputControlDirective,
} from '@isa/ui/input-controls';
import { ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-my-form',
  imports: [
    ReactiveFormsModule,
    CheckboxComponent,
    DropdownButtonComponent,
    DropdownFilterComponent,
    DropdownOptionComponent,
    TextFieldComponent,
    InputControlDirective,
  ],
  template: '...'
})
export class MyFormComponent {}
```

### 2. Basic Text Field

```html
<ui-text-field>
  <input
    type="text"
    uiInputControl
    formControlName="username"
    placeholder="Enter username"
  />
</ui-text-field>
```

### 3. Checkbox

```html
<ui-checkbox>
  <input type="checkbox" formControlName="acceptTerms" />
</ui-checkbox>
```

### 4. Dropdown

```html
<ui-dropdown [(ngModel)]="selectedOption" label="Select option">
  <ui-dropdown-option [value]="1">Option 1</ui-dropdown-option>
  <ui-dropdown-option [value]="2">Option 2</ui-dropdown-option>
  <ui-dropdown-option [value]="3">Option 3</ui-dropdown-option>
</ui-dropdown>
```

### 5. Dropdown with Filter

```html
<ui-dropdown [(ngModel)]="selectedCountry" label="Select country">
  <ui-dropdown-filter placeholder="Search..."></ui-dropdown-filter>
  <ui-dropdown-option [value]="'de'">Germany</ui-dropdown-option>
  <ui-dropdown-option [value]="'at'">Austria</ui-dropdown-option>
  <ui-dropdown-option [value]="'ch'">Switzerland</ui-dropdown-option>
</ui-dropdown>
```

## Core Concepts

### Component Categories

The library provides three main categories of form controls:

#### 1. Text Input Controls
- **TextFieldComponent** - Standard text input with container, labels, and errors
- **TextareaComponent** - Multi-line text input
- **InlineInputComponent** - Compact inline text input for space-constrained UIs

#### 2. Selection Controls
- **CheckboxComponent** - Single checkbox with bullet or checkbox appearance
- **ChecklistComponent** - Multiple checkboxes working as a group
- **DropdownButtonComponent** - Select dropdown with keyboard navigation
- **ChipsComponent** - Single-select chip group
- **ListboxDirective** - CDK listbox wrapper for custom list selections

#### 3. Supporting Components
- **InputControlDirective** - Core directive for input field integration
- **TextFieldContainerComponent** - Layout container for text fields
- **TextFieldErrorsComponent** - Validation error display
- **TextFieldClearComponent** - Clear button for text inputs
- **CheckboxLabelDirective** - Label styling for checkboxes
- **ChecklistValueDirective** - Value binding for checklist items
- **ChipOptionComponent** - Individual chip option
- **DropdownOptionComponent** - Individual dropdown option
- **DropdownFilterComponent** - Filter input for dropdown options
- **ListboxItemDirective** - Individual listbox item

### Control Value Accessor Pattern

All form controls implement Angular's `ControlValueAccessor` interface, enabling seamless integration with both reactive and template-driven forms:

```typescript
export class DropdownButtonComponent<T> implements ControlValueAccessor {
  value = model<T>();
  disabled = model<boolean>(false);

  writeValue(obj: unknown): void { ... }
  registerOnChange(fn: (value: T) => void): void { ... }
  registerOnTouched(fn: () => void): void { ... }
  setDisabledState(isDisabled: boolean): void { ... }
}
```

### Appearance Variants

#### Checkbox Appearances
```typescript
const CheckboxAppearance = {
  Bullet: 'bullet',      // Round bullet-style selector
  Checkbox: 'checkbox',  // Traditional square checkbox (default)
} as const;
```

#### Text Field Sizes
```typescript
const TextFieldSize = {
  Small: 'small',
  Medium: 'medium',      // Default
  Large: 'large',
} as const;
```

## API Reference

### CheckboxComponent

A customizable checkbox component supporting different visual appearances.

**Selector:** `ui-checkbox`

**Inputs:**
- `appearance: CheckboxAppearance` - Visual style ('checkbox' | 'bullet'). Default: 'checkbox'

**Usage:**
```html
<!-- Default checkbox appearance -->
<ui-checkbox>
  <input type="checkbox" />
</ui-checkbox>

<!-- Bullet appearance -->
<ui-checkbox [appearance]="CheckboxAppearance.Bullet">
  <input type="checkbox" />
</ui-checkbox>
```

---

### DropdownButtonComponent<T>

A dropdown select component with keyboard navigation and CDK overlay integration.

**Selector:** `ui-dropdown`

**Inputs:**
- `appearance: DropdownAppearance` - Visual style. Default: 'accent-outline'
- `label: string` - Dropdown label text
- `showSelectedValue: boolean` - Show selected option text. Default: true
- `tabIndex: number` - Tab index for keyboard navigation. Default: 0
- `id: string` - Optional element ID
- `equals: (a: T | null, b: T | null) => boolean` - Custom equality function for comparing option values. Default: `lodash.isEqual`

**Outputs:**
- `value: ModelSignal<T>` - Two-way bindable selected value
- `disabled: ModelSignal<boolean>` - Disabled state

**Methods:**
- `open(): void` - Opens the dropdown overlay
- `close(): void` - Closes the dropdown overlay
- `select(option, options?): void` - Selects an option programmatically

**Keyboard Navigation:**
- `Arrow Down/Up` - Navigate through options
- `Enter` - Select highlighted option and close
- `Escape` - Close dropdown
- `Space` - Toggle dropdown open/close (when focused)

**Usage:**
```typescript
interface Product {
  id: number;
  name: string;
}

products: Product[] = [
  { id: 1, name: 'Product A' },
  { id: 2, name: 'Product B' }
];
selectedProduct: Product;
```

```html
<ui-dropdown
  [(value)]="selectedProduct"
  label="Select Product"
  appearance="accent-outline">
  @for (product of products; track product.id) {
    <ui-dropdown-option [value]="product">
      {{ product.name }}
    </ui-dropdown-option>
  }
</ui-dropdown>
```

**Custom Equality Comparison:**

When working with object values, you may need custom comparison logic (e.g., comparing by ID instead of deep equality):

```typescript
// Compare products by ID instead of deep equality
readonly productEquals = (a: Product | null, b: Product | null) => a?.id === b?.id;
```

```html
<ui-dropdown
  [(value)]="selectedProduct"
  [equals]="productEquals"
  label="Select Product">
  @for (product of products; track product.id) {
    <ui-dropdown-option [value]="product">
      {{ product.name }}
    </ui-dropdown-option>
  }
</ui-dropdown>
```

---

### DropdownFilterComponent

A filter input component for use within a DropdownButtonComponent. Renders as a sticky input at the top of the options panel, allowing users to filter options by typing.

**Selector:** `ui-dropdown-filter`

**Inputs:**
- `placeholder: string` - Placeholder text for the filter input. Default: 'Suchen...'

**Features:**
- Sticky positioning at top of options panel
- Auto-focuses when dropdown opens
- Clears automatically when dropdown closes
- Arrow key navigation works while typing
- Enter key selects the highlighted option

**Usage:**
```html
<ui-dropdown [(ngModel)]="selectedCountry" label="Select country">
  <ui-dropdown-filter placeholder="Search countries..."></ui-dropdown-filter>
  <ui-dropdown-option [value]="'de'">Germany</ui-dropdown-option>
  <ui-dropdown-option [value]="'at'">Austria</ui-dropdown-option>
  <ui-dropdown-option [value]="'ch'">Switzerland</ui-dropdown-option>
  <ui-dropdown-option [value]="'fr'">France</ui-dropdown-option>
  <ui-dropdown-option [value]="'it'">Italy</ui-dropdown-option>
</ui-dropdown>
```

**Keyboard Navigation (when filter is focused):**
- `Arrow Down/Up` - Navigate through options
- `Enter` - Select highlighted option and close
- `Space` - Types in the input (does not toggle dropdown)
- `Escape` - Keeps focus in input

---

### DropdownOptionComponent<T>

A selectable option component for use within a DropdownButtonComponent. Implements the CDK Highlightable interface for keyboard navigation support.

**Selector:** `ui-dropdown-option`

**Inputs:**
| Input | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `value` | `T` | Yes | - | The value associated with this option |
| `disabled` | `boolean` | No | `false` | Whether this option is disabled |

**Usage:**
```html
<ui-dropdown [(ngModel)]="selected">
  <ui-dropdown-option [value]="'opt1'">Option 1</ui-dropdown-option>
  <ui-dropdown-option [value]="'opt2'">Option 2</ui-dropdown-option>
  <ui-dropdown-option [value]="'opt3'" [disabled]="true">Disabled</ui-dropdown-option>
</ui-dropdown>
```

**Features:**
- Uses host dropdown's `equals` function for value comparison (defaults to `lodash.isEqual`)
- Automatic filtering support when used with `DropdownFilterComponent`
- CSS classes applied automatically: `active`, `selected`, `disabled`, `filtered`
- ARIA `role="option"` and `aria-selected` attributes for accessibility

---

### ChipsComponent<T>

Single-selection chip group with form integration.

**Selector:** `ui-chips`

**Outputs:**
- `value: ModelSignal<T>` - Selected chip value
- `disabled: ModelSignal<boolean>` - Disabled state

**Methods:**
- `select(value: T, options?): void` - Selects a chip
- `toggle(value: T, options?): void` - Toggles chip selection
- `isSelected(value: T): boolean` - Checks if value is selected

**Usage:**
```typescript
type Size = 'S' | 'M' | 'L' | 'XL';
selectedSize: Size = 'M';
```

```html
<ui-chips [(ngModel)]="selectedSize">
  <ui-chip [value]="'S'">Small</ui-chip>
  <ui-chip [value]="'M'">Medium</ui-chip>
  <ui-chip [value]="'L'">Large</ui-chip>
  <ui-chip [value]="'XL'">Extra Large</ui-chip>
</ui-chips>
```

---

### ChecklistComponent

Multi-select checkbox group returning an array of selected values.

**Selector:** `ui-checklist`

**Outputs:**
- `value: ModelSignal<unknown[]>` - Array of selected values

**Usage:**
```typescript
selectedFruits: string[] = ['apple', 'banana'];
```

```html
<ui-checklist [(ngModel)]="selectedFruits">
  <label class="ui-checkbox-label">
    <ui-checkbox>
      <input type="checkbox" [uiChecklistValue]="'apple'">
    </ui-checkbox>
    Apple
  </label>
  <label class="ui-checkbox-label">
    <ui-checkbox>
      <input type="checkbox" [uiChecklistValue]="'banana'">
    </ui-checkbox>
    Banana
  </label>
</ui-checklist>
```

---

### TextFieldComponent

Container component for text input fields with consistent styling.

**Selector:** `ui-text-field`

**Inputs:**
- `size: TextFieldSize` - Field size ('small' | 'medium' | 'large'). Default: 'medium'

**Content Projection:**
- Requires `InputControlDirective` on child input element

**Usage:**
```html
<ui-text-field size="medium">
  <input
    type="text"
    uiInputControl
    formControlName="email"
    placeholder="Enter email"
  />
</ui-text-field>
```

---

### InlineInputComponent

Compact inline text input for space-constrained interfaces.

**Selector:** `ui-inline-input`

**Inputs:**
- `size: InlineInputSize` - Field size ('small' | 'medium'). Default: 'medium'

**Usage:**
```html
<ui-inline-input size="small">
  <input type="text" uiInputControl [(ngModel)]="quantity" />
</ui-inline-input>
```

---

### ListboxDirective

Wrapper directive for Angular CDK listbox with ISA styling.

**Selector:** `[uiListbox]`

**Inputs (via CDK):**
- `value: any` - Selected value(s)
- `compareWith: (o1: any, o2: any) => boolean` - Comparison function
- `disabled: boolean` - Disabled state

**Outputs (via CDK):**
- `valueChange: EventEmitter` - Emits when selection changes

**Usage:**
```html
<div uiListbox [value]="selectedItem" (valueChange)="handleChange($event)">
  <div uiListboxItem [value]="item1">Item 1</div>
  <div uiListboxItem [value]="item2">Item 2</div>
</div>
```

## Usage Examples

### Reactive Form with Validation

```typescript
import { Component, inject } from '@angular/core';
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import {
  TextFieldComponent,
  TextFieldContainerComponent,
  TextFieldErrorsComponent,
  InputControlDirective,
  CheckboxComponent,
  DropdownButtonComponent,
  DropdownOptionComponent
} from '@isa/ui/input-controls';

@Component({
  selector: 'app-user-form',
  imports: [
    ReactiveFormsModule,
    TextFieldComponent,
    TextFieldContainerComponent,
    TextFieldErrorsComponent,
    InputControlDirective,
    CheckboxComponent,
    DropdownButtonComponent,
    DropdownOptionComponent
  ],
  template: `
    <form [formGroup]="userForm" (ngSubmit)="onSubmit()">
      <ui-text-field-container>
        <label>Email</label>
        <ui-text-field>
          <input
            type="email"
            uiInputControl
            formControlName="email"
            placeholder="user@example.com"
          />
        </ui-text-field>
        <ui-text-field-errors />
      </ui-text-field-container>

      <ui-dropdown formControlName="role" label="Select Role">
        <ui-dropdown-option [value]="'admin'">Administrator</ui-dropdown-option>
        <ui-dropdown-option [value]="'user'">User</ui-dropdown-option>
      </ui-dropdown>

      <label class="ui-checkbox-label">
        <ui-checkbox>
          <input type="checkbox" formControlName="acceptTerms" />
        </ui-checkbox>
        I accept the terms and conditions
      </label>

      <button type="submit" [disabled]="!userForm.valid">Submit</button>
    </form>
  `
})
export class UserFormComponent {
  #fb = inject(FormBuilder);

  userForm = this.#fb.group({
    email: ['', [Validators.required, Validators.email]],
    role: ['user', Validators.required],
    acceptTerms: [false, Validators.requiredTrue]
  });

  onSubmit() {
    if (this.userForm.valid) {
      console.log('Form submitted:', this.userForm.value);
    }
  }
}
```

## Form Integration

### Reactive Forms

All components implement `ControlValueAccessor` and work with `FormControl`:

```typescript
import { FormControl, Validators } from '@angular/forms';

emailControl = new FormControl('', [Validators.required, Validators.email]);
roleControl = new FormControl<string>('user');
agreeControl = new FormControl(false, Validators.requiredTrue);
```

```html
<ui-text-field>
  <input uiInputControl [formControl]="emailControl" />
</ui-text-field>

<ui-dropdown [formControl]="roleControl" label="Role">...</ui-dropdown>

<ui-checkbox><input [formControl]="agreeControl" /></ui-checkbox>
```

### Template-Driven Forms

Use `ngModel` for two-way binding:

```html
<ui-text-field>
  <input uiInputControl [(ngModel)]="email" name="email" />
</ui-text-field>

<ui-dropdown [(ngModel)]="role" name="role" label="Role">...</ui-dropdown>
```

## Accessibility

### Keyboard Navigation

All components support comprehensive keyboard navigation:

**Dropdown:**
- `Arrow Down/Up` - Navigate through options
- `Enter` - Select highlighted option and close
- `Escape` - Close dropdown without selection
- `Tab` - Move focus to next element

**Listbox:**
- `Arrow Down/Up` - Navigate items
- `Home/End` - Jump to first/last item
- `Space/Enter` - Select item

**Checkbox/Checklist:**
- `Space` - Toggle checkbox state
- `Tab` - Move between checkboxes

### ARIA Attributes

Components include proper ARIA attributes for screen readers:

```html
<!-- Dropdown -->
<ui-dropdown
  role="combobox"
  aria-haspopup="listbox"
  [attr.aria-expanded]="isOpen()">
</ui-dropdown>

<!-- Listbox -->
<div uiListbox role="listbox">
  <div uiListboxItem role="option"></div>
</div>
```

## Testing

The library uses **Jest** with **Spectator** for testing.

### Running Tests

```bash
# Run tests for this library
npx nx test ui-input-controls --skip-nx-cache

# Run tests with coverage
npx nx test ui-input-controls --code-coverage --skip-nx-cache

# Run tests in watch mode
npx nx test ui-input-controls --watch
```

### Test Coverage

The library includes comprehensive unit tests covering:

- **Component rendering** - All visual states and variants
- **Form integration** - ControlValueAccessor implementation
- **Keyboard navigation** - Arrow keys, Enter, Escape, Tab
- **Validation** - Error states and validation display
- **Accessibility** - ARIA attributes and screen reader support
- **User interactions** - Click, keyboard, focus events

## Architecture Notes

### Design Patterns

#### 1. ControlValueAccessor Pattern
All form controls implement the CVA interface for seamless Angular Forms integration.

**Benefits:**
- Works with both reactive and template-driven forms
- Automatic validation integration
- Consistent API across all controls

#### 2. Signal-Based Reactivity
Components use Angular signals for reactive state management:

```typescript
appearance = input<CheckboxAppearance>(CheckboxAppearance.Checkbox);
appearanceClass = computed(() =>
  this.appearance() === CheckboxAppearance.Bullet
    ? 'ui-checkbox__bullet'
    : 'ui-checkbox__checkbox'
);
```

#### 3. CDK Integration
Leverages Angular CDK for complex interactions:

- **Overlay** - Dropdown positioning (CdkConnectedOverlay)
- **A11y** - Keyboard navigation (ActiveDescendantKeyManager)
- **Listbox** - Accessible list selection (CdkListbox)

### Performance Considerations

1. **OnPush Change Detection** - All components use OnPush strategy
2. **Signal Reactivity** - Computed signals prevent unnecessary re-renders
3. **KeyManager** - Efficient keyboard navigation without DOM queries

## Dependencies

### Required Libraries

- `@angular/core` - Angular framework
- `@angular/forms` - Forms module
- `@angular/cdk/a11y` - Accessibility utilities
- `@angular/cdk/listbox` - Listbox primitive
- `@angular/cdk/overlay` - Overlay positioning
- `@angular/cdk/collections` - SelectionModel
- `@ng-icons/core` - Icon system
- `@isa/icons` - ISA icon library
- `lodash` - Utility functions (isEqual)

### Path Alias

Import from: `@isa/ui/input-controls`

## License

Internal ISA Frontend library - not for external distribution.
