# Timepicker

- Entry point: `@koobiq/components/timepicker`
- NgModule: `KbqTimepickerModule`
- Page: https://koobiq.io/en/components/timepicker/overview

## Overview

A time field (timepicker) is a special input field that allows entering only time.

### When to use

Use when you need to specify time.

**Example: Timepicker validation symbols** (`timepicker-overview`)

```ts timepicker-overview-example.ts
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { LuxonDateModule } from '@koobiq/angular-luxon-adapter/adapter';
import { DateAdapter } from '@koobiq/components/core';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqTimepickerModule, TimeFormats } from '@koobiq/components/timepicker';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { DateTime } from 'luxon';

@Component({
    selector: 'timepicker-overview-example',
    imports: [KbqToolTipModule, KbqIconModule, FormsModule, KbqTimepickerModule, LuxonDateModule],
    template: `
        <div class="docs-row">
            <kbq-form-field #tooltip="kbqTooltip" kbqTooltipModifier="warning" [kbqTooltip]="'Только цифры'">
                <i kbq-icon="kbq-clock_16" kbqPrefix></i>
                <input kbqTimepicker [format]="timeFormats.HHmm" [kbqValidationTooltip]="tooltip" [(ngModel)]="time" />
            </kbq-form-field>
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TimepickerOverviewExample {
    private adapter = inject<DateAdapter<DateTime>>(DateAdapter);

    timeFormats = TimeFormats;

    time: DateTime;

    constructor() {
        this.time = this.adapter.today().startOf('hour');
    }
}
```

### Component structure

Consists of an input field, mask, and icon (optional).

#### Variations

The input mask comes in two variations, with different time formats:

**Example: Timepicker variations** (`timepicker-variations`)

```ts timepicker-variations-example.ts
import { LowerCasePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { LuxonDateModule } from '@koobiq/angular-luxon-adapter/adapter';
import { DateAdapter } from '@koobiq/components/core';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqRadioModule } from '@koobiq/components/radio';
import { KbqTimepickerModule, TimeFormats } from '@koobiq/components/timepicker';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { DateTime } from 'luxon';

@Component({
    selector: 'timepicker-variations-example',
    imports: [
        KbqRadioModule,
        FormsModule,
        KbqToolTipModule,
        KbqTimepickerModule,
        KbqIconModule,
        LuxonDateModule,
        LowerCasePipe
    ],
    templateUrl: 'timepicker-variations-example.html',
    styleUrls: ['timepicker-variations-example.css'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TimepickerVariationsExample {
    private adapter = inject<DateAdapter<DateTime>>(DateAdapter);

    value: DateTime;

    timeFormat = TimeFormats.HHmm;
    protected readonly TimeFormats = TimeFormats;

    constructor() {
        this.value = this.adapter.today();
    }
}
```

```html timepicker-variations-example.html
<div class="example-timepicker-group">
    <kbq-radio-group [(ngModel)]="timeFormat">
        <kbq-radio-button class="example-radio-button" [value]="TimeFormats.HHmm">
            {{ TimeFormats.HHmm | lowercase }}
        </kbq-radio-button>
        <kbq-radio-button class="example-radio-button" [value]="TimeFormats.HHmmss">
            {{ TimeFormats.HHmmss | lowercase }}
        </kbq-radio-button>
    </kbq-radio-group>

    <kbq-form-field
        #tooltip="kbqTooltip"
        class="example-timepicker-element"
        kbqTooltipModifier="warning"
        [kbqTooltip]="'Только цифры'"
    >
        <i kbq-icon="kbq-clock_16" kbqPrefix></i>
        <input kbqTimepicker [format]="timeFormat" [kbqValidationTooltip]="tooltip" [(ngModel)]="value" />
    </kbq-form-field>
</div>
```

```css timepicker-variations-example.css
.kbq-radio-group {
    display: flex;
    flex-direction: column;
}

.kbq-radio-button {
    margin-bottom: 8px;
}
```

### How it works

Time can be entered in two ways: using number keys and using arrow keys on the keyboard. Time input is only allowed in 24-hour format.

When entering values, depending on where the focus is (hours, minutes, or seconds), the field validates acceptable values at that position: hours 0–23, minutes and seconds 0–59.

After entering two digits (hours or minutes), a colon is inserted, and the cursor jumps to the next part of the mask (to minutes or seconds). After entering two digits for seconds, the cursor does not change position, and no colon is inserted.

#### Input using arrows

The ↑ ↓ keys increase or decrease the selected time unit by 1. Increase (decrease) affects only the selected unit (for example, in hours, 23 is followed by 0, and vice versa).

For more details on input, see the section [Focus and keyboard interaction](#focus-and-keyboard-interaction).

#### Default state

In most cases, the field is empty, showing only the input mask. In other cases, when specifying time is not important for the user but important for the system, it will be useful to pre-fill the field with the current time (current time is understood as the time when the page was opened; it does not change dynamically).

### Validation

#### Validation during input

##### Entering letters and symbols

[Validation during input](https://koobiq.io/en/other/validation.md#during-value-input) is triggered:

- when entering letters in any part of the mask;
- when entering the first character, except digits.

**Example: Timepicker validation symbols** (`timepicker-overview`)

```ts timepicker-overview-example.ts
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { LuxonDateModule } from '@koobiq/angular-luxon-adapter/adapter';
import { DateAdapter } from '@koobiq/components/core';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqTimepickerModule, TimeFormats } from '@koobiq/components/timepicker';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { DateTime } from 'luxon';

@Component({
    selector: 'timepicker-overview-example',
    imports: [KbqToolTipModule, KbqIconModule, FormsModule, KbqTimepickerModule, LuxonDateModule],
    template: `
        <div class="docs-row">
            <kbq-form-field #tooltip="kbqTooltip" kbqTooltipModifier="warning" [kbqTooltip]="'Только цифры'">
                <i kbq-icon="kbq-clock_16" kbqPrefix></i>
                <input kbqTimepicker [format]="timeFormats.HHmm" [kbqValidationTooltip]="tooltip" [(ngModel)]="time" />
            </kbq-form-field>
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TimepickerOverviewExample {
    private adapter = inject<DateAdapter<DateTime>>(DateAdapter);

    timeFormats = TimeFormats;

    time: DateTime;

    constructor() {
        this.time = this.adapter.today().startOf('hour');
    }
}
```

##### Entering symbols and digits

If after entering two digits (in hours or minutes) any character other than digits and letters is entered, the field ignores this input.

If after entering one digit (in hours or minutes) any character other than digits and letters is entered, the field treats this character as a separator and converts it to a colon, and a zero is inserted before the entered digit.

If you enter values greater than 23 for hours and greater than 59 for minutes and seconds, they are converted to 23 for hours and 59 for minutes and seconds.

**Example: Timepicker validation symbols** (`timepicker-overview`)

```ts timepicker-overview-example.ts
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { LuxonDateModule } from '@koobiq/angular-luxon-adapter/adapter';
import { DateAdapter } from '@koobiq/components/core';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqTimepickerModule, TimeFormats } from '@koobiq/components/timepicker';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { DateTime } from 'luxon';

@Component({
    selector: 'timepicker-overview-example',
    imports: [KbqToolTipModule, KbqIconModule, FormsModule, KbqTimepickerModule, LuxonDateModule],
    template: `
        <div class="docs-row">
            <kbq-form-field #tooltip="kbqTooltip" kbqTooltipModifier="warning" [kbqTooltip]="'Только цифры'">
                <i kbq-icon="kbq-clock_16" kbqPrefix></i>
                <input kbqTimepicker [format]="timeFormats.HHmm" [kbqValidationTooltip]="tooltip" [(ngModel)]="time" />
            </kbq-form-field>
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TimepickerOverviewExample {
    private adapter = inject<DateAdapter<DateTime>>(DateAdapter);

    timeFormats = TimeFormats;

    time: DateTime;

    constructor() {
        this.time = this.adapter.today().startOf('hour');
    }
}
```

#### Validation of copied values

When copying values from the clipboard or pasting them using drag-and-drop, validation during input is triggered. If the copied value contained characters other than digits and letters, the rules from the "Entering symbols and digits" section above are applied to them.

**Example: Timepicker validation symbols** (`timepicker-overview`)

```ts timepicker-overview-example.ts
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { LuxonDateModule } from '@koobiq/angular-luxon-adapter/adapter';
import { DateAdapter } from '@koobiq/components/core';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqTimepickerModule, TimeFormats } from '@koobiq/components/timepicker';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { DateTime } from 'luxon';

@Component({
    selector: 'timepicker-overview-example',
    imports: [KbqToolTipModule, KbqIconModule, FormsModule, KbqTimepickerModule, LuxonDateModule],
    template: `
        <div class="docs-row">
            <kbq-form-field #tooltip="kbqTooltip" kbqTooltipModifier="warning" [kbqTooltip]="'Только цифры'">
                <i kbq-icon="kbq-clock_16" kbqPrefix></i>
                <input kbqTimepicker [format]="timeFormats.HHmm" [kbqValidationTooltip]="tooltip" [(ngModel)]="time" />
            </kbq-form-field>
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TimepickerOverviewExample {
    private adapter = inject<DateAdapter<DateTime>>(DateAdapter);

    timeFormats = TimeFormats;

    time: DateTime;

    constructor() {
        this.time = this.adapter.today().startOf('hour');
    }
}
```

#### Validation using ReactiveFormsModule

**Example: Timepicker field validation** (`timepicker-field-validation`)

```ts timepicker-field-validation-example.ts
import { afterNextRender, ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { LuxonDateModule } from '@koobiq/angular-luxon-adapter/adapter';
import { DateAdapter } from '@koobiq/components/core';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqTimepickerModule, TimeFormats } from '@koobiq/components/timepicker';
import { DateTime } from 'luxon';

/** @title Timepicker field validation */
@Component({
    selector: 'timepicker-field-validation-example',
    imports: [KbqTimepickerModule, ReactiveFormsModule, KbqIconModule, LuxonDateModule],
    template: `
        <kbq-form-field>
            <i kbq-icon="kbq-clock_16" kbqPrefix></i>
            <input kbqTimepicker [max]="max" [min]="min" [formControl]="control" [format]="timeFormats.HHmm" />
            @if (control.hasError('required')) {
                <kbq-error>Required</kbq-error>
            }
            <!-- Component built-in validation -->
            @if (control.hasError('kbqTimepickerLowerThenMin')) {
                <kbq-error>Min is {{ min.toFormat(timeFormats.HHmm) }}</kbq-error>
            }
            <!-- Component built-in validation -->
            @if (control.hasError('kbqTimepickerHigherThenMax')) {
                <kbq-error>Maximum is {{ max.toFormat(timeFormats.HHmm) }}</kbq-error>
            }
        </kbq-form-field>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TimepickerFieldValidationExample {
    private readonly adapter = inject(DateAdapter<DateTime>);
    protected readonly timeFormats = TimeFormats;

    protected readonly max = this.adapter.today().startOf('hour').plus({ hours: 1 });
    protected readonly min = this.adapter.today().startOf('hour').minus({ hours: 1 });

    protected readonly control = new FormControl<DateTime | null>(null, [Validators.required]);

    constructor() {
        afterNextRender(() => {
            this.control.markAsTouched();
            this.control.updateValueAndValidity();
        });
    }
}
```

### Focus and keyboard interaction

| Key               | Action                                                                                                                                                                             |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Tab \ Shift + Tab | Move focus to (from) the time field                                                                                                                                                |
| ↓ ↑               | Cursor is set on any time unit (hours, minutes, or seconds) → decrease or increase the value by 1                                                                                  |
| →                 | Move the cursor to the next character. If the cursor is set after the last time unit (minutes or seconds, depending on variation), place the cursor before the first hour digit    |
| ←                 | Move the cursor to the previous character. If the cursor is set before the first hour digit, move the cursor after the last time unit (minutes or seconds, depending on variation) |
| PgUp \ Home       | Place the cursor before the first hour digit                                                                                                                                       |
| PgDn \ End        | Move the cursor after the last time unit (minutes or seconds, depending on variation)                                                                                              |
| Space             | — If in any part of the mask → nothing happens                                                                                                                                     |
|                   | — If after entering two digits (hours or minutes) → move to the next part of the mask (minutes or seconds);                                                                        |
|                   | — If after entering one digit (in hours or minutes) → the field treats this key as a separator, a colon is inserted, and a zero is inserted before the entered digit               |

### Design and animation

The time field has a fixed width, which is built by the formula _left padding + mask + right padding_.

The mask width will be different for each language; after entering time, the width does not change, even if the entered value is less than the mask width.

The left padding varies depending on the variation (with or without icon). The right padding is always 16px.

### Additional

#### Using Datepicker and Timepicker together

This is an example of using Datepicker and Timepicker together to select a specific date and time. This approach is useful when you need to set an exact time for an event or deadline.

The user can select a date through Datepicker and then specify the exact time through Timepicker.

An implementation example is available in the [Datepicker](https://koobiq.io/en/components/datepicker.md#using-datepicker-and-timepicker-together) section

## API

### Directives

#### KbqTimepicker (directive)

```ts
@Directive({ selector: 'input[kbqTimepicker]', exportAs: 'kbqTimepicker' })
class KbqTimepicker<D> implements KbqFormFieldControl<D>, ControlValueAccessor, Validator {
    readonly errorStateMatcher = input<ErrorStateMatcher>();
    readonly disabled = input<boolean>(false);
    readonly id = input<string | undefined>();
    readonly format = input<TimeFormats>(DEFAULT_TIME_FORMAT);
    readonly min = input<D | null>(null);
    readonly max = input<D | null>(null);
    readonly value = input<D | null | undefined>();
    readonly kbqValidationTooltip = input<KbqTooltipTrigger | undefined>();
    readonly incorrectInput = output<void>();
    get hasSelection(): boolean;
    get isFullFormat(): boolean;
    get isShortFormat(): boolean;
    get viewValue(): string;
    get ngControl(): any;
    selectionStart: number | null;
    selectionEnd: number | null;
    get timeFormatPlaceholder(): string;
    onInput: () => void;
    getSize(): number;
    focus(): void;
    focusChanged(isFocused: boolean): void;
    onBlur(): void;
    onPaste($event: any): void;
    onKeyDown(event: KeyboardEvent): void;
    validate(control: AbstractControl): ValidationErrors | null;
    registerOnValidatorChange(fn: () => void): void;
}
```

- `[errorStateMatcher]`: `ErrorStateMatcher` — Object used to control when error messages are shown.
- `[disabled]`: `boolean` — Whether the timepicker is disabled. Also set by the bound form control.
- `[id]`: `string | undefined` — Unique id of the element, generated when not provided.
- `[format]`: `TimeFormats` — Time format the input parses and renders. An unsupported value falls back to the default.
- `[min]`: `D | null` — Earliest time the control accepts. Anything the date adapter cannot read is treated as unset.
- `[max]`: `D | null` — Latest time the control accepts. Anything the date adapter cannot read is treated as unset.
- `[value]`: `D | null | undefined` — The time the control holds: set with `[value]`, by the form control and by typing.
- `[kbqValidationTooltip]`: `KbqTooltipTrigger | undefined` — Tooltip shown for a moment whenever a keystroke is rejected.
- `[localeOverrides]`: `KbqPartialLocaleData | undefined` — Localized strings for this element and its subtree, keyed by locale section. Every key is optional at
  every depth.
- `timeFormatPlaceholder`: `string` — Localized placeholder

### Enums

#### TimeFormats (enum)

```ts
enum TimeFormats {
    HHmmss = 'HH:mm:ss',
    HHmm = 'HH:mm'
}
```

#### TimeParts (enum)

```ts
enum TimeParts {
    hours,
    minutes,
    seconds
}
```

### Constants

#### AM_PM_FORMAT_REGEXP (const)

```ts
const AM_PM_FORMAT_REGEXP: RegExp;
```

#### DEFAULT_TIME_FORMAT (const)

```ts
const DEFAULT_TIME_FORMAT: TimeFormats;
```

#### HOURS_MINUTES_REGEXP (const)

```ts
const HOURS_MINUTES_REGEXP: RegExp;
```

#### HOURS_MINUTES_SECONDS_REGEXP (const)

```ts
const HOURS_MINUTES_SECONDS_REGEXP: RegExp;
```

#### HOURS_ONLY_REGEXP (const)

```ts
const HOURS_ONLY_REGEXP: RegExp;
```

#### HOURS_PER_DAY (const)

```ts
const HOURS_PER_DAY: number;
```

#### kbqTimepickerLocaleConfigurationProvider (const)

Utility provider for `KBQ_TIMEPICKER_LOCALE_CONFIGURATION`. Only the strings you pass are overridden; the rest
keep following the active locale.

```ts
const kbqTimepickerLocaleConfigurationProvider: (
    configuration: KbqDeepPartial<KbqTimepickerLocaleConfiguration>
) => Provider;
```

#### MINUTES_PER_HOUR (const)

```ts
const MINUTES_PER_HOUR: number;
```

#### SECONDS_PER_MINUTE (const)

```ts
const SECONDS_PER_MINUTE: number;
```

#### TIMEFORMAT_PLACEHOLDERS (const)

```ts
const TIMEFORMAT_PLACEHOLDERS: { [timeFormat: string]: string };
```

#### TimeFormatToLocaleKeys (const)

Object that maps each time format to a related locale key

```ts
const TimeFormatToLocaleKeys: Record<TimeFormats, 'full' | 'short'>;
```
