# Button toggle

- Entry point: `@koobiq/components/button-toggle`
- NgModule: `KbqButtonToggleModule`
- Page: https://koobiq.io/en/components/button-toggle/overview

## Overview

A button group is suitable for choosing from a list of 3 to 5 items when they fit in a single row without wrapping.

**Example: Button toggle** (`button-toggle-overview`)

```ts button-toggle-overview-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonToggleModule } from '@koobiq/components/button-toggle';
import { KbqIconModule } from '@koobiq/components/icon';

@Component({
    selector: 'button-toggle-overview-example',
    imports: [
        KbqButtonToggleModule,
        KbqIconModule
    ],
    templateUrl: 'button-toggle-overview-example.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ButtonToggleOverviewExample {}
```

```html button-toggle-overview-example.html
<div class="layout-margin-bottom-m">
    <kbq-button-toggle-group #group1="kbqButtonToggleGroup">
        <kbq-button-toggle [checked]="true" [value]="1">
            <i kbq-icon="kbq-briefcase_16"></i>
            Курьером
        </kbq-button-toggle>
        <kbq-button-toggle [value]="2">
            <i kbq-icon="kbq-briefcase_16"></i>
            Ослик Экспресс
        </kbq-button-toggle>
        <kbq-button-toggle [value]="3">
            <i kbq-icon="kbq-briefcase_16"></i>
            Почтой Средиземья
        </kbq-button-toggle>
    </kbq-button-toggle-group>
</div>

<div class="layout-margin-bottom-m">
    <kbq-button-toggle-group #group2="kbqButtonToggleGroup">
        <kbq-button-toggle [checked]="true" [value]="1">Курьером</kbq-button-toggle>
        <kbq-button-toggle [value]="2">Ослик Экспресс</kbq-button-toggle>
        <kbq-button-toggle [value]="3">Почтой Средиземья</kbq-button-toggle>
    </kbq-button-toggle-group>
</div>

<div>
    <!-- an icon carries no accessible name, so an icon-only toggle needs one of its own -->
    <kbq-button-toggle-group #group3="kbqButtonToggleGroup" aria-label="Способ доставки">
        <kbq-button-toggle aria-label="Курьером" [checked]="true" [value]="1">
            <i kbq-icon="kbq-briefcase_16"></i>
        </kbq-button-toggle>
        <kbq-button-toggle aria-label="Ослик Экспресс" [value]="2">
            <i kbq-icon="kbq-briefcase_16"></i>
        </kbq-button-toggle>
        <kbq-button-toggle aria-label="Почтой Средиземья" [value]="3">
            <i kbq-icon="kbq-briefcase_16"></i>
        </kbq-button-toggle>
    </kbq-button-toggle-group>
</div>
```

### Disabled

A regular button and a pressed button can each be individually disabled.

**Example: Button toggle disabled partial** (`button-toggle-disabled-partial-overview`)

```ts button-toggle-disabled-partial-overview-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonToggleModule } from '@koobiq/components/button-toggle';
import { KbqIconModule } from '@koobiq/components/icon';

@Component({
    selector: 'button-toggle-disabled-partial-overview-example',
    imports: [
        KbqButtonToggleModule,
        KbqIconModule
    ],
    templateUrl: 'button-toggle-disabled-partial-overview-example.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ButtonToggleDisabledPartialOverviewExample {}
```

```html button-toggle-disabled-partial-overview-example.html
<div class="layout-margin-bottom-m">
    <kbq-button-toggle-group #group1="kbqButtonToggleGroup">
        <kbq-button-toggle [checked]="true" [disabled]="true" [value]="1">
            <i kbq-icon="kbq-play_16"></i>
            Курьером
        </kbq-button-toggle>
        <kbq-button-toggle [value]="2">
            <i kbq-icon="kbq-play_16"></i>
            Ослик Экспресс
        </kbq-button-toggle>
        <kbq-button-toggle [value]="3">
            <i kbq-icon="kbq-play_16"></i>
            Почтой Средиземья
        </kbq-button-toggle>
    </kbq-button-toggle-group>
</div>

<div>
    <kbq-button-toggle-group #group2="kbqButtonToggleGroup">
        <kbq-button-toggle [checked]="true" [value]="1">
            <i kbq-icon="kbq-play_16"></i>
            Курьером
        </kbq-button-toggle>
        <kbq-button-toggle [value]="2">
            <i kbq-icon="kbq-play_16"></i>
            Ослик Экспресс
        </kbq-button-toggle>
        <kbq-button-toggle [disabled]="true" [value]="3">
            <i kbq-icon="kbq-play_16"></i>
            Почтой Средиземья
        </kbq-button-toggle>
    </kbq-button-toggle-group>
</div>
```

When all items are disabled or the pressed button is blocked, the entire group is styled as unavailable for interaction.

**Example: Button toggle disabled all** (`button-toggle-disabled-all-overview`)

```ts button-toggle-disabled-all-overview-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { KbqButtonToggleModule } from '@koobiq/components/button-toggle';
import { KbqIconModule } from '@koobiq/components/icon';

@Component({
    selector: 'button-toggle-disabled-all-overview-example',
    imports: [
        KbqButtonToggleModule,
        FormsModule,
        KbqIconModule
    ],
    templateUrl: 'button-toggle-disabled-all-overview-example.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ButtonToggleDisabledAllOverviewExample {
    model = 1;
}
```

```html button-toggle-disabled-all-overview-example.html
<div class="layout-margin-bottom-m">
    <kbq-button-toggle-group #group1="kbqButtonToggleGroup" [disabled]="true" [(ngModel)]="model">
        <kbq-button-toggle [value]="1">
            <i kbq-icon="kbq-circle-info_16"></i>
            Курьером
        </kbq-button-toggle>
        <kbq-button-toggle [value]="2">
            <i kbq-icon="kbq-circle-info_16"></i>
            Ослик Экспресс
        </kbq-button-toggle>
        <kbq-button-toggle [value]="3">
            <i kbq-icon="kbq-circle-info_16"></i>
            Почтой Средиземья
        </kbq-button-toggle>
    </kbq-button-toggle-group>
</div>

<div>
    <kbq-button-toggle-group #group2="kbqButtonToggleGroup" [disabled]="true">
        <kbq-button-toggle [value]="1">
            <i kbq-icon="kbq-circle-info_16"></i>
            Курьером
        </kbq-button-toggle>
        <kbq-button-toggle [value]="2">
            <i kbq-icon="kbq-circle-info_16"></i>
            Ослик Экспресс
        </kbq-button-toggle>
        <kbq-button-toggle [value]="3">
            <i kbq-icon="kbq-circle-info_16"></i>
            Почтой Средиземья
        </kbq-button-toggle>
    </kbq-button-toggle-group>
</div>
```

### Width alignment

By default, the size of the button group depends on its content, but there is an additional option to stretch it to the available width. Items share the available space equally, and content inside each button is centered.

**Example: Button toggle alignment** (`button-toggle-alignment-overview`)

```ts button-toggle-alignment-overview-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { KbqButtonToggleModule } from '@koobiq/components/button-toggle';
import { KbqIconModule } from '@koobiq/components/icon';

@Component({
    selector: 'button-toggle-alignment-overview-example',
    imports: [
        KbqButtonToggleModule,
        FormsModule,
        KbqIconModule
    ],
    templateUrl: 'button-toggle-alignment-overview-example.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ButtonToggleAlignmentOverviewExample {
    group = [
        'Курьером',
        'Ослик Экспресс',
        'Почтой Средиземья'
    ];

    model = 0;
}
```

```html button-toggle-alignment-overview-example.html
<kbq-button-toggle-group #group1="kbqButtonToggleGroup" class="layout-margin-bottom-l" stretched [(ngModel)]="model">
    @for (toggle of group; track toggle) {
        <kbq-button-toggle [value]="$index">
            <i kbq-icon="kbq-briefcase_16"></i>
            <span>{{ toggle }}</span>
        </kbq-button-toggle>
    }
</kbq-button-toggle-group>

<kbq-button-toggle-group #group2="kbqButtonToggleGroup" class="layout-margin-bottom-l" stretched>
    @for (toggle of group; track toggle) {
        <kbq-button-toggle [checked]="$index === 1" [value]="$index">
            {{ toggle }}
        </kbq-button-toggle>
    }
</kbq-button-toggle-group>

<!-- an icon carries no accessible name, so an icon-only toggle needs one of its own -->
<kbq-button-toggle-group #group3="kbqButtonToggleGroup" aria-label="Способ доставки" [stretched]="true">
    @for (toggle of group; track toggle) {
        <kbq-button-toggle [aria-label]="toggle" [checked]="$index === 2" [value]="$index">
            <i kbq-icon="kbq-briefcase_16"></i>
        </kbq-button-toggle>
    }
</kbq-button-toggle-group>
```

If text does not fit within a button, it is truncated with an ellipsis. A tooltip with the full name is available on hover or focus.

Mark an icon next to the text with `kbqButtonPrefix` or `kbqButtonSuffix`: it is then laid out outside the truncated text and stays fully visible. An unmarked icon shares one box with the text, and such text is clipped without an ellipsis.

**Example: Button toggle tooltip** (`button-toggle-tooltip-overview`)

```ts button-toggle-tooltip-overview-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { KbqButtonToggleModule } from '@koobiq/components/button-toggle';
import { KbqIconModule } from '@koobiq/components/icon';

@Component({
    selector: 'button-toggle-tooltip-overview-example',
    imports: [
        KbqButtonToggleModule,
        FormsModule,
        KbqIconModule
    ],
    template: `
        <kbq-button-toggle-group class="layout-margin-bottom-l" stretched [(ngModel)]="model">
            @for (toggle of group; track toggle) {
                <kbq-button-toggle [value]="$index">
                    {{ toggle }}
                </kbq-button-toggle>
            }
        </kbq-button-toggle-group>

        <kbq-button-toggle-group stretched [(ngModel)]="model">
            @for (toggle of group; track toggle) {
                <kbq-button-toggle [value]="$index">
                    <i kbqButtonPrefix kbq-icon="kbq-briefcase_16"></i>
                    <span>{{ toggle }}</span>
                </kbq-button-toggle>
            }
        </kbq-button-toggle-group>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ButtonToggleTooltipOverviewExample {
    group = [
        'Длинный текст кнопки-переключателя, чтобы показать, как обрезается текст',
        'Ослик Экспресс',
        'Почтой Средиземья'
    ];

    model = 1;
}
```

### Accessibility

A single-selection group is announced as a `radiogroup` of radio buttons: it is a single tab stop, the arrow keys move focus and selection together, and `Home`/`End` jump to the ends of the group. With `multiple` the toggles become independent toggle buttons in a `group`, each with its own pressed state and its own place in the tab order.

Name the group with `aria-label` or `aria-labelledby` so that its purpose is announced along with the selected item:

```html
<kbq-button-toggle-group aria-label="Delivery method">
    <kbq-button-toggle [value]="1">By courier</kbq-button-toggle>
    <kbq-button-toggle [value]="2">By post</kbq-button-toggle>
</kbq-button-toggle-group>
```

An icon carries no accessible name of its own, so a toggle that projects nothing but icons needs one: `aria-label` and `aria-labelledby` are inputs of the toggle and are forwarded to the inner button, which is the element the name is computed for. In development builds such a toggle logs a warning until it is named.

## API

### Components

#### KbqButtonToggle (component)

Single button inside of a toggle group.

```ts
@Component({ selector: 'kbq-button-toggle', exportAs: 'kbqButtonToggle' })
class KbqButtonToggle {
    checked = input<boolean | undefined>();
    value = input<NonNullable<unknown> | null | undefined>();
    readonly ariaLabel = input<string | null>(null, {
        alias: 'aria-label'
    });
    readonly ariaLabelledby = input<string | null>(null, {
        alias: 'aria-labelledby'
    });
    disabled = input<boolean | undefined>();
    readonly change = output<KbqButtonToggleChange>();
    get type(): ToggleType;
    get iconType(): string;
    focus(): void;
    focusViaKeyboard(): void;
    onToggleClick(): void;
    markForCheck(): void;
}
```

- `[checked]`: `boolean | undefined` — Whether the button is checked.
- `[value]`: `NonNullable<unknown> | null | undefined` — Value of the toggle. The value of a selected toggle is the value of its group.
- `[aria-label]`: `string | null` — Accessible name of the toggle. Required when the toggle projects nothing but icons.
- `[aria-labelledby]`: `string | null` — Id of the element that labels the toggle.
- `[disabled]`: `boolean | undefined` — Whether the toggle is disabled. A toggle inside a disabled group is disabled as well.
- `(change)`: `KbqButtonToggleChange` — Event emitted when the group value changes.
- `type`: `ToggleType` — Whether the toggle acts as a radio button (a single-selection group) or as a checkbox
  (a multiple-selection group, or a standalone toggle).
- `iconType`: `string` — Whether the toggle holds icons only (`-icon`), icons beside a label (`-icon-text`) or no icon
  at all (`''`). Rendered as the `.kbq-button-toggle-icon`/`.kbq-button-toggle-icon-text` host
  class, which is the only signal a stylesheet gets: CSS cannot see text nodes, so a selector can
  never tell an icon-only toggle from a label that happens to contain an icon.
- `focus()`: `void` — Focuses the button.
- `focusViaKeyboard()`: `void` — Focuses the button as if the user had reached it with the keyboard, showing the focus ring.
- `onToggleClick()`: `void` — Checks the button toggle due to an interaction with the underlying native button.
- `markForCheck()`: `void` — Marks the button toggle as needing checking for change detection.

  Kept for back-compatibility. A toggle now derives `checked` and `disabled` from signals owned
  by its group, so it re-renders on its own and nothing in the library calls this any more.

### Directives

#### KbqButtonToggleGroup (directive)

Selection group for `KbqButtonToggle`.

By default it behaves like a radio-button group — one toggle at a time, announced as a
`radiogroup` and walked with the arrow keys. With `multiple` the toggles become independent
toggle buttons and the group is announced as a plain `group`.

Name it with a plain `aria-label`/`aria-labelledby` attribute. The host is the very element the
consumer writes, so an input aliased to the same name would only write back what is already
there — and would wipe an `[attr.aria-label]` binding, which never reaches the input.

```ts
@Directive({
    selector: 'kbq-button-toggle-group',
    exportAs: 'kbqButtonToggleGroup'
})
class KbqButtonToggleGroup implements ControlValueAccessor {
    readonly vertical = input<boolean>(false);
    readonly stretched = input<boolean>(false);
    readonly multiple = input<boolean>(false);
    value = model<NonNullable<unknown> | null | undefined>();
    disabled = input<boolean | undefined>();
    readonly change = output<KbqButtonToggleChange>();
    get selected(): KbqButtonToggle | KbqButtonToggle[] | null;
    readonly buttonToggles: Signal<readonly KbqButtonToggle[]>;
    emitChangeEvent(source: KbqButtonToggle): void;
    syncButtonToggle(
        toggle: KbqButtonToggle,
        select: boolean,
        isUserInput?: boolean
    ): void;
    isSelected(toggle: KbqButtonToggle): boolean;
    isPrechecked(toggle: KbqButtonToggle): boolean;
}
```

- `[vertical]`: `boolean` — Whether the toggle group is vertical.
- `[stretched]`: `boolean` — Whether the toggle group stretches to fill its container width, with toggles sharing equal space.
- `[multiple]`: `boolean` — Whether multiple button toggles can be selected.
- `[(value)]`: `NonNullable<unknown> | null | undefined` — Value of the toggle group: an array in multiple-selection mode, a single value otherwise.

  An accessor rather than a `model()`: writing it walks the toggles to find the ones that match,
  and reading it reports the selection. `[(value)]` works all the same.

  An assigned value that corresponds to no toggle is kept and reported as is, so that it applies
  to a toggle rendered later — the same contract as `KbqRadioGroup`. It follows that `value` can
  name a toggle `selected` does not hold: `selected` only ever reports toggles that exist.
- `[disabled]`: `boolean | undefined` — Whether the whole button toggle group is disabled.
- `(change)`: `KbqButtonToggleChange` — Event emitted when the group's value changes.
- `selected`: `KbqButtonToggle | KbqButtonToggle[] | null` — Selected button toggles in the group: an array in multiple-selection mode, one toggle otherwise.
  Only toggles that exist, so a `value` waiting for its toggle is not represented here.
- `buttonToggles`: `Signal<readonly KbqButtonToggle[]>` — Child button toggle buttons.
- `emitChangeEvent()`: `void` — Dispatches a change event for the group value.
  - `source`: `KbqButtonToggle` — Toggle the change originated from. Taken from the interaction rather than from the
    selection, which is empty whenever the last selected toggle of a multiple-selection group is
    unchecked — there is no "last selected" toggle left to report then.
- `syncButtonToggle()`: `void` — Syncs a button toggle's selected state with the model value.
  - `toggle`: `KbqButtonToggle` — Toggle to be synced.
  - `select`: `boolean` — Whether the toggle should be selected.
  - `isUserInput`: `boolean` — Whether the change was a result of a user interaction.
- `isSelected()`: `boolean` — Checks whether a button toggle is selected.
- `isPrechecked()`: `boolean` — Determines whether a button toggle should be checked on init, from the value waiting for it.

### Classes

#### KbqButtonToggleChange (class)

Change event object emitted by KbqButtonToggle.

```ts
class KbqButtonToggleChange {
    source: KbqButtonToggle;
    value: any;
    constructor(source: KbqButtonToggle, value: any);
}
```

- `source`: `KbqButtonToggle` — The KbqButtonToggle that emits the event.
- `value`: `any` — The value assigned to the KbqButtonToggle.
- `constructor()`
  - `source`: `KbqButtonToggle` — The KbqButtonToggle that emits the event.
  - `value`: `any` — The value assigned to the KbqButtonToggle.

### Types

#### ToggleType (type)

Acceptable types for a button toggle.

```ts
type ToggleType = 'checkbox' | 'radio';
```
