# Popover

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

## Overview

A popover is a small non-modal dialog without dimming that opens next to a trigger element. It can contain text, input fields, and any other controls.

**Example: Popover common** (`popover-overview`)

```ts popover-overview-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqPopoverModule } from '@koobiq/components/popover';

@Component({
    selector: 'popover-overview-example',
    imports: [KbqPopoverModule, KbqButtonModule, KbqInputModule],
    template: `
        <ng-template #customContent>
            <div class="layout-margin-bottom-s">Интересно, что это такое?</div>
            <kbq-form-field>
                <input kbqInput placeholder="Интересная мысль" />
            </kbq-form-field>
        </ng-template>

        <ng-template #customFooter>
            <div class="layout-row layout-wrap" style="gap: 16px">
                <button kbq-button [color]="'contrast'" (click)="kbqPopover.hide(0)">Сохранить</button>
                <button kbq-button (click)="kbqPopover.hide(0)">Отмена</button>
            </div>
        </ng-template>

        <button
            #kbqPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverClass]="'popover-overview-example'"
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
        >
            Открыть поповер
        </button>
    `,
    styles: `
        ::ng-deep .kbq-popover.kbq-popover_medium.popover-overview-example {
            width: 400px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-row layout-align-center'
    }
})
export class PopoverOverviewExample {}
```

### Size

By default, the popover opens with a preset **Medium** width, but two additional sizes and the option to use a custom value are available.

**Example: Popover width** (`popover-width`)

```ts popover-width-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { PopUpSizes } from '@koobiq/components/core';
import { KbqPopoverModule } from '@koobiq/components/popover';

@Component({
    selector: 'popover-width-example',
    imports: [
        KbqPopoverModule,
        KbqButtonModule
    ],
    templateUrl: 'popover-width-example.html',
    styles: `
        ::ng-deep .kbq-popover.popover-width-example p:first-child {
            margin-top: 0;
        }

        ::ng-deep .kbq-popover.popover-width-example p:last-child {
            margin-bottom: 0;
        }

        ::ng-deep .kbq-popover.kbq-popover_medium.popover-width-custom-example {
            max-width: 320px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverWidthExample {
    popUpSizes = PopUpSizes;
    selectedSize = PopUpSizes.Small;
}
```

```html popover-width-example.html
<ng-template #customContent>
    В Windows есть вещи, известные как системные переменные. Это переменные, от которых зависит функционирование
    Windows, потому что Windows всегда будет знать, что означают переменные и где они находятся, даже когда
    мы перемещаем биты и байты повсюду. Системная переменная MAX_PATH — это та, которая ограничивает имена файлов и пути
    к файлам до 260 символов.
</ng-template>

<ng-template #customLargeContent>
    <p>
        В Windows есть вещи, известные как системные переменные. Это переменные, от которых зависит функционирование
        Windows, потому что Windows всегда будет знать, что означают переменные и где они находятся, даже когда
        мы перемещаем биты и байты повсюду. Системная переменная MAX_PATH — это та, которая ограничивает имена файлов
        и пути к файлам до 260 символов.
    </p>
    <p>
        Будучи переменной, вы думаете, мы могли бы изменить это. Нет, мы не должны. Это все равно что выдернуть нитку
        из свитера. Как только одна системная переменная изменяется, другие системные переменные и зависимые
        от них компоненты начинают распадаться.
    </p>
</ng-template>

<div style="gap: 16px" class="layout-row layout-wrap layout-align-center">
    <button
        kbq-button
        kbqPopover
        [kbqPopoverHasCloseButton]="true"
        [kbqPopoverClass]="'popover-width-example'"
        [kbqPopoverContent]="customLargeContent"
        [kbqPopoverHeader]="'Medium'"
        [kbqPopoverSize]="selectedSize"
        (click)="selectedSize = popUpSizes.Medium"
    >
        Medium
    </button>
    <button
        kbq-button
        kbqPopover
        [kbqPopoverHasCloseButton]="true"
        [kbqPopoverClass]="'popover-width-example'"
        [kbqPopoverContent]="customLargeContent"
        [kbqPopoverHeader]="'Large'"
        [kbqPopoverSize]="selectedSize"
        (click)="selectedSize = popUpSizes.Large"
    >
        Large
    </button>
    <button
        kbq-button
        kbqPopover
        [kbqPopoverHasCloseButton]="true"
        [kbqPopoverClass]="'popover-width-custom-example'"
        [kbqPopoverContent]="customContent"
        [kbqPopoverHeader]="'Custom Width'"
    >
        Custom Width
    </button>
</div>
```

Popovers with compact content open with reduced inner padding. Popovers of this size have no header or footer:

**Example: popover-small** (`popover-small`)

```ts popover-small-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { PopUpSizes } from '@koobiq/components/core';
import { KbqIcon } from '@koobiq/components/icon';
import { KbqLink, KbqLinkSuffix } from '@koobiq/components/link';
import { KbqPopoverModule } from '@koobiq/components/popover';

@Component({
    selector: 'popover-small-example',
    imports: [
        KbqPopoverModule,
        KbqButtonModule,
        KbqLink,
        KbqLinkSuffix,
        KbqIcon
    ],
    templateUrl: 'popover-small-example.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverSmallExample {
    popUpSizes = PopUpSizes;
}
```

```html popover-small-example.html
<ng-template #customSmallContent>
    Schatz, Daniel; Bashroush, Rabih; Wall, Julie (2017). 
    <a href="">"Towards a More Representative Definition of Cyber Security"</a>
    . Journal of Digital Forensics, Security and Law. 12 (2). 
    <a href="">ISSN 1558-7215</a>
    .
</ng-template>

<div style="gap: 16px" class="layout-row layout-wrap layout-align-center">
    <div
        kbqLink
        kbqPopover
        [kbqPopoverClass]="'popover-small-example'"
        [kbqPopoverContent]="customSmallContent"
        [kbqPopoverSize]="popUpSizes.Small"
    >
        Small Size
        <i kbq-icon="kbq-circle-question_16" kbqLinkSuffix></i>
    </div>
</div>
```

The height of the popover depends on its content. The recommended maximum height is 480px (may be increased at the designer's discretion).

**Example: Popover height** (`popover-height`)

```ts popover-height-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqFormsModule } from '@koobiq/components/core';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqPopoverModule, KbqPopoverTrigger } from '@koobiq/components/popover';

@Component({
    selector: 'popover-height-example',
    imports: [KbqInputModule, KbqButtonModule, KbqPopoverModule, KbqFormsModule],
    templateUrl: 'popover-height-example.html',
    styles: `
        ::ng-deep .kbq-popover.popover-height-custom-example .kbq-popover__container {
            max-height: 240px;
        }

        ::ng-deep .kbq-popover.popover-height-custom-example .kbq-popover__header {
            flex-shrink: 0;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverHeightExample {
    activePopover: KbqPopoverTrigger;
}
```

```html popover-height-example.html
<ng-template #shortContent>
    <form novalidate class="kbq-form-vertical">
        <div class="kbq-form__fieldset">
            <div class="kbq-form__row">
                <label class="kbq-form__label">Relying Party Indicator</label>
                <kbq-form-field class="kbq-form__control">
                    <input kbqInput name="input" />
                </kbq-form-field>
            </div>
            <div class="kbq-form__row">
                <label class="kbq-form__label">Relying Party Indicator</label>
                <kbq-form-field class="kbq-form__control">
                    <input kbqInput name="input" />
                </kbq-form-field>
            </div>
            <div class="kbq-form__row">
                <label class="kbq-form__label">Post Back URL</label>
                <kbq-form-field class="kbq-form__control">
                    <input kbqInput name="input" />
                    <kbq-hint>
                        URL of the Management Center SAML assertion consumer, including schema (http:// or https://) and
                        port.
                    </kbq-hint>
                </kbq-form-field>
            </div>
            <div class="kbq-form__row">
                <label class="kbq-form__label">Identity Provider Metadata</label>
                <kbq-form-field class="kbq-form__control">
                    <input kbqInput name="input" />
                    <kbq-hint>Your Identity Provider’s metadata in XML format</kbq-hint>
                </kbq-form-field>
            </div>
        </div>
    </form>
</ng-template>

<ng-template #customContent>
    <form novalidate class="kbq-form-vertical">
        <div class="kbq-form__fieldset">
            <div class="kbq-form__row">
                <label class="kbq-form__label">Relying Party Indicator</label>
                <kbq-form-field class="kbq-form__control">
                    <input kbqInput name="input" />
                </kbq-form-field>
            </div>
            <div class="kbq-form__row">
                <label class="kbq-form__label">Relying Party Indicator</label>
                <kbq-form-field class="kbq-form__control">
                    <input kbqInput name="input" />
                </kbq-form-field>
            </div>
            <div class="kbq-form__row">
                <label class="kbq-form__label">Post Back URL</label>
                <kbq-form-field class="kbq-form__control">
                    <input kbqInput name="input" />
                    <kbq-hint>
                        URL of the Management Center SAML assertion consumer, including schema (http:// or https://) and
                        port.
                    </kbq-hint>
                </kbq-form-field>
            </div>
            <div class="kbq-form__row">
                <label class="kbq-form__label">Group Attribute</label>
                <kbq-form-field class="kbq-form__control">
                    <input kbqInput name="input" />
                </kbq-form-field>
            </div>
            <div class="kbq-form__row">
                <label class="kbq-form__label">Identity Provider Metadata</label>
                <kbq-form-field class="kbq-form__control">
                    <input kbqInput name="input" />
                    <kbq-hint>Your Identity Provider’s metadata in XML format</kbq-hint>
                </kbq-form-field>
            </div>
        </div>
    </form>
</ng-template>

<ng-template #customFooter>
    <div style="gap: 16px" class="layout-row layout-wrap">
        <button kbq-button [color]="'contrast'" (click)="activePopover.hide(0)">Save</button>
        <button kbq-button (click)="activePopover.hide(0)">Cancel</button>
    </div>
</ng-template>

<div style="gap: 16px" class="layout-row layout-wrap">
    <button
        #kbqNoScrollPopover="kbqPopover"
        kbq-button
        kbqPopover
        [kbqPopoverContent]="shortContent"
        [kbqPopoverFooter]="customFooter"
        [kbqPopoverHeader]="'SSO-connection'"
        (click)="activePopover = kbqNoScrollPopover"
    >
        Без прокрутки
    </button>
    <button
        #kbqAutoHeightPopover="kbqPopover"
        kbq-button
        kbqPopover
        [kbqPopoverContent]="customContent"
        [kbqPopoverFooter]="customFooter"
        [kbqPopoverHeader]="'SSO-connection'"
        (click)="activePopover = kbqAutoHeightPopover"
    >
        С прокруткой, высота по умолчанию
    </button>
    <button
        #kbqCustomHeightPopover="kbqPopover"
        kbq-button
        kbqPopover
        [kbqPopoverClass]="'popover-height-custom-example'"
        [kbqPopoverContent]="customContent"
        [kbqPopoverFooter]="customFooter"
        [kbqPopoverHeader]="'SSO-connection'"
        (click)="activePopover = kbqCustomHeightPopover"
    >
        С прокруткой, произвольная высота
    </button>
</div>
```

### Configuring padding

To flexibly control popover padding based on its content, reset the default values using `[kbqPopoverDefaultPaddings]="false"`. Then apply inner padding (padding) directly in the template of the content area:

**Example: popover-paddings** (`popover-paddings`)

```ts popover-paddings-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { PopUpPlacements, PopUpSizes } from '@koobiq/components/core';
import { KbqListModule } from '@koobiq/components/list';
import { KbqPopoverModule } from '@koobiq/components/popover';

@Component({
    selector: 'popover-paddings-example',
    imports: [
        KbqPopoverModule,
        KbqButtonModule,
        KbqListModule
    ],
    templateUrl: 'popover-paddings-example.html',
    styles: `
        ::ng-deep .popover-custom-paddings-example .kbq-popover__content {
            margin: 4px;
            width: 376px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverPaddingsExample {
    popUpSizes = PopUpSizes;
    popUpPlacements = PopUpPlacements;
}
```

```html popover-paddings-example.html
<ng-template #customContent>
    <kbq-list-selection>
        <kbq-list-option [value]="'Abarth'">Abarth</kbq-list-option>
        <kbq-list-option [value]="'Acura'">Acura</kbq-list-option>
        <kbq-list-option [value]="'Alfa Romeo'">Alfa Romeo</kbq-list-option>
        <kbq-list-option [value]="'Alpine'">Alpine</kbq-list-option>
        <kbq-list-option [value]="'Aston Martin'">Aston Martin</kbq-list-option>
        <kbq-list-option [value]="'Audi'">Audi</kbq-list-option>
        <kbq-list-option [value]="'Bentley'">Bentley</kbq-list-option>
        <kbq-list-option [value]="'BMW'">BMW</kbq-list-option>
        <kbq-list-option [value]="'Borgward'">Borgward</kbq-list-option>
        <kbq-list-option [value]="'Bugatti'">Bugatti</kbq-list-option>
        <kbq-list-option [value]="'Buick'">Buick</kbq-list-option>
        <kbq-list-option [value]="'BYD'">BYD</kbq-list-option>
        <kbq-list-option [value]="'Cadillac'">Cadillac</kbq-list-option>
        <kbq-list-option [value]="'Caterham'">Caterham</kbq-list-option>
        <kbq-list-option [value]="'Changan'">Changan</kbq-list-option>
        <kbq-list-option [value]="'Chery'">Chery</kbq-list-option>
        <kbq-list-option [value]="'Chevrolet'">Chevrolet</kbq-list-option>
        <kbq-list-option [value]="'Chrysler'">Chrysler</kbq-list-option>
        <kbq-list-option [value]="'Citroën'">Citroën</kbq-list-option>
        <kbq-list-option [value]="'Cupra'">Cupra</kbq-list-option>
        <kbq-list-option [value]="'Dacia'">Dacia</kbq-list-option>
        <kbq-list-option [value]="'Daewoo'">Daewoo</kbq-list-option>
        <kbq-list-option [value]="'Daihatsu'">Daihatsu</kbq-list-option>
        <kbq-list-option [value]="'Datsun'">Datsun</kbq-list-option>
        <kbq-list-option [value]="'DeLorean'">DeLorean</kbq-list-option>
        <kbq-list-option [value]="'Dodge'">Dodge</kbq-list-option>
        <kbq-list-option [value]="'DS Automobiles'">DS Automobiles</kbq-list-option>
        <kbq-list-option [value]="'Eagle'">Eagle</kbq-list-option>
        <kbq-list-option [value]="'Ferrari'">Ferrari</kbq-list-option>
        <kbq-list-option [value]="'Fiat'">Fiat</kbq-list-option>
        <kbq-list-option [value]="'Ford'">Ford</kbq-list-option>
        <kbq-list-option [value]="'Geely'">Geely</kbq-list-option>
        <kbq-list-option [value]="'Genesis'">Genesis</kbq-list-option>
        <kbq-list-option [value]="'GMC'">GMC</kbq-list-option>
        <kbq-list-option [value]="'Great Wall'">Great Wall</kbq-list-option>
        <kbq-list-option [value]="'Haval'">Haval</kbq-list-option>
        <kbq-list-option [value]="'Honda'">Honda</kbq-list-option>
        <kbq-list-option [value]="'Hummer'">Hummer</kbq-list-option>
        <kbq-list-option [value]="'Hyundai'">Hyundai</kbq-list-option>
        <kbq-list-option [value]="'Infiniti'">Infiniti</kbq-list-option>
        <kbq-list-option [value]="'Isuzu'">Isuzu</kbq-list-option>
        <kbq-list-option [value]="'IVECO'">IVECO</kbq-list-option>
        <kbq-list-option [value]="'Jaguar'">Jaguar</kbq-list-option>
        <kbq-list-option [value]="'Jeep'">Jeep</kbq-list-option>
        <kbq-list-option [value]="'Kia'">Kia</kbq-list-option>
        <kbq-list-option [value]="'Koenigsegg'">Koenigsegg</kbq-list-option>
        <kbq-list-option [value]="'Lada'">Lada</kbq-list-option>
        <kbq-list-option [value]="'Lamborghini'">Lamborghini</kbq-list-option>
        <kbq-list-option [value]="'Lancia'">Lancia</kbq-list-option>
        <kbq-list-option [value]="'Land Rover'">Land Rover</kbq-list-option>
        <kbq-list-option [value]="'Lexus'">Lexus</kbq-list-option>
        <kbq-list-option [value]="'Lincoln'">Lincoln</kbq-list-option>
        <kbq-list-option [value]="'Lotus'">Lotus</kbq-list-option>
        <kbq-list-option [value]="'Maserati'">Maserati</kbq-list-option>
        <kbq-list-option [value]="'Maybach'">Maybach</kbq-list-option>
        <kbq-list-option [value]="'Mazda'">Mazda</kbq-list-option>
        <kbq-list-option [value]="'McLaren'">McLaren</kbq-list-option>
        <kbq-list-option [value]="'Mercedes-Benz'">Mercedes-Benz</kbq-list-option>
        <kbq-list-option [value]="'MG'">MG</kbq-list-option>
        <kbq-list-option [value]="'Mini'">Mini</kbq-list-option>
        <kbq-list-option [value]="'Mitsubishi'">Mitsubishi</kbq-list-option>
        <kbq-list-option [value]="'Nissan'">Nissan</kbq-list-option>
        <kbq-list-option [value]="'Opel'">Opel</kbq-list-option>
        <kbq-list-option [value]="'Pagani'">Pagani</kbq-list-option>
        <kbq-list-option [value]="'Peugeot'">Peugeot</kbq-list-option>
        <kbq-list-option [value]="'Polestar'">Polestar</kbq-list-option>
        <kbq-list-option [value]="'Porsche'">Porsche</kbq-list-option>
        <kbq-list-option [value]="'Ram'">Ram</kbq-list-option>
        <kbq-list-option [value]="'Renault'">Renault</kbq-list-option>
        <kbq-list-option [value]="'Rivian'">Rivian</kbq-list-option>
        <kbq-list-option [value]="'Rolls-Royce'">Rolls-Royce</kbq-list-option>
        <kbq-list-option [value]="'Saab'">Saab</kbq-list-option>
        <kbq-list-option [value]="'Seat'">Seat</kbq-list-option>
        <kbq-list-option [value]="'Skoda'">Skoda</kbq-list-option>
        <kbq-list-option [value]="'Smart'">Smart</kbq-list-option>
        <kbq-list-option [value]="'SsangYong'">SsangYong</kbq-list-option>
        <kbq-list-option [value]="'Subaru'">Subaru</kbq-list-option>
        <kbq-list-option [value]="'Suzuki'">Suzuki</kbq-list-option>
        <kbq-list-option [value]="'Tesla'">Tesla</kbq-list-option>
        <kbq-list-option [value]="'Toyota'">Toyota</kbq-list-option>
        <kbq-list-option [value]="'Volkswagen'">Volkswagen</kbq-list-option>
        <kbq-list-option [value]="'Volvo'">Volvo</kbq-list-option>
        <kbq-list-option [value]="'Zotye'">Zotye</kbq-list-option>
    </kbq-list-selection>
</ng-template>

<ng-template #customFooter>
    <div class="layout-row layout-padding-l" style="gap: 16px">
        <button kbq-button [color]="'contrast'">Apply</button>
        <button kbq-button>Cancel</button>
    </div>
</ng-template>

<div style="gap: 16px" class="layout-row layout-wrap layout-align-center">
    <button
        kbq-button
        kbqPopover
        [kbqPopoverDefaultPaddings]="false"
        [kbqPopoverArrow]="false"
        [kbqPopoverPlacement]="popUpPlacements.BottomLeft"
        [kbqPopoverOffset]="4"
        [kbqPopoverClass]="'popover-custom-paddings-example'"
        [kbqPopoverSize]="popUpSizes.Medium"
        [kbqPopoverContent]="customContent"
        [kbqPopoverFooter]="customFooter"
    >
        Custom Paddings
    </button>
</div>
```

The default padding also keeps the focus outline of the first field in the content area from being clipped by the panel, which hides its overflow. After resetting it, reserve that space in your own padding.

### Hiding the arrow

The arrow extending from the popover can be hidden, which allows the popover to be positioned closer to the trigger element.

**Example: Popover arrowless** (`popover-arrowless`)

```ts popover-arrowless-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqComponentColors, PopUpSizes } from '@koobiq/components/core';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqPopoverModule } from '@koobiq/components/popover';

@Component({
    selector: 'popover-arrowless-example',
    imports: [
        KbqButtonModule,
        KbqInputModule,
        KbqPopoverModule
    ],
    template: `
        <ng-template #customContent>
            <div [style.width.px]="400">
                <div class="layout-margin-bottom-s">I wonder what this?</div>
                <kbq-form-field>
                    <input kbqInput placeholder="Interesting thought" />
                </kbq-form-field>
            </div>
        </ng-template>

        <ng-template #customFooter>
            <div class="layout-row layout-wrap layout-gap-l">
                <button color="contrast" kbq-button (click)="kbqPopover.hide(0)">Save</button>
                <button kbq-button (click)="kbqPopover.hide(0)">Cancel</button>
            </div>
        </ng-template>

        <button
            #kbqPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverSize]="popoverSize"
            [kbqPopoverArrow]="false"
        >
            Open popover
        </button>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-align-center-center layout-row'
    }
})
export class PopoverArrowlessExample {
    colors = KbqComponentColors;
    protected readonly popoverSize = PopUpSizes.Large;
}
```

### Offset

The popover has an additional `kbqPopoverOffset` parameter that controls the offset of the popover relative to the trigger element.

**Example: Popover arrow and offset example** (`popover-arrow-and-offset`)

```ts popover-arrow-and-offset-example.ts
import { AfterViewInit, ChangeDetectionStrategy, Component, viewChild } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqButtonToggleChange, KbqButtonToggleGroup, KbqButtonToggleModule } from '@koobiq/components/button-toggle';
import { PopUpSizes } from '@koobiq/components/core';
import { KbqDd, KbqDl, KbqDt } from '@koobiq/components/dl';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqPopoverModule } from '@koobiq/components/popover';
import { KbqToggleChange, KbqToggleModule } from '@koobiq/components/toggle';

@Component({
    selector: 'popover-arrow-and-offset-example',
    imports: [
        KbqButtonModule,
        KbqPopoverModule,
        KbqDl,
        KbqDt,
        KbqDd,
        KbqToggleModule,
        KbqButtonToggleModule,
        KbqInputModule
    ],
    template: `
        <div class="layout-row layout-align-space-around-center">
            <div class="layout-row layout-align-center-center">
                <ng-template #customContent>
                    <div [style.width.px]="400">
                        <div class="layout-margin-bottom-s">I wonder what this?</div>
                        <kbq-form-field>
                            <input kbqInput placeholder="Interesting thought" />
                        </kbq-form-field>
                    </div>
                </ng-template>

                <ng-template #customFooter>
                    <div class="layout-row layout-wrap layout-gap-l">
                        <button color="contrast" kbq-button (click)="kbqPopover.hide(0)">Save</button>
                        <button kbq-button (click)="kbqPopover.hide(0)">Cancel</button>
                    </div>
                </ng-template>

                <button
                    #kbqPopover="kbqPopover"
                    kbq-button
                    kbqPopover
                    [kbqPopoverContent]="customContent"
                    [kbqPopoverFooter]="customFooter"
                    [kbqPopoverArrow]="arrow"
                    [kbqPopoverOffset]="offset"
                    [kbqPopoverSize]="popoverSize"
                >
                    Open popover
                </button>
            </div>

            <kbq-dl [vertical]="true">
                <kbq-dt>Arrow</kbq-dt>
                <kbq-dd>
                    <kbq-toggle [checked]="arrow" (change)="onArrowChange($event)" />
                </kbq-dd>

                <kbq-dt>Offset</kbq-dt>
                <kbq-dd>
                    <kbq-button-toggle-group [value]="offset" (change)="onOffsetChange($event)">
                        <kbq-button-toggle [value]="-4">-4px</kbq-button-toggle>
                        <kbq-button-toggle [value]="0">0px</kbq-button-toggle>
                        <kbq-button-toggle [value]="4">4px</kbq-button-toggle>
                        <kbq-button-toggle [value]="8">8px</kbq-button-toggle>
                    </kbq-button-toggle-group>
                </kbq-dd>
            </kbq-dl>
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverArrowAndOffsetExample implements AfterViewInit {
    protected arrow = true;
    protected offset: number | null = null;
    protected offsetChanged = false;
    protected readonly popoverSize = PopUpSizes.Large;
    protected readonly toggleGroup = viewChild.required(KbqButtonToggleGroup);

    ngAfterViewInit() {
        Promise.resolve().then(() => {
            this.toggleGroup().buttonToggles().at(3)!.checked = true;
        });
    }

    onArrowChange({ checked }: KbqToggleChange) {
        if (!this.offsetChanged) {
            if (checked) {
                this.toggleGroup().buttonToggles().at(3)!.checked = true;
            } else {
                this.toggleGroup().buttonToggles().at(2)!.checked = true;
            }
        }

        this.arrow = checked;
    }

    onOffsetChange({ value }: KbqButtonToggleChange) {
        this.offsetChanged = true;
        this.offset = value;
    }
}
```

### Alignment

Consider the context in which the popover appears. Configure the alignment so that the popover does not obscure elements that may be needed.

#### Centered on the element

The arrow is positioned at the center of the popover and points to the middle of the trigger element.

**Example: Popover placement center** (`popover-placement-center`)

```ts popover-placement-center-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { PopUpPlacements } from '@koobiq/components/core';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqPopoverModule, KbqPopoverTrigger } from '@koobiq/components/popover';

@Component({
    selector: 'popover-placement-center-example',
    imports: [KbqInputModule, KbqButtonModule, KbqPopoverModule],
    templateUrl: 'popover-placement-center-example.html',
    styles: `
        .popover-placement-center-example {
            display: flex;
            column-gap: 16px;
        }

        .popover-placement-center-example__column {
            display: flex;
            flex-direction: column;
            justify-content: center;
            width: 100%;
        }

        .popover-placement-center-example__column button {
            width: 100%;
        }

        .popover-placement-center-example__column button + button {
            margin-top: 48px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverPlacementCenterExample {
    placements = PopUpPlacements;
    activePopover: KbqPopoverTrigger;
}
```

```html popover-placement-center-example.html
<div class="popover-placement-center-example">
    <ng-template #customContent>
        <div class="layout-margin-bottom-s">Интересно, что это такое?</div>
        <kbq-form-field>
            <input kbqInput placeholder="Интересная мысль" />
        </kbq-form-field>
    </ng-template>

    <ng-template #customFooter>
        <div style="gap: 16px" class="layout-row layout-wrap">
            <button kbq-button [color]="'contrast'" (click)="activePopover.hide(0)">Сохранить</button>
            <button kbq-button (click)="activePopover.hide(0)">Отмена</button>
        </div>
    </ng-template>

    <div class="popover-placement-center-example__column">
        <button
            #kbqLeftPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverPlacement]="placements.Left"
            (click)="activePopover = kbqLeftPopover"
        >
            left
        </button>
    </div>
    <div class="popover-placement-center-example__column">
        <button
            #kbqTopPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverPlacement]="placements.Top"
            (click)="activePopover = kbqTopPopover"
        >
            top
        </button>
        <button
            #kbqBottomPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverPlacement]="placements.Bottom"
            (click)="activePopover = kbqBottomPopover"
        >
            bottom
        </button>
    </div>
    <div class="popover-placement-center-example__column">
        <button
            #kbqRightPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverPlacement]="placements.Right"
            (click)="activePopover = kbqRightPopover"
        >
            right
        </button>
    </div>
</div>
```

#### At the edge of the element

The popover aligns with the edge of the trigger element; the arrow is at a fixed distance from the edge of the popover. If the trigger element is too small, the arrow aligns with its center.

**Example: Popover placement edges** (`popover-placement-edges`)

```ts popover-placement-edges-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { PopUpPlacements } from '@koobiq/components/core';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqPopoverModule, KbqPopoverTrigger } from '@koobiq/components/popover';

@Component({
    selector: 'popover-placement-edges-example',
    imports: [
        KbqPopoverModule,
        KbqButtonModule,
        KbqInputModule
    ],
    templateUrl: 'popover-placement-edges-example.html',
    styles: `
        .popover-placement-edges-example {
            display: flex;
            column-gap: 16px;
        }

        .popover-placement-edges-example__column {
            display: flex;
            flex-direction: column;
            justify-content: center;
            width: 100%;
        }

        .popover-placement-edges-example__column button {
            width: 100%;
        }

        .popover-placement-edges-example__column button + button {
            margin-top: 52px;
        }

        .popover-placement-edges-example__column.middle button + button {
            margin-top: 136px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverPlacementEdgesExample {
    placements = PopUpPlacements;
    activePopover: KbqPopoverTrigger;
}
```

```html popover-placement-edges-example.html
<div class="popover-placement-edges-example">
    <ng-template #customContent>
        <div class="layout-margin-bottom-s">Интересно, что это такое?</div>
        <kbq-form-field>
            <input kbqInput placeholder="Интересная мысль" />
        </kbq-form-field>
    </ng-template>

    <ng-template #customFooter>
        <div style="gap: 16px" class="layout-row layout-wrap">
            <button kbq-button [color]="'contrast'" (click)="activePopover.hide(0)">Сохранить</button>
            <button kbq-button (click)="activePopover.hide(0)">Отмена</button>
        </div>
    </ng-template>

    <div class="popover-placement-edges-example__column">
        <button
            #kbqLeftTopPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverArrow]="false"
            [kbqPopoverPlacement]="placements.LeftTop"
            (click)="activePopover = kbqLeftTopPopover"
        >
            leftTop
        </button>
        <button
            #kbqLeftBottomPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverArrow]="false"
            [kbqPopoverPlacement]="placements.LeftBottom"
            (click)="activePopover = kbqLeftBottomPopover"
        >
            leftBottom
        </button>
    </div>
    <div class="popover-placement-edges-example__column middle">
        <button
            #kbqTopLeftPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverPlacement]="placements.TopLeft"
            (click)="activePopover = kbqTopLeftPopover"
        >
            topLeft
        </button>
        <button
            #kbqBottomLeftPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverPlacement]="placements.BottomLeft"
            (click)="activePopover = kbqBottomLeftPopover"
        >
            bottomLeft
        </button>
    </div>
    <div class="popover-placement-edges-example__column"></div>
    <div class="popover-placement-edges-example__column middle">
        <button
            #kbqTopRightPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverPlacement]="placements.TopRight"
            (click)="activePopover = kbqTopRightPopover"
        >
            topRight
        </button>
        <button
            #kbqBottomRightPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverPlacement]="placements.BottomRight"
            (click)="activePopover = kbqBottomRightPopover"
        >
            bottomRight
        </button>
    </div>
    <div class="popover-placement-edges-example__column">
        <button
            #kbqRightTopPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverArrow]="false"
            [kbqPopoverPlacement]="placements.RightTop"
            (click)="activePopover = kbqRightTopPopover"
        >
            rightTop
        </button>
        <button
            #kbqRightBottomPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqPopoverArrow]="false"
            [kbqPopoverPlacement]="placements.RightBottom"
            (click)="activePopover = kbqRightBottomPopover"
        >
            rightBottom
        </button>
    </div>
</div>
```

### Behavior when scrolling the page

By default, the popover does not close when the page scrolls and scrolls along with the page, remaining at the position from which it was opened.

The popover can be configured to close when the page scrolls; which behavior to choose is up to the designer.

**Example: popover-scroll** (`popover-scroll`)

```ts popover-scroll-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqPopoverModule, KbqPopoverTrigger } from '@koobiq/components/popover';

@Component({
    selector: 'popover-scroll-example',
    imports: [
        KbqButtonModule,
        KbqPopoverModule,
        KbqInputModule
    ],
    template: `
        <ng-template #customContent>
            <div class="layout-margin-bottom-s">Интересно, что это такое?</div>
            <kbq-form-field>
                <input kbqInput placeholder="Интересная мысль" />
            </kbq-form-field>
        </ng-template>

        <ng-template #customFooter>
            <div class="layout-row layout-wrap" style="gap: 16px">
                <button kbq-button [color]="'contrast'" (click)="activePopover.hide(0)">Сохранить</button>
                <button kbq-button (click)="activePopover.hide(0)">Отмена</button>
            </div>
        </ng-template>

        <div class="layout-row layout-wrap" style="gap: 16px">
            <button
                #kbqNoClosePopover="kbqPopover"
                kbq-button
                kbqPopover
                [kbqPopoverClass]="'popover-scroll-example'"
                [kbqPopoverContent]="customContent"
                [kbqPopoverFooter]="customFooter"
                (click)="activePopover = kbqNoClosePopover"
            >
                Поповер не закрывается при прокрутке
            </button>
            <button
                #kbqClosePopover="kbqPopover"
                kbq-button
                kbqPopover
                [kbqPopoverCloseOnScroll]="true"
                [kbqPopoverClass]="'popover-scroll-example'"
                [kbqPopoverContent]="customContent"
                [kbqPopoverFooter]="customFooter"
                (click)="activePopover = kbqClosePopover"
            >
                Закрывается при прокрутке
            </button>
        </div>
    `,
    styles: `
        ::ng-deep .kbq-popover.kbq-popover_medium.popover-scroll-example {
            width: 400px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverScrollExample {
    activePopover: KbqPopoverTrigger;
}
```

The behavior is controlled by `kbqPopoverCloseOnScroll`, which has three states. Left unbound, the popover survives a scroll but still closes once it has scrolled out of a container marked with the `kbq-hide-nested-popup` class. Set to `true`, it closes on any scroll. Set to `false`, it never closes on a scroll — not even in such a container.

### Layering

By default, the popover appears above the horizontal [Navbar](https://koobiq.io/en/components/navbar.md) and [Topbar](https://koobiq.io/en/components/top-bar.md). To have it slide under them while the page scrolls, as in the example below, mark the content area with `kbqOverlayLayer` — see [Panels under a sticky header](https://koobiq.io/en/components/core.md#panels-under-a-sticky-header). A popover with `kbqPopoverStickToWindow` stays above the bars.

**Example: popover-scrolling-and-layering** (`popover-scrolling-and-layering`)

```ts popover-scrolling-and-layering-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';

@Component({
    selector: 'popover-scrolling-and-layering-example',
    template: `
        <iframe
            src="/examples/popover-scrolling-and-layering-page"
            loading="lazy"
            width="648"
            height="400"
            title="popover-scrolling-and-layering-example"
            style="border: none"
        ></iframe>
    `,
    styles: `
        :host {
            display: flex;
            margin: -20px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverScrollingAndLayeringExample {}
```

### Usage examples

The popover can be used without a header and footer — these are optional elements.

The size of the vertical inner padding in the content area depends on the presence of a header and footer. Without a header, the top padding increases. Without a footer, the bottom padding increases.

**Example: Popover Header** (`popover-header`)

```ts popover-header-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqBadgeColors, KbqBadgeModule } from '@koobiq/components/badge';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqDd, KbqDl, KbqDt } from '@koobiq/components/dl';
import { KbqPopoverModule } from '@koobiq/components/popover';

@Component({
    selector: 'popover-header-example',
    imports: [
        KbqDl,
        KbqDt,
        KbqDd,
        KbqPopoverModule,
        KbqButtonModule,
        KbqBadgeModule
    ],
    templateUrl: 'popover-header-example.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverHeaderExample {
    badgeColors = KbqBadgeColors;
}
```

```html popover-header-example.html
<ng-template #customContent>
    <p style="margin-top: 0">
        Жёлтые кубики — это песок, кубик зелёный — весенний лесок, синие кубики — это река. Кубиков много — река широка.
        Кубик на кубик — растут этажи: учатся строить UI малыши.
    </p>
    <kbq-dl class="kbq-dl_wide">
        <kbq-dt>Желтые кубики</kbq-dt>
        <kbq-dd>Песок</kbq-dd>

        <kbq-dt>Кубик Зеленый</kbq-dt>
        <kbq-dd>Весенний лесок</kbq-dd>

        <kbq-dt>Кубиков много</kbq-dt>
        <kbq-dd>Река широка Кубик на кубик Растут этажи</kbq-dd>

        <kbq-dt>Кубик на кубик</kbq-dt>
        <kbq-dd>
            <kbq-badge [badgeColor]="badgeColors.FadeContrast">Растут этажи</kbq-badge>
        </kbq-dd>

        <kbq-dt>Кубики вместе сложились</kbq-dt>
        <kbq-dd>И сайт оживает</kbq-dd>

        <kbq-dt>Взрослые мир создают</kbq-dt>
        <kbq-dd>О котором мечтают</kbq-dd>
    </kbq-dl>
</ng-template>

<ng-template #customFooter>
    <button kbq-button (click)="kbqPopover.hide()">Закрыть</button>
</ng-template>

<div style="gap: 16px" class="layout-row layout-wrap">
    <button
        kbq-button
        kbqPopover
        [kbqPopoverHasCloseButton]="true"
        [kbqPopoverContent]="customContent"
        [kbqPopoverHeader]="'Заголовок поповера'"
    >
        Только с шапкой
    </button>
    <button
        #kbqPopover="kbqPopover"
        kbq-button
        kbqPopover
        [kbqPopoverContent]="customContent"
        [kbqPopoverFooter]="customFooter"
    >
        Только с футером
    </button>
    <button kbq-button kbqPopover [kbqPopoverContent]="customContent">Без хедера и футера</button>
</div>
```

#### Title in the content area

The title can be moved to the content area instead of placing it in a separate header panel. This approach works well when there is no scrolling in the popover.

**Example: Popover content** (`popover-content`)

```ts popover-content-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqComponentColors } from '@koobiq/components/core';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqPopoverModule } from '@koobiq/components/popover';

@Component({
    selector: 'popover-content-example',
    imports: [
        KbqDropdownModule,
        KbqButtonModule,
        KbqInputModule,
        KbqIconModule,
        KbqPopoverModule
    ],
    templateUrl: 'popover-content-example.html',
    styleUrls: ['popover-content-example.css'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverContentExample {
    members: Record<string, string>[] = [
        { name: 'Alex Unipraise', role: 'Editor' },
        { name: 'Serge Vox', role: 'Editor' },
        { name: 'Rick Brick', role: 'Viewer' },
        { name: 'Viber Curly', role: 'Viewer' },
        { name: 'Jackie Ckang', role: 'Viewer' },
        { name: 'Robert Skinner', role: 'Viewer' },
        { name: 'Woodie Hoodie', role: 'Viewer' },
        { name: 'Alex Buckmaster', role: 'Viewer' },
        { name: 'Chris Glasser', role: 'Viewer' },
        { name: 'Corina McCoy', role: 'Viewer' }
    ];
    protected readonly componentColors = KbqComponentColors;
}
```

```html popover-content-example.html
<ng-template #shareContent>
    <div class="kbq-subheading">Share project</div>
    <div class="popover-content-share-example__text">
        Give your teammates access to the project and start collaborating in real time
    </div>
    <form novalidate class="kbq-form-vertical">
        <div class="kbq-form__fieldset">
            <div class="kbq-form__label-wrapper">
                <label class="kbq-form__label">Email</label>
            </div>
            <div class="kbq-form__fieldset kbq-horizontal">
                <div class="kbq-form__row">
                    <kbq-form-field class="kbq-form__control">
                        <input kbqInput name="input" />
                    </kbq-form-field>
                </div>
                <div class="kbq-form__row">
                    <button kbq-button [color]="'contrast'" (click)="kbqSharePopover.hide(0)">Send invite</button>
                </div>
            </div>
        </div>
    </form>
    <div class="example-popover-content-share__list">
        <div class="example-popover-content-share__list-title">Members</div>
        @for (member of members; track member) {
            <div class="example-popover-content-share__list-item">
                <div class="example-popover-content-share__list-item-left">
                    <img
                        alt="{{ member.name }}"
                        src="https://koobiq.io/assets/images/popover/list-member-{{ $index + 1 }}.png"
                        class="example-popover-content-share__list-item-image"
                    />
                    <div class="example-popover-content-share__list-item-name">{{ member.name }}</div>
                </div>
                <button
                    kbq-button
                    class="kbq-button_transparent"
                    [color]="componentColors.Contrast"
                    [kbqDropdownTriggerFor]="appDropdown"
                >
                    {{ member.role }}
                    <i kbq-icon="kbq-chevron-down-s_16" [color]="componentColors.Contrast"></i>
                </button>
            </div>
        }
        <kbq-dropdown #appDropdown="kbqDropdown" [class]="'max-width'" [navigationWithWrap]="true">
            <button kbq-dropdown-item>Editor</button>
            <button kbq-dropdown-item>Viewer</button>
        </kbq-dropdown>
    </div>
</ng-template>

<ng-template #customContent>
    <div
        class="example-popover-content-custom__image"
        [style]="{ 'background-image': 'url(\'/assets/images/popover/hero-header.jpg\')' }"
    ></div>
    <div class="example-popover-content-custom__container">
        <h4 class="kbq-title">Абракадабра!</h4>
        <p>Таинственное слово, которому приписывалась чудодейственная сила</p>
        <button kbq-button [color]="'contrast'" (click)="kbqCustomPopover.hide(0)">Сделать магию</button>
    </div>
</ng-template>

<div style="gap: 16px" class="layout-row layout-wrap">
    <button
        #kbqSharePopover="kbqPopover"
        kbq-button
        kbqPopover
        [kbqPopoverHasCloseButton]="true"
        [kbqPopoverClass]="'example-popover-content-share'"
        [kbqPopoverContent]="shareContent"
    >
        Без панели хедера
    </button>
    <button
        #kbqCustomPopover="kbqPopover"
        kbq-button
        kbqPopover
        [kbqPopoverHasCloseButton]="true"
        [kbqPopoverClass]="'example-popover-content-custom'"
        [kbqPopoverContent]="customContent"
    >
        Нестандартные стили и отступы
    </button>
</div>
```

```css popover-content-example.css
::ng-deep .kbq-popover.example-popover-content-custom .kbq-title {
    margin: 0 0;
}

::ng-deep .kbq-popover.example-popover-content-custom .kbq-popover__content {
    padding: 0 0;
}

::ng-deep .kbq-popover.example-popover-content-custom .kbq-popover__content p {
    margin-top: 8px;
    margin-bottom: 16px;
    text-align: center;
}

::ng-deep
    .kbq-popover.example-popover-content-custom
    .kbq-popover__content
    .kbq-popover__close-container
    .kbq-popover__close {
    transform: none;
    top: 8px;
    right: 8px;
}

.example-popover-content-custom__image {
    padding-top: calc(172 / 400 * 100%);
    background-size: cover;
}

.example-popover-content-custom__container {
    padding: 24px 32px 40px 32px;
    display: flex;
    flex-direction: column;
    align-items: center;
}

::ng-deep .kbq-popover.example-popover-content-share .kbq-form__label {
    display: block;
}

::ng-deep .kbq-popover.example-popover-content-share .kbq-form__fieldset {
    justify-content: space-between;
}

::ng-deep .kbq-popover.example-popover-content-share .kbq-form__row:first-child {
    width: 100%;
}

::ng-deep
    .kbq-popover.example-popover-content-share
    .kbq-form__fieldset.kbq-horizontal
    .kbq-form__row:not(:first-child) {
    padding-left: 8px;
}

.example-popover-content-share__text {
    color: #6c7393;
    margin-top: 4px;
    margin-bottom: 12px;
}

.example-popover-content-share__list {
    margin-top: 16px;
}

.example-popover-content-share__list-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 12px;
}

.example-popover-content-share__list-item-left {
    display: flex;
    align-items: center;
}

.example-popover-content-share__list-item-image {
    width: 32px;
    height: 32px;
    border-radius: 100%;
    margin-right: 12px;
}
```

#### Close button in the top corner

The close button can be placed in the header or in the top right corner when there is no header panel. This is a way to eliminate a footer with a single "Close" button.

**Example: Popover close** (`popover-close`)

```ts popover-close-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqComponentColors } from '@koobiq/components/core';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqPopoverModule } from '@koobiq/components/popover';

@Component({
    selector: 'popover-close-example',
    imports: [KbqInputModule, KbqButtonModule, KbqPopoverModule, KbqIconModule, KbqDropdownModule],
    templateUrl: 'popover-close-example.html',
    styleUrls: ['popover-close-example.css'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverCloseExample {
    members: Record<string, string>[] = [
        { name: 'Alex Unipraise', role: 'Editor' },
        { name: 'Serge Vox', role: 'Editor' },
        { name: 'Rick Brick', role: 'Viewer' },
        { name: 'Viber Curly', role: 'Viewer' },
        { name: 'Jackie Ckang', role: 'Viewer' },
        { name: 'Robert Skinner', role: 'Viewer' },
        { name: 'Woodie Hoodie', role: 'Viewer' },
        { name: 'Alex Buckmaster', role: 'Viewer' },
        { name: 'Chris Glasser', role: 'Viewer' },
        { name: 'Corina McCoy', role: 'Viewer' }
    ];
    protected readonly componentColors = KbqComponentColors;
}
```

```html popover-close-example.html
<ng-template #customContent>
    <h1 style="margin-top: 0" class="kbq-headline">Making hybrid‑remote inclusive with unique audio technology</h1>
    <p style="margin-top: 0">
        Traditional video conferencing can make remote colleagues feel like second-class citizens
    </p>
    <p>
        Around is designed to be inclusive, so remote team members truly feel like part of the group. By being able to
        use your own device, everyone is seen and heard clearly — and no one ever sounds “far from the mic.” Everyone
        stays engaged. No one steals the spotlight.
    </p>
    <p>
        We take privacy and security very seriously. We leverage industry standard data security policies to keep all
        customer data safe and treat the protection of user privacy and data with the utmost importance. You can learn
        more about our privacy policy here.
    </p>
    <p>
        Encrypted Calls & Communication. All calls (including audio/video) are secured with TLS and AES-128 encryption.
    </p>
    <p>
        We take privacy and security very seriously. We leverage industry standard data security policies to keep all
        customer data safe and treat the protection of user privacy and data with the utmost importance. You can learn
        more about our privacy policy here.
    </p>
    <p style="margin-bottom: 0">
        Around is designed to be inclusive, so remote team members truly feel like part of the group. By being able to
        use your own device, everyone is seen and heard clearly — and no one ever sounds “far from the mic.” Everyone
        stays engaged. No one steals the spotlight.
    </p>
</ng-template>

<ng-template #shareContent>
    <div class="kbq-subheading">Share project</div>
    <div class="example-popover-close__text">
        Give your teammates access to the project and start collaborating in real time
    </div>
    <form novalidate class="kbq-form-vertical">
        <div class="kbq-form__fieldset">
            <div class="kbq-form__label-wrapper">
                <label class="kbq-form__label">Email</label>
            </div>
            <div class="kbq-form__fieldset kbq-horizontal">
                <div class="kbq-form__row">
                    <kbq-form-field class="kbq-form__control">
                        <input kbqInput name="input" />
                    </kbq-form-field>
                </div>
                <div class="kbq-form__row">
                    <button kbq-button [color]="'contrast'" (click)="kbqSharePopover.hide(0)">Send invite</button>
                </div>
            </div>
        </div>
    </form>
    <div class="example-popover-close__list">
        <div class="example-popover-close__list-title">Members</div>
        @for (member of members; track member) {
            <div class="example-popover-close__list-item">
                <div class="example-popover-close__list-item-left">
                    <img
                        alt="{{ member.name }}"
                        src="https://koobiq.io/assets/images/popover/list-member-{{ $index + 1 }}.png"
                        class="example-popover-close__list-item-image"
                    />
                    <div class="example-popover-close__list-item-name">{{ member.name }}</div>
                </div>
                <button
                    kbq-button
                    class="kbq-button_transparent"
                    [color]="componentColors.Contrast"
                    [kbqDropdownTriggerFor]="appDropdown"
                >
                    {{ member.role }}
                    <i kbq-icon="kbq-chevron-down-s_16" [color]="componentColors.Contrast"></i>
                </button>
            </div>
        }
        <kbq-dropdown #appDropdown="kbqDropdown" [class]="'max-width'" [navigationWithWrap]="true">
            <button kbq-dropdown-item>Editor</button>
            <button kbq-dropdown-item>Viewer</button>
        </kbq-dropdown>
    </div>
</ng-template>

<div style="gap: 16px" class="layout-row layout-wrap">
    <button
        #kbqSharePopover="kbqPopover"
        kbq-button
        kbqPopover
        [kbqPopoverHasCloseButton]="true"
        [kbqPopoverContent]="customContent"
    >
        Крестик в углу с большим заголовком
    </button>
    <button
        kbq-button
        kbqPopover
        [kbqPopoverHasCloseButton]="true"
        [kbqPopoverClass]="'example-popover-close'"
        [kbqPopoverContent]="shareContent"
    >
        Крестик в углу в окне «Поделиться»
    </button>
</div>
```

```css popover-close-example.css
::ng-deep .kbq-popover.example-popover-close .kbq-form__label {
    display: block;
}

::ng-deep .kbq-popover.example-popover-close .kbq-form__fieldset {
    justify-content: space-between;
}

::ng-deep .kbq-popover.example-popover-close .kbq-form__row:first-child {
    width: 100%;
}

::ng-deep .kbq-popover.example-popover-close .kbq-form__fieldset.kbq-horizontal .kbq-form__row:not(:first-child) {
    padding-left: 8px;
}

.example-popover-close__text {
    color: #6c7393;
    margin-top: 4px;
    margin-bottom: 12px;
}

.example-popover-close__list {
    margin-top: 16px;
}

.example-popover-close__list-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 12px;
}

.example-popover-close__list-item-left {
    display: flex;
    align-items: center;
}

.example-popover-close__list-item-image {
    width: 32px;
    height: 32px;
    border-radius: 100%;
    margin-right: 12px;
}
```

#### Confirming an action

`[kbqPopoverConfirm]` is a ready-made variant of the popover that asks the user to confirm an action. It has neither a header nor a footer: the panel holds the question and a single confirming button, emits `confirm` when that button is pressed, and closes itself.

```html
<button kbq-button kbqPopoverConfirm (confirm)="deleteReport()">Delete</button>
```

Both strings are optional and can be set per trigger:

```html
<button
    kbq-button
    kbqPopoverConfirm
    kbqPopoverConfirmText="Delete the report?"
    kbqPopoverConfirmButtonText="Delete"
    (confirm)="deleteReport()"
>
    Delete
</button>
```

Left unbound, the question and the button caption come from the `popoverConfirm` section of the active locale, so an application that switched its locale gets the translated wording without configuring anything. To override the wording for a whole application — or for any part of it — provide `KBQ_POPOVER_CONFIRM_TEXT` and `KBQ_POPOVER_CONFIRM_BUTTON_TEXT`: an input beats the provider, and the provider beats the locale.

#### Opening next to a text selection

A popover can open next to selected text. It stays next to the selection while the page scrolls, and the selection stays in place when an action is chosen.

**Example: Popover next to a text selection** (`popover-text-selection`)

```ts popover-text-selection-example.ts
import { DOCUMENT } from '@angular/common';
import { ChangeDetectionStrategy, Component, ElementRef, inject, signal, viewChild } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqCaretRect, kbqCreateCaretOrigin, kbqGetSelectionRect } from '@koobiq/components/core';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqPopoverModule, KbqPopoverTrigger } from '@koobiq/components/popover';
import { debounceTime, fromEvent } from 'rxjs';

/** `Node.TEXT_NODE`, spelled out so that the example never reads a DOM global. */
const TEXT_NODE = 3;

const EMPTY_RECT: KbqCaretRect = { x: 0, y: 0, width: 0, height: 0 };

/** How long a keyboard selection has to stand still before the toolbar follows it, in milliseconds. */
const SELECTION_SETTLE_DELAY = 200;

/** Each format is the element the selected text is wrapped in. */
const FORMATS = [
    { tag: 'strong', icon: 'kbq-text-bold_16', label: 'Bold' },
    { tag: 'em', icon: 'kbq-text-italic_16', label: 'Italic' },
    { tag: 'u', icon: 'kbq-text-underline_16', label: 'Underline' }
] as const;

type Format = (typeof FORMATS)[number];

@Component({
    selector: 'popover-text-selection-example',
    imports: [
        KbqButtonModule,
        KbqIconModule,
        KbqPopoverModule
    ],
    template: `
        <ng-template #toolbar>
            <!-- A click on the toolbar would otherwise drop the selection the toolbar is attached to. -->
            <div
                aria-label="Text formatting"
                class="example-toolbar__buttons"
                role="group"
                (mousedown)="$event.preventDefault()"
            >
                @for (format of formats; track format.tag) {
                    <button
                        kbq-button
                        kbqStyle="transparent"
                        [attr.aria-label]="format.label"
                        [attr.aria-pressed]="active().includes(format.tag)"
                        [class.kbq-active]="active().includes(format.tag)"
                        (click)="toggle(format)"
                    >
                        <i [kbq-icon]="format.icon"></i>
                    </button>
                }
            </div>
        </ng-template>

        <p
            #text
            class="example-article"
            kbqPopover
            kbqPopoverAriaLabel="Text formatting"
            kbqPopoverClass="example-toolbar"
            kbqPopoverPlacement="top"
            kbqPopoverSize="custom"
            kbqTrigger="manual"
            [kbqPopoverArrow]="false"
            [kbqPopoverAutoFocus]="false"
            [kbqPopoverContent]="toolbar"
            [kbqPopoverDefaultPaddings]="false"
            [kbqPopoverOrigin]="origin"
            [kbqPopoverVisible]="visible()"
        >
            Select any part of this paragraph to format it: the toolbar appears right above the selected text and
            follows it while the page scrolls. Press Escape or click anywhere else to close it.
        </p>
    `,
    styles: `
        :host {
            display: flex;
            justify-content: center;
            padding: 48px 0 24px;
        }

        .example-article {
            max-width: 440px;
            margin: 0;
        }

        /* The panel holds a toolbar, so it never scrolls: a pixel of overflow would show a scrollbar. */
        ::ng-deep .example-toolbar .kbq-popover__content {
            overflow: hidden;
        }

        .example-toolbar__buttons {
            display: flex;
            padding: var(--kbq-size-xxs);
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverTextSelectionExample {
    private readonly document = inject(DOCUMENT);
    private readonly text = viewChild<ElementRef<HTMLElement>>('text');
    private readonly popover = viewChild(KbqPopoverTrigger);

    /** Stays on the selection: the rectangle is measured again every time the panel is positioned. */
    protected readonly origin = kbqCreateCaretOrigin(() => this.measureSelection());
    protected readonly visible = signal(false);
    /** The formats the selection already carries. */
    protected readonly active = signal<string[]>([]);
    protected readonly formats = FORMATS;

    /** Whether a pointer selection is still being dragged out. */
    private selecting = false;

    constructor() {
        // A pointer selection is only final on `pointerup`: shown on the first `selectionchange`, the toolbar
        // would anchor to the first characters of a selection the user is still dragging out.
        fromEvent<PointerEvent>(this.document, 'pointerdown')
            .pipe(takeUntilDestroyed())
            .subscribe((event) => {
                if (this.isInsideToolbar(event.target)) return;

                this.selecting = true;
                this.visible.set(false);
            });

        fromEvent<PointerEvent>(this.document, 'pointerup')
            .pipe(takeUntilDestroyed())
            .subscribe((event) => {
                if (this.isInsideToolbar(event.target)) return;

                this.selecting = false;
                this.readSelection();
            });

        // A keyboard selection has no such end, so the toolbar follows it once it stops growing.
        fromEvent(this.document, 'selectionchange')
            .pipe(debounceTime(SELECTION_SETTLE_DELAY), takeUntilDestroyed())
            .subscribe(() => {
                if (!this.selecting) {
                    this.readSelection();
                }
            });

        // The toolbar never takes focus on its own, so its `Escape` and `Tab` handlers are out of reach.
        fromEvent<KeyboardEvent>(this.document, 'keydown')
            .pipe(takeUntilDestroyed())
            .subscribe((event) => this.handleKeydown(event));
    }

    protected toggle(format: Format): void {
        const range = this.getRange();

        if (!range) return;

        const wrapper = this.findWrapper(range, format.tag);
        const next = this.document.createRange();

        if (wrapper) {
            // The whole wrapper goes, so a selection inside a longer formatted run unformats that run.
            const contents = Array.from(wrapper.childNodes);

            if (!contents.length) return;

            wrapper.replaceWith(...contents);

            next.setStartBefore(contents[0]);
            next.setEndAfter(contents[contents.length - 1]);
        } else {
            const element = this.document.createElement(format.tag);

            // Unlike `surroundContents`, this survives a selection that starts or ends inside another element.
            element.append(range.extractContents());
            range.insertNode(element);

            next.selectNodeContents(element);
        }

        this.select(next);

        this.readSelection();
        this.popover()?.updatePosition(true);
    }

    private handleKeydown(event: KeyboardEvent): void {
        if (!this.visible()) return;

        if (event.key === 'Escape') {
            this.visible.set(false);
        }

        // The panel is in an overlay at the end of the document, so `Tab` would walk past it.
        if (event.key === 'Tab' && !event.shiftKey && !this.isInsideToolbar(this.document.activeElement)) {
            const button = this.document.querySelector<HTMLButtonElement>('.example-toolbar button');

            if (button) {
                event.preventDefault();
                button.focus();
            }
        }
    }

    private readSelection(): void {
        const range = this.getRange();
        const wasVisible = this.visible();

        this.visible.set(!!range);
        this.active.set(range ? FORMATS.filter(({ tag }) => this.findWrapper(range, tag)).map(({ tag }) => tag) : []);

        // An open panel has to be told to move, and only a re-applied position re-reads the origin rectangle.
        if (wasVisible && range) {
            this.popover()?.updatePosition(true);
        }
    }

    private isInsideToolbar(target: EventTarget | null): boolean {
        return !!(target as HTMLElement | null)?.closest?.('.example-toolbar');
    }

    private measureSelection(): KbqCaretRect {
        const element = this.text()?.nativeElement;

        return (element && kbqGetSelectionRect(element)) ?? EMPTY_RECT;
    }

    /** The current selection, when it is a range of text inside the paragraph. */
    private getRange(): Range | null {
        const selection = this.document.getSelection();
        const element = this.text()?.nativeElement;

        if (!element || !selection || selection.isCollapsed || !selection.rangeCount) return null;

        if (!element.contains(selection.anchorNode) || !element.contains(selection.focusNode)) return null;

        return selection.getRangeAt(0);
    }

    private findWrapper(range: Range, tag: string): HTMLElement | null {
        const article = this.text()?.nativeElement;
        const node = range.commonAncestorContainer;
        const element = node.nodeType === TEXT_NODE ? node.parentElement : (node as HTMLElement);
        const wrapper = element?.closest<HTMLElement>(tag);

        return wrapper && article?.contains(wrapper) ? wrapper : null;
    }

    private select(range: Range): void {
        const selection = this.document.getSelection();

        selection?.removeAllRanges();
        selection?.addRange(range);
    }
}
```

`kbqPopoverOrigin` accepts an element or a rectangle in viewport coordinates. `kbqCreateCaretOrigin` creates a dynamic origin for the selection, and `kbqGetSelectionRect` measures its position inside the element. `updatePosition(true)` moves an open popover to a changed selection. `null` anchors it back to the trigger.

`[kbqPopoverAutoFocus]="false"` leaves the focus and selection where they were. Cancel the default `mousedown` action inside the popover so the first click does not collapse the selection. Handle `Escape` on the document: the focus stays in the text, so neither the trigger nor the panel receives the key.

The popover opens once the selection is final: after `pointerup` for pointer selection and after a short pause for keyboard selection. Otherwise, it anchors to the first characters of a selection that is still growing. The popover lives in an overlay at the end of the document, so `Tab` walks past it; when needed, handle `Tab` separately and move focus to the first popover element.

### Opening and closing

By default the popover opens on a click and on `Enter`/`Space`, and closes on a click outside it, on `Esc`, or through the close button. Other triggers are selected with `kbqTrigger`: `hover`, `focus`, `manual`, or a comma-separated combination of them.

`kbqEnterDelay` and `kbqLeaveDelay` set the delays before opening and closing, in milliseconds. Both default to `0`, with one exception: a popover whose trigger includes `hover` closes after 500 ms unless `kbqLeaveDelay` is bound explicitly, which keeps the panel reachable while the pointer travels towards it. Bind `kbqLeaveDelay="0"` to opt out.

`kbqTrigger`, `kbqEnterDelay` and `kbqLeaveDelay` are shared with the [tooltip](https://koobiq.io/en/components/tooltip.md) directive, so binding them on an element that carries both reconfigures both.

### Accessibility

The panel is exposed as a dialog and is labelled by its header. A popover without a header has nothing to take its name from, so give it one with `kbqPopoverAriaLabel`: until the panel has a name it is not announced as a dialog at all, because an unnamed dialog tells the user nothing.

Opening the popover by click, by keyboard or programmatically moves focus into the panel and keeps it there while it is open; every closing path returns focus to the trigger. A popover opened by hover or focus does not take focus, because it is dismissed by the pointer leaving it.

### Recommendations

When a short text without interactive elements needs to be shown in a popup, use a [tooltip](https://koobiq.io/en/components/tooltip.md).

A [Dropdown](https://koobiq.io/en/components/dropdown.md) is suitable for showing a list of commands. Dropdown does not support Tab navigation inside the dropdown menu. A popover, on the other hand, works like a modal window: when opened, it captures focus and navigates through the window's content.

## API

### Components

#### KbqPopoverComponent (component)

```ts
@Component({ selector: 'kbq-popover-component' })
class KbqPopoverComponent extends KbqPopUp {
    prefix: string;
    header: string | TemplateRef<any>;
    footer: string | TemplateRef<any>;
    trigger: KbqPopoverTrigger;
    isTrapFocus: boolean;
    hasCloseButton: boolean;
    panelId: string;
    ariaLabel: string | undefined;
    get elementRef(): ElementRef<HTMLElement>;
    readonly cdkTrapFocus: Signal<CdkTrapFocus>;
    get headerId(): string;
    readonly destroyRef: DestroyRef;
    readonly hovered: Signal<boolean>;
    content: string | TemplateRef<unknown>;
    context: { $implicit: unknown } | null;
    classMap: {};
    arrow: boolean;
    defaultPaddings: boolean;
    offset: number | null;
    visibility: PopUpVisibility;
    visibleChange: EventEmitter<boolean>;
    updateClassMap(
        placement: string,
        customClass: string,
        size: KbqPopUpSizeValues
    ): void;
    updateTrapFocus(isTrapFocus: boolean): void;
    onEscape(event?: Event): void;
    isTemplateRef(value: any): boolean;
    show(delay: number): void;
    hide(delay: number): void;
    isVisible(): boolean;
    afterHidden(): Observable<void>;
    detectChanges(): void;
}
```

- `panelId`: `string` — Id of the panel element, referenced by the trigger's `aria-controls`. Written by the trigger.
- `ariaLabel`: `string | undefined` — Accessible name of the panel, used when there is no header to label it. Written by the trigger.
- `elementRef`: `ElementRef<HTMLElement>` — Panel element (`.kbq-popover`), not the component host.

  Deliberately shadows the injected `KbqPopUp.elementRef`: the base measures and decorates the visible
  panel, so both `applyPopupMargins` here and `setStickPosition`/`addEventListenerForHide` in the base
  must resolve to the same element. The confirm template carries the matching `#popover` reference.
- `headerId`: `string` — Id of the header text node, referenced by the panel's `aria-labelledby`.
- `hovered`: `Signal<boolean>` — Whether the pointer is over the pop-up.
- `classMap`: `{}` — CSS classes of the pop-up element: the placement, the custom class and the modifiers of the subclass.
- `visibility`: `PopUpVisibility` — Stage of the pop-up's life: not shown yet, visible, or hidden.
- `onEscape()`: `void` — Closes the panel on `Escape` inside it, unless a control there spent the key closing a popup of its own.
- `hide()`: `void` — Hides the popup after a specified delay.

  The hide timeout triggers the hiding of the popup by updating visibility and emitting relevant events.
  - `delay`: `number` — The delay in milliseconds before hiding the popup.
- `afterHidden()`: `Observable<void>` — Returns an observable that notifies when the tooltip has been hidden from view.

#### KbqPopoverConfirmComponent (component)

```ts
@Component({ selector: 'kbq-popover-confirm-component' })
class KbqPopoverConfirmComponent extends KbqPopoverComponent {
    confirmButtonText: string;
    confirmText: string;
    prefix: string;
    header: string | TemplateRef<any>;
    footer: string | TemplateRef<any>;
    trigger: KbqPopoverTrigger;
    isTrapFocus: boolean;
    hasCloseButton: boolean;
    panelId: string;
    ariaLabel: string | undefined;
    get elementRef(): ElementRef<HTMLElement>;
    readonly cdkTrapFocus: Signal<CdkTrapFocus>;
    get headerId(): string;
    readonly destroyRef: DestroyRef;
    readonly hovered: Signal<boolean>;
    content: string | TemplateRef<unknown>;
    context: { $implicit: unknown } | null;
    classMap: {};
    arrow: boolean;
    defaultPaddings: boolean;
    offset: number | null;
    visibility: PopUpVisibility;
    visibleChange: EventEmitter<boolean>;
    updateClassMap(
        placement: string,
        customClass: string,
        size: KbqPopUpSizeValues
    ): void;
    updateTrapFocus(isTrapFocus: boolean): void;
    onEscape(event?: Event): void;
    isTemplateRef(value: any): boolean;
    show(delay: number): void;
    hide(delay: number): void;
    isVisible(): boolean;
    afterHidden(): Observable<void>;
    detectChanges(): void;
}
```

- `confirmButtonText`: `string` — Caption of the confirm button. Written by the trigger.
- `confirmText`: `string` — Question rendered in the panel. Written by the trigger.
- `panelId`: `string` — Id of the panel element, referenced by the trigger's `aria-controls`. Written by the trigger.
- `ariaLabel`: `string | undefined` — Accessible name of the panel, used when there is no header to label it. Written by the trigger.
- `elementRef`: `ElementRef<HTMLElement>` — Panel element (`.kbq-popover`), not the component host.

  Deliberately shadows the injected `KbqPopUp.elementRef`: the base measures and decorates the visible
  panel, so both `applyPopupMargins` here and `setStickPosition`/`addEventListenerForHide` in the base
  must resolve to the same element. The confirm template carries the matching `#popover` reference.
- `headerId`: `string` — Id of the header text node, referenced by the panel's `aria-labelledby`.
- `hovered`: `Signal<boolean>` — Whether the pointer is over the pop-up.
- `classMap`: `{}` — CSS classes of the pop-up element: the placement, the custom class and the modifiers of the subclass.
- `visibility`: `PopUpVisibility` — Stage of the pop-up's life: not shown yet, visible, or hidden.
- `onEscape()`: `void` — Closes the panel on `Escape` inside it, unless a control there spent the key closing a popup of its own.
- `hide()`: `void` — Hides the popup after a specified delay.

  The hide timeout triggers the hiding of the popup by updating visibility and emitting relevant events.
  - `delay`: `number` — The delay in milliseconds before hiding the popup.
- `afterHidden()`: `Observable<void>` — Returns an observable that notifies when the tooltip has been hidden from view.

### Directives

#### KbqPopoverConfirmTrigger (directive)

```ts
@Directive({ selector: '[kbqPopoverConfirm]', exportAs: 'kbqPopoverConfirm' })
class KbqPopoverConfirmTrigger extends KbqPopoverTrigger {
    readonly popoverHideIfNotInViewPort = input<boolean>(true, {
        alias: 'kbqPopoverHideIfNotInViewPort'
    });
    readonly confirm = output<void>();
    confirmText: string;
    confirmButtonText: string;
    readonly panelId: string;
    preventClose: boolean;
    defaultPaddings: boolean;
    set popoverDefaultPaddings(value: boolean);
    popoverVisible: boolean;
    popoverPlacement: KbqPopUpPlacementValues;
    popoverPlacementPriority: string | string[] | null;
    stickToWindow: KbqStickToWindowPlacementValues | undefined;
    container: HTMLElement | undefined;
    set popoverContainer(value: HTMLElement | undefined);
    hasBackdrop: boolean;
    set popoverHasBackdrop(value: boolean);
    header: string | TemplateRef<any>;
    content: string | TemplateRef<any>;
    footer: string | TemplateRef<any>;
    disabled: boolean;
    trigger: string;
    size: KbqPopUpSizeValues;
    customClass: string;
    context: unknown;
    ariaLabel: string | undefined;
    hasCloseButton: boolean;
    set popoverHasCloseButton(value: boolean);
    closeOnScroll: boolean | null;
    set popoverCloseOnScroll(value: boolean);
    get hasClickTrigger(): boolean;
    get capturesFocusOnOpen(): boolean;
    backdropClass: string;
    set popoverBackdropClass(value: string);
    arrow: boolean;
    offset: number | null;
    autoFocus: boolean;
    popoverOrigin: KbqOverlayOrigin | null;
    enterDelay: number;
    set kbqLeaveDelay(value: number);
    readonly placementChange: EventEmitter<KbqPopUpPlacementValues>;
    readonly visibleChange: EventEmitter<boolean>;
    isOpen: boolean;
    get isAttached(): boolean;
    get openedChange(): Observable<boolean>;
    updatePosition(reapplyPosition?: boolean): void;
    restoreFocus(): void;
    close(): void;
    updateOrigin(origin: KbqOverlayOrigin | null): void;
}
```

- `[kbqPopoverHideIfNotInViewPort]`: `boolean` — Input (`kbqPopoverHideIfNotInViewPort`) — whether the popover closes once its trigger scrolls out of the
  nearest scrollable ancestor. A bound `null` or `undefined` restores the default, `true`.
- `(confirm)`: `void` — Emits when the user confirms the action.
- `confirmText`: `string` — Input (`kbqPopoverConfirmText`) — question rendered in the panel. Falls back to
  `KBQ_POPOVER_CONFIRM_TEXT` and then to the `popoverConfirm` section of the active locale while
  unset or `undefined`.
- `confirmButtonText`: `string` — Input (`kbqPopoverConfirmButtonText`) — caption of the confirm button. Falls back to
  `KBQ_POPOVER_CONFIRM_BUTTON_TEXT` and then to the `popoverConfirm` section of the active locale
  while unset or `undefined`.
- `panelId`: `string` — Id of the panel this trigger controls, shared with `aria-controls`.
- `preventClose`: `boolean` — prevents closure by any event
- `defaultPaddings`: `boolean` — Whether the header, content and footer keep their default paddings. Bound with `kbqPopoverDefaultPaddings`.
- `popoverDefaultPaddings`: `boolean` — Input (`kbqPopoverDefaultPaddings`) — sets `defaultPaddings`.
- `popoverVisible`: `boolean` — Input (`kbqPopoverVisible`) — opens the popover when set to `true` and closes it when set to `false`.
- `popoverPlacement`: `KbqPopUpPlacementValues` — Input (`kbqPopoverPlacement`) — preferred placement of the panel relative to the trigger.
- `popoverPlacementPriority`: `string | string[] | null` — Input (`kbqPopoverPlacementPriority`) — ordered placements tried before the default strategy.
- `stickToWindow`: `KbqStickToWindowPlacementValues | undefined` — Additionally positions the element relative to the window side (Top, Right, Bottom and Left).
  If container is specified, the positioning will be relative to it.
- `container`: `HTMLElement | undefined` — Container for additional positioning, used with `kbqPopoverStickToWindow`. Bound with `kbqPopoverContainer`.
- `popoverContainer`: `HTMLElement | undefined` — Input (`kbqPopoverContainer`) — sets `container`.
- `hasBackdrop`: `boolean` — Whether a backdrop is rendered behind the panel. With a backdrop the popover closes on a backdrop click
  instead of on any outside pointer event. Bound with `kbqPopoverHasBackdrop`.
- `popoverHasBackdrop`: `boolean` — Input (`kbqPopoverHasBackdrop`) — sets `hasBackdrop`.
- `header`: `string | TemplateRef<any>` — Input (`kbqPopoverHeader`) — header of the panel, as a string or a template.
- `content`: `string | TemplateRef<any>` — Input (`kbqPopoverContent`) — content of the panel, as a string or a template.
- `footer`: `string | TemplateRef<any>` — Input (`kbqPopoverFooter`) — footer of the panel, as a string or a template.
- `disabled`: `boolean` — Input (`kbqPopoverDisabled`) — disables the trigger and closes an open popover.
- `trigger`: `string` — Input (`kbqTrigger`) with the comma-separated trigger events. An empty value resets to click + keydown
  and rebinds listeners. The alias is shared with `kbqTooltip`, so binding it on an element that carries
  both reconfigures both.
- `size`: `KbqPopUpSizeValues` — Input (`kbqPopoverSize`) — preset width of the panel. An unknown value falls back to `medium`.
- `customClass`: `string` — Input (`kbqPopoverClass`) with extra CSS classes applied to the panel.
- `context`: `unknown` — Context for popover templates (kbqPopoverHeader, kbqPopoverContent and kbqPopoverFooter).
- `ariaLabel`: `string | undefined` — Accessible name of the panel. Ignored while a string `kbqPopoverHeader` is set — the header labels the
  dialog then — and recommended for every header-less popover.
- `hasCloseButton`: `boolean` — Renders the icon-only close button in the top corner of the panel. Bound with `kbqPopoverHasCloseButton`.
- `popoverHasCloseButton`: `boolean` — Input (`kbqPopoverHasCloseButton`) — sets `hasCloseButton`.
- `closeOnScroll`: `boolean | null` — Controls the behavior of closing the component on scroll. Bound with `kbqPopoverCloseOnScroll`. Three
  states:
  - `null` (default) — the popover survives a scroll, except when it scrolls out of a
    `.kbq-hide-nested-popup` container, which closes it;
  - `true` — any scroll closes the popover;
  - `false` — no scroll closes the popover, not even a `.kbq-hide-nested-popup` one.

  Use CloseScrollStrategy as alternative
- `popoverCloseOnScroll`: `boolean` — Input (`kbqPopoverCloseOnScroll`) — sets `closeOnScroll`.
- `hasClickTrigger`: `boolean` — Whether the trigger opens the popover on click, which is also what makes it a labelled dialog opener.
- `capturesFocusOnOpen`: `boolean` — Whether opening the popover should move the keyboard focus into the panel. Only deliberate opens
  (click, keyboard, programmatic) do — a hover- or focus-triggered popover must not steal focus.
- `backdropClass`: `string` — CSS class applied to the backdrop, when there is one. Bound with `kbqPopoverBackdropClass`.
- `popoverBackdropClass`: `string` — Input (`kbqPopoverBackdropClass`) — sets `backdropClass`.
- `arrow`: `boolean` — Input (`kbqPopoverArrow`) — renders the arrow pointing at the trigger.
- `offset`: `number | null` — Input (`kbqPopoverOffset`) — distance between the trigger and the panel, in pixels.
- `autoFocus`: `boolean` — Input (`kbqPopoverAutoFocus`) — whether opening the popover moves the keyboard focus into the panel. Turn it
  off for a panel that must not disturb what the reader is doing: taking focus away from a text selection
  collapses it. Focus then never enters the panel, so its content has to be reachable some other way.
- `popoverOrigin`: `KbqOverlayOrigin | null` — Input (`kbqPopoverOrigin`) — what the panel is positioned against: an element, or a rectangle in viewport
  coordinates such as the one `kbqCreateCaretOrigin` keeps on the caret. `null` anchors it to the trigger.

  Bind it before `kbqPopoverVisible`, which opens the panel as it is written.
- `enterDelay`: `number` — Input (`kbqEnterDelay`) — delay before opening, in milliseconds. Defaults to `0`.
- `kbqLeaveDelay`: `number` — Input (`kbqLeaveDelay`) — delay before closing, in milliseconds. Defaults to `0`, and to
  `defaultHoverLeaveDelay` when `kbqTrigger` includes `hover`.
- `placementChange`: `EventEmitter<KbqPopUpPlacementValues>` — Emits the resolved placement whenever the panel is repositioned.

  Declared as `string` to match the abstract member on `KbqPopUpTrigger`: `EventEmitter` is invariant,
  so a narrower emitter cannot override a wider one. Narrowing both is a change to the shared pop-up
  base and belongs to the branch that owns it.
- `visibleChange`: `EventEmitter<boolean>` — Emits `true` when the panel opens and `false` when it closes.
- `isOpen`: `boolean` — Whether the pop-up overlay is currently open.
- `isAttached`: `boolean` — Whether the pop-up overlay is currently attached.

  Unlike `isOpen`, which follows the visibility of the pop-up component and therefore lags a task behind
  `show()`/`hide()`, this flips synchronously — it is the window during which the overlay (and its
  backdrop) is present in the DOM. Part of the `KbqSiblingPopup` contract.
- `openedChange`: `Observable<boolean>` — Emits `true` when the pop-up opens and `false` when it closes. Part of the `KbqSiblingPopup` contract.
- `updatePosition()`: `void` — Re-applies the overlay position.

  Deliberately narrower than the inherited one, which also rebuilds the closing-action subscription:
  that stream carries a `delay(0)`, so a reposition coalesced into the same task as an outside click —
  which is the task in which that click's change detection writes a popover input — would drop the
  close the click had already scheduled.
- `restoreFocus()`: `void` — Returns focus to the trigger when the popover is closed while focus is still inside its panel.

  Goes through `FocusMonitor` rather than a native `focus()` call: the kbq focus ring is only rendered
  for a monitored origin, and a `program` origin keeps a tooltip on the same element from re-opening.
- `close()`: `void` — Closes the popover on a deliberate action — the panel's close button, or `Escape` inside the panel.

  `hide()` keeps a hover popover open while the pointer rests on the panel, so that the pointer can
  travel from the trigger to the content; that is exactly where the pointer is when the close button is
  clicked, so a deliberate close answers to `kbqPopoverPreventClose` and to nothing else.
- `updateOrigin()`: `void` — Positions the pop-up against `origin` — an element, or a rectangle in viewport coordinates such as the one
  `kbqCreateCaretOrigin` keeps on the caret — instead of the host element; `null` restores the host
  element. An open pop-up moves to the new origin at once.

  This moves the position origin only. `setExternalNativeElement` moves the host itself, and with it
  the trigger's event listeners, its focus target and the scroll containers the pop-up follows.

#### KbqPopoverTrigger (directive)

```ts
@Directive({ selector: '[kbqPopover]', exportAs: 'kbqPopover' })
class KbqPopoverTrigger extends KbqPopUpTrigger<KbqPopoverComponent> {
    readonly popoverHideIfNotInViewPort = input<boolean>(true, {
        alias: 'kbqPopoverHideIfNotInViewPort'
    });
    readonly panelId: string;
    preventClose: boolean;
    defaultPaddings: boolean;
    set popoverDefaultPaddings(value: boolean);
    popoverVisible: boolean;
    popoverPlacement: KbqPopUpPlacementValues;
    popoverPlacementPriority: string | string[] | null;
    stickToWindow: KbqStickToWindowPlacementValues | undefined;
    container: HTMLElement | undefined;
    set popoverContainer(value: HTMLElement | undefined);
    hasBackdrop: boolean;
    set popoverHasBackdrop(value: boolean);
    header: string | TemplateRef<any>;
    content: string | TemplateRef<any>;
    footer: string | TemplateRef<any>;
    disabled: boolean;
    trigger: string;
    size: KbqPopUpSizeValues;
    customClass: string;
    context: unknown;
    ariaLabel: string | undefined;
    hasCloseButton: boolean;
    set popoverHasCloseButton(value: boolean);
    closeOnScroll: boolean | null;
    set popoverCloseOnScroll(value: boolean);
    get hasClickTrigger(): boolean;
    get capturesFocusOnOpen(): boolean;
    backdropClass: string;
    set popoverBackdropClass(value: string);
    arrow: boolean;
    offset: number | null;
    autoFocus: boolean;
    popoverOrigin: KbqOverlayOrigin | null;
    enterDelay: number;
    set kbqLeaveDelay(value: number);
    readonly placementChange: EventEmitter<KbqPopUpPlacementValues>;
    readonly visibleChange: EventEmitter<boolean>;
    isOpen: boolean;
    get isAttached(): boolean;
    get openedChange(): Observable<boolean>;
    updatePosition(reapplyPosition?: boolean): void;
    restoreFocus(): void;
    close(): void;
    updateOrigin(origin: KbqOverlayOrigin | null): void;
}
```

- `[kbqPopoverHideIfNotInViewPort]`: `boolean` — Input (`kbqPopoverHideIfNotInViewPort`) — whether the popover closes once its trigger scrolls out of the
  nearest scrollable ancestor. A bound `null` or `undefined` restores the default, `true`.
- `panelId`: `string` — Id of the panel this trigger controls, shared with `aria-controls`.
- `preventClose`: `boolean` — prevents closure by any event
- `defaultPaddings`: `boolean` — Whether the header, content and footer keep their default paddings. Bound with `kbqPopoverDefaultPaddings`.
- `popoverDefaultPaddings`: `boolean` — Input (`kbqPopoverDefaultPaddings`) — sets `defaultPaddings`.
- `popoverVisible`: `boolean` — Input (`kbqPopoverVisible`) — opens the popover when set to `true` and closes it when set to `false`.
- `popoverPlacement`: `KbqPopUpPlacementValues` — Input (`kbqPopoverPlacement`) — preferred placement of the panel relative to the trigger.
- `popoverPlacementPriority`: `string | string[] | null` — Input (`kbqPopoverPlacementPriority`) — ordered placements tried before the default strategy.
- `stickToWindow`: `KbqStickToWindowPlacementValues | undefined` — Additionally positions the element relative to the window side (Top, Right, Bottom and Left).
  If container is specified, the positioning will be relative to it.
- `container`: `HTMLElement | undefined` — Container for additional positioning, used with `kbqPopoverStickToWindow`. Bound with `kbqPopoverContainer`.
- `popoverContainer`: `HTMLElement | undefined` — Input (`kbqPopoverContainer`) — sets `container`.
- `hasBackdrop`: `boolean` — Whether a backdrop is rendered behind the panel. With a backdrop the popover closes on a backdrop click
  instead of on any outside pointer event. Bound with `kbqPopoverHasBackdrop`.
- `popoverHasBackdrop`: `boolean` — Input (`kbqPopoverHasBackdrop`) — sets `hasBackdrop`.
- `header`: `string | TemplateRef<any>` — Input (`kbqPopoverHeader`) — header of the panel, as a string or a template.
- `content`: `string | TemplateRef<any>` — Input (`kbqPopoverContent`) — content of the panel, as a string or a template.
- `footer`: `string | TemplateRef<any>` — Input (`kbqPopoverFooter`) — footer of the panel, as a string or a template.
- `disabled`: `boolean` — Input (`kbqPopoverDisabled`) — disables the trigger and closes an open popover.
- `trigger`: `string` — Input (`kbqTrigger`) with the comma-separated trigger events. An empty value resets to click + keydown
  and rebinds listeners. The alias is shared with `kbqTooltip`, so binding it on an element that carries
  both reconfigures both.
- `size`: `KbqPopUpSizeValues` — Input (`kbqPopoverSize`) — preset width of the panel. An unknown value falls back to `medium`.
- `customClass`: `string` — Input (`kbqPopoverClass`) with extra CSS classes applied to the panel.
- `context`: `unknown` — Context for popover templates (kbqPopoverHeader, kbqPopoverContent and kbqPopoverFooter).
- `ariaLabel`: `string | undefined` — Accessible name of the panel. Ignored while a string `kbqPopoverHeader` is set — the header labels the
  dialog then — and recommended for every header-less popover.
- `hasCloseButton`: `boolean` — Renders the icon-only close button in the top corner of the panel. Bound with `kbqPopoverHasCloseButton`.
- `popoverHasCloseButton`: `boolean` — Input (`kbqPopoverHasCloseButton`) — sets `hasCloseButton`.
- `closeOnScroll`: `boolean | null` — Controls the behavior of closing the component on scroll. Bound with `kbqPopoverCloseOnScroll`. Three
  states:
  - `null` (default) — the popover survives a scroll, except when it scrolls out of a
    `.kbq-hide-nested-popup` container, which closes it;
  - `true` — any scroll closes the popover;
  - `false` — no scroll closes the popover, not even a `.kbq-hide-nested-popup` one.

  Use CloseScrollStrategy as alternative
- `popoverCloseOnScroll`: `boolean` — Input (`kbqPopoverCloseOnScroll`) — sets `closeOnScroll`.
- `hasClickTrigger`: `boolean` — Whether the trigger opens the popover on click, which is also what makes it a labelled dialog opener.
- `capturesFocusOnOpen`: `boolean` — Whether opening the popover should move the keyboard focus into the panel. Only deliberate opens
  (click, keyboard, programmatic) do — a hover- or focus-triggered popover must not steal focus.
- `backdropClass`: `string` — CSS class applied to the backdrop, when there is one. Bound with `kbqPopoverBackdropClass`.
- `popoverBackdropClass`: `string` — Input (`kbqPopoverBackdropClass`) — sets `backdropClass`.
- `arrow`: `boolean` — Input (`kbqPopoverArrow`) — renders the arrow pointing at the trigger.
- `offset`: `number | null` — Input (`kbqPopoverOffset`) — distance between the trigger and the panel, in pixels.
- `autoFocus`: `boolean` — Input (`kbqPopoverAutoFocus`) — whether opening the popover moves the keyboard focus into the panel. Turn it
  off for a panel that must not disturb what the reader is doing: taking focus away from a text selection
  collapses it. Focus then never enters the panel, so its content has to be reachable some other way.
- `popoverOrigin`: `KbqOverlayOrigin | null` — Input (`kbqPopoverOrigin`) — what the panel is positioned against: an element, or a rectangle in viewport
  coordinates such as the one `kbqCreateCaretOrigin` keeps on the caret. `null` anchors it to the trigger.

  Bind it before `kbqPopoverVisible`, which opens the panel as it is written.
- `enterDelay`: `number` — Input (`kbqEnterDelay`) — delay before opening, in milliseconds. Defaults to `0`.
- `kbqLeaveDelay`: `number` — Input (`kbqLeaveDelay`) — delay before closing, in milliseconds. Defaults to `0`, and to
  `defaultHoverLeaveDelay` when `kbqTrigger` includes `hover`.
- `placementChange`: `EventEmitter<KbqPopUpPlacementValues>` — Emits the resolved placement whenever the panel is repositioned.

  Declared as `string` to match the abstract member on `KbqPopUpTrigger`: `EventEmitter` is invariant,
  so a narrower emitter cannot override a wider one. Narrowing both is a change to the shared pop-up
  base and belongs to the branch that owns it.
- `visibleChange`: `EventEmitter<boolean>` — Emits `true` when the panel opens and `false` when it closes.
- `isOpen`: `boolean` — Whether the pop-up overlay is currently open.
- `isAttached`: `boolean` — Whether the pop-up overlay is currently attached.

  Unlike `isOpen`, which follows the visibility of the pop-up component and therefore lags a task behind
  `show()`/`hide()`, this flips synchronously — it is the window during which the overlay (and its
  backdrop) is present in the DOM. Part of the `KbqSiblingPopup` contract.
- `openedChange`: `Observable<boolean>` — Emits `true` when the pop-up opens and `false` when it closes. Part of the `KbqSiblingPopup` contract.
- `updatePosition()`: `void` — Re-applies the overlay position.

  Deliberately narrower than the inherited one, which also rebuilds the closing-action subscription:
  that stream carries a `delay(0)`, so a reposition coalesced into the same task as an outside click —
  which is the task in which that click's change detection writes a popover input — would drop the
  close the click had already scheduled.
- `restoreFocus()`: `void` — Returns focus to the trigger when the popover is closed while focus is still inside its panel.

  Goes through `FocusMonitor` rather than a native `focus()` call: the kbq focus ring is only rendered
  for a monitored origin, and a `program` origin keeps a tooltip on the same element from re-opening.
- `close()`: `void` — Closes the popover on a deliberate action — the panel's close button, or `Escape` inside the panel.

  `hide()` keeps a hover popover open while the pointer rests on the panel, so that the pointer can
  travel from the trigger to the content; that is exactly where the pointer is when the close button is
  clicked, so a deliberate close answers to `kbqPopoverPreventClose` and to nothing else.
- `updateOrigin()`: `void` — Positions the pop-up against `origin` — an element, or a rectangle in viewport coordinates such as the one
  `kbqCreateCaretOrigin` keeps on the caret — instead of the host element; `null` restores the host
  element. An open pop-up moves to the new origin at once.

  This moves the position origin only. `setExternalNativeElement` moves the host itself, and with it
  the trigger's event listeners, its focus target and the scroll containers the pop-up follows.

### Constants

#### defaultHoverLeaveDelay (const)

Leave delay applied to a hover-triggered popover when `kbqLeaveDelay` is not bound.

```ts
const defaultHoverLeaveDelay: number;
```

#### defaultOffsetYWithArrow (const)

```ts
const defaultOffsetYWithArrow: number;
```

#### KBQ_POPOVER_CONFIRM_BUTTON_TEXT (const)

Overrides the confirm-button caption of every confirmation popover in the injector scope. Takes precedence
over the locale, but not over the `kbqPopoverConfirmButtonText` input.

```ts
const KBQ_POPOVER_CONFIRM_BUTTON_TEXT: InjectionToken<string>;
```

#### KBQ_POPOVER_CONFIRM_TEXT (const)

Overrides the question of every confirmation popover in the injector scope. Takes precedence over the
locale, but not over the `kbqPopoverConfirmText` input.

```ts
const KBQ_POPOVER_CONFIRM_TEXT: InjectionToken<string>;
```

#### KBQ_POPOVER_SCROLL_STRATEGY (const)

```ts
const KBQ_POPOVER_SCROLL_STRATEGY: InjectionToken<() => ScrollStrategy>;
```

## Examples

### Opening on hover

**Example: Popover hover** (`popover-hover`)

```ts popover-hover-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqPopoverModule } from '@koobiq/components/popover';

@Component({
    selector: 'popover-hover-example',
    imports: [KbqPopoverModule, KbqButtonModule, KbqInputModule],
    template: `
        <ng-template #customContent>
            <div class="layout-margin-bottom-s">Интересно, что это такое?</div>
            <kbq-form-field>
                <input kbqInput placeholder="Интересная мысль" />
            </kbq-form-field>
        </ng-template>

        <ng-template #customFooter>
            <div class="layout-row layout-wrap" style="gap: 16px">
                <button kbq-button [color]="'contrast'" (click)="kbqPopover.hide(0)">Сохранить</button>
                <button kbq-button (click)="kbqPopover.hide(0)">Отмена</button>
            </div>
        </ng-template>

        <button
            #kbqPopover="kbqPopover"
            kbq-button
            kbqPopover
            [kbqPopoverClass]="'popover-overview-example'"
            [kbqPopoverContent]="customContent"
            [kbqPopoverFooter]="customFooter"
            [kbqTrigger]="'hover'"
        >
            Открыть поповер
        </button>
    `,
    styles: `
        ::ng-deep .kbq-popover.kbq-popover_medium.popover-overview-example {
            width: 400px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PopoverHoverExample {}
```
