fix(ui-layout, input-controls-dropdown, oms-return-details): prevent stale scroll events from closing dropdown on open Delay scroll listener registration using requestAnimationFrame when activating CloseOnScrollDirective. This prevents stale scroll events still in the event queue from immediately triggering closeOnScroll when opening the dropdown after scrolling. Also adds conditional rendering for product format and publication date in return-details-order-group-item component. Refs: #5513
@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
- Quick Start
- Core Concepts
- API Reference
- Usage Examples
- Form Integration
- Accessibility
- Testing
- 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
import { Component } from '@angular/core';
import {
CheckboxComponent,
DropdownButtonComponent,
DropdownOptionComponent,
TextFieldComponent,
InputControlDirective,
} from '@isa/ui/input-controls';
import { ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-my-form',
imports: [
ReactiveFormsModule,
CheckboxComponent,
DropdownButtonComponent,
DropdownOptionComponent,
TextFieldComponent,
InputControlDirective,
],
template: '...'
})
export class MyFormComponent {}
2. Basic Text Field
<ui-text-field>
<input
type="text"
uiInputControl
formControlName="username"
placeholder="Enter username"
/>
</ui-text-field>
3. Checkbox
<ui-checkbox>
<input type="checkbox" formControlName="acceptTerms" />
</ui-checkbox>
4. Dropdown
<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>
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
- 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:
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
const CheckboxAppearance = {
Bullet: 'bullet', // Round bullet-style selector
Checkbox: 'checkbox', // Traditional square checkbox (default)
} as const;
Text Field Sizes
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:
<!-- Default checkbox appearance -->
<ui-checkbox>
<input type="checkbox" />
</ui-checkbox>
<!-- Bullet appearance -->
<ui-checkbox [appearance]="CheckboxAppearance.Bullet">
<input type="checkbox" />
</ui-checkbox>
DropdownButtonComponent
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 textshowSelectedValue: boolean- Show selected option text. Default: truetabIndex: number- Tab index for keyboard navigation. Default: 0id: string- Optional element ID
Outputs:
value: ModelSignal<T>- Two-way bindable selected valuedisabled: ModelSignal<boolean>- Disabled state
Methods:
open(): void- Opens the dropdown overlayclose(): void- Closes the dropdown overlayselect(option, options?): void- Selects an option programmatically
Keyboard Navigation:
Arrow Down/Up- Navigate through optionsEnter- Select highlighted option and closeEscape- Close dropdown
Usage:
interface Product {
id: number;
name: string;
}
products: Product[] = [
{ id: 1, name: 'Product A' },
{ id: 2, name: 'Product B' }
];
selectedProduct: Product;
<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>
ChipsComponent
Single-selection chip group with form integration.
Selector: ui-chips
Outputs:
value: ModelSignal<T>- Selected chip valuedisabled: ModelSignal<boolean>- Disabled state
Methods:
select(value: T, options?): void- Selects a chiptoggle(value: T, options?): void- Toggles chip selectionisSelected(value: T): boolean- Checks if value is selected
Usage:
type Size = 'S' | 'M' | 'L' | 'XL';
selectedSize: Size = 'M';
<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:
selectedFruits: string[] = ['apple', 'banana'];
<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
InputControlDirectiveon child input element
Usage:
<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:
<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 functiondisabled: boolean- Disabled state
Outputs (via CDK):
valueChange: EventEmitter- Emits when selection changes
Usage:
<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
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:
import { FormControl, Validators } from '@angular/forms';
emailControl = new FormControl('', [Validators.required, Validators.email]);
roleControl = new FormControl<string>('user');
agreeControl = new FormControl(false, Validators.requiredTrue);
<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:
<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 optionsEnter- Select highlighted option and closeEscape- Close dropdown without selectionTab- Move focus to next element
Listbox:
Arrow Down/Up- Navigate itemsHome/End- Jump to first/last itemSpace/Enter- Select item
Checkbox/Checklist:
Space- Toggle checkbox stateTab- Move between checkboxes
ARIA Attributes
Components include proper ARIA attributes for screen readers:
<!-- 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
# 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:
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
- OnPush Change Detection - All components use OnPush strategy
- Signal Reactivity - Computed signals prevent unnecessary re-renders
- 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 librarylodash- Utility functions (isEqual)
Path Alias
Import from: @isa/ui/input-controls
License
Internal ISA Frontend library - not for external distribution.