# Actions panel

- Entry point: `@koobiq/components/actions-panel`
- NgModule: `KbqActionsPanelModule`
- Page: https://koobiq.io/en/components/actions-panel/overview

## Overview

`KbqActionsPanel` - popup panel with bulk actions on selected objects.

**Example: Actions panel overview** (`actions-panel-overview`)

```ts actions-panel-overview-example.ts
import {
    afterNextRender,
    ChangeDetectionStrategy,
    Component,
    ElementRef,
    inject,
    TemplateRef,
    viewChild
} from '@angular/core';
import { KbqActionsPanel, KbqActionsPanelRef } from '@koobiq/components/actions-panel';
import { KbqButtonModule } from '@koobiq/components/button';

@Component({
    selector: 'actions-panel-overview-example',
    imports: [KbqButtonModule],
    template: `
        <button kbq-button (click)="open()">open</button>

        <ng-template>
            <button color="contrast" kbq-button (click)="action('Action1')">Action 1</button>
            <button color="contrast" kbq-button class="layout-margin-left-xxs" (click)="action('Action2')">
                Action 2
            </button>
            <button color="contrast" kbq-button class="layout-margin-left-xxs" (click)="action('Action3')">
                Action 3
            </button>
        </ng-template>
    `,
    styles: `
        :host {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 64px;
            overflow: hidden;
        }
    `,
    providers: [KbqActionsPanel],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ActionsPanelOverviewExample {
    private readonly actionsPanel = inject(KbqActionsPanel, { self: true });
    private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
    private readonly templateRef = viewChild.required(TemplateRef);
    private actionsPanelRef?: KbqActionsPanelRef | null;

    constructor() {
        afterNextRender(() => this.open());
    }

    protected action(action: string): void {
        this.actionsPanelRef?.close(action);
    }

    protected open(): void {
        if (this.actionsPanelRef) return;

        this.actionsPanelRef = this.actionsPanel.open(this.templateRef(), {
            overlayContainer: this.elementRef
        });

        this.actionsPanelRef.beforeOpened.subscribe(() => {
            console.log('before actions-panel opened');
        });

        this.actionsPanelRef.afterOpened.subscribe(() => {
            console.log('after actions-panel opened');
        });

        this.actionsPanelRef.beforeClosed.subscribe((result) => {
            console.log('before actions-panel closed', result);
        });

        this.actionsPanelRef.afterClosed.subscribe((result) => {
            console.log('after actions-panel closed', result);
            this.actionsPanelRef = null;
        });
    }
}
```

### Properties

#### Adaptability

The bulk actions panel becomes compact to fit within the available screen area. Adaptability is implemented using the [`KbqOverflowItems`](https://koobiq.io/en/components/overflow-items.md) component, which automatically hides elements with dynamic adaptation to the container width. The `additionalResizeObserverTargets` attribute is used to track panel size changes, allowing you to monitor size changes of specified elements on the page.

**Example: Actions panel adaptive** (`actions-panel-adaptive`)

```ts actions-panel-adaptive-example.ts
import {
    afterNextRender,
    ChangeDetectionStrategy,
    Component,
    ElementRef,
    inject,
    TemplateRef,
    viewChild
} from '@angular/core';
import { KbqActionsPanel, KbqActionsPanelRef } from '@koobiq/components/actions-panel';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqDividerModule } from '@koobiq/components/divider';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqOverflowItemsModule } from '@koobiq/components/overflow-items';
import { KbqToastService } from '@koobiq/components/toast';

type ExampleAction = {
    id: string;
    icon: string;
};

@Component({
    selector: 'example-actions-panel',
    imports: [
        KbqOverflowItemsModule,
        KbqButtonModule,
        KbqIconModule,
        KbqDropdownModule,
        KbqDividerModule
    ],
    template: `
        <button kbq-button (click)="open()">open</button>

        <ng-template let-data>
            <div
                #kbqOverflowItems="kbqOverflowItems"
                kbqOverflowItems
                [additionalResizeObserverTargets]="additionalResizeObserverTargets"
            >
                <div order="99" [kbqOverflowItem]="action.Counter">
                    <div class="example-counter">Selected: {{ data.length }}</div>
                    <kbq-divider class="example-divider-vertical" [vertical]="true" />
                </div>

                @for (action of actions; track action.id) {
                    <button
                        color="contrast"
                        kbq-button
                        [kbqOverflowItem]="action.id"
                        [class.layout-margin-left-xxs]="!$first"
                        (click)="onAction(action)"
                    >
                        <i kbq-icon [class]="action.icon"></i>
                        {{ action.id }}
                    </button>
                }

                @let hiddenItemIDs = kbqOverflowItems.hiddenItemIDs();
                <!-- ignores when only action.Counter is hidden -->
                @if (hiddenItemIDs.size > 1) {
                    <button
                        kbqOverflowItemsResult
                        color="contrast"
                        kbq-button
                        aria-label="More"
                        [kbqDropdownTriggerFor]="dropdown"
                    >
                        <i kbq-icon="kbq-ellipsis-vertical_16"></i>
                    </button>
                }

                <kbq-dropdown #dropdown="kbqDropdown">
                    <div class="example-counter-dropdown">Selected: {{ data.length }}</div>
                    <kbq-divider />

                    @for (action of actions; track action.id) {
                        @if (hiddenItemIDs.has(action.id)) {
                            <button kbq-dropdown-item (click)="onAction(action)">
                                <i [kbq-icon]="action.icon"></i>
                                {{ action.id }}
                            </button>
                        }
                    }
                </kbq-dropdown>
            </div>
        </ng-template>
    `,
    styles: `
        :host {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 64px;
        }

        .kbq-overflow-item {
            display: flex;
            align-items: center;
        }

        .example-counter {
            margin: 0 var(--kbq-size-m);
            width: 75px;
        }

        .example-counter,
        .example-counter-dropdown {
            user-select: none;
            white-space: nowrap;
        }

        .example-counter-dropdown {
            font-weight: var(--kbq-typography-text-normal-strong-font-weight);
            margin: var(--kbq-size-s) var(--kbq-size-m);
        }

        .example-divider-vertical {
            background-color: var(--kbq-actions-panel-vertical-divider-background-color);
            height: var(--kbq-actions-panel-vertical-divider-height) !important;
            margin: var(--kbq-actions-panel-vertical-divider-margin);
        }
    `,
    providers: [KbqActionsPanel],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExampleActionsPanel {
    private readonly actionsPanel = inject(KbqActionsPanel, { self: true });
    private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
    private readonly templateRef = viewChild.required(TemplateRef);
    private actionsPanelRef: KbqActionsPanelRef | null;
    private readonly toast = inject(KbqToastService);

    protected readonly actions: ExampleAction[] = [
        { id: 'Responsible', icon: 'kbq-user_16' },
        { id: 'Status', icon: 'kbq-arrow-right-s_16' },
        { id: 'Archive', icon: 'kbq-box-archive-arrow-down_16' }
    ];
    protected readonly action = { Counter: 'counter' };
    protected readonly additionalResizeObserverTargets = this.elementRef.nativeElement;

    constructor() {
        afterNextRender(() => this.open());
    }

    protected open(): void {
        if (this.actionsPanelRef) return;

        this.actionsPanelRef = this.actionsPanel.open(this.templateRef(), {
            data: { length: 5 },
            overlayContainer: this.elementRef
        });

        this.actionsPanelRef.afterOpened.subscribe(() => {
            console.log('ActionsPanel opened');
        });

        this.actionsPanelRef.afterClosed.subscribe((result) => {
            console.log('ActionsPanel closed by action:', result);
            this.actionsPanelRef = null;
        });
    }

    protected onAction(action: ExampleAction): void {
        this.toast.show({ title: `Action initiated ${action.id}` });
    }
}

@Component({
    selector: 'actions-panel-adaptive-example',
    imports: [ExampleActionsPanel],
    template: `
        <div>First, the number of records is hidden</div>
        <example-actions-panel [style.width.px]="377" />

        <div>Then, the actions are hidden under the dropdown menu</div>
        <example-actions-panel [style.width.px]="308" />

        <div>Everything is hidden under the dropdown menu</div>
        <example-actions-panel [style.width.px]="89" />
    `,
    styles: `
        div {
            color: var(--kbq-foreground-contrast-secondary);
            margin: var(--kbq-size-s) var(--kbq-size-s) 0;
        }

        example-actions-panel {
            min-width: 89px;
            max-width: 100%;
            overflow: hidden;
            resize: horizontal;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ActionsPanelAdaptiveExample {}
```

#### Additional counter

The component has an option to show an additional counter. The principle of operation of the additional counter can be customized to meet the needs of your product. For example, if the list items are grouped, the counter text can show the total number of items, not just groups.

**Example: Actions panel custom counter** (`actions-panel-custom-counter`)

```ts actions-panel-custom-counter-example.ts
import {
    afterNextRender,
    ChangeDetectionStrategy,
    Component,
    ElementRef,
    inject,
    TemplateRef,
    viewChild
} from '@angular/core';
import { KbqActionsPanel, KbqActionsPanelRef } from '@koobiq/components/actions-panel';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqDividerModule } from '@koobiq/components/divider';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqOverflowItemsModule } from '@koobiq/components/overflow-items';
import { KbqToastService } from '@koobiq/components/toast';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

type ExampleAction = {
    id: string;
    icon: string;
    divider?: boolean;
};

@Component({
    selector: 'actions-panel-custom-counter-example',
    imports: [
        KbqOverflowItemsModule,
        KbqButtonModule,
        KbqIconModule,
        KbqDropdownModule,
        KbqDividerModule,
        KbqToolTipModule
    ],
    template: `
        <button kbq-button (click)="open()">open</button>

        <ng-template let-data>
            <div #kbqOverflowItems="kbqOverflowItems" kbqOverflowItems>
                <div order="99" [kbqOverflowItem]="action.Counter">
                    <div class="example-counter">
                        <div class="kbq-text-normal-medium">Selected: {{ data.selected }}</div>
                        <span
                            aria-label="Total number of items in selected groups: +{{ data.counter }}"
                            class="example-counter-extra kbq-text-normal"
                            kbqTooltip="Total number of items in selected groups"
                            tabindex="0"
                            [kbqTooltipOffset]="22"
                        >
                            +{{ data.counter }}
                        </span>
                    </div>
                    <kbq-divider class="example-divider-vertical" [vertical]="true" />
                </div>

                @for (action of actions; track action.id) {
                    <div [kbqOverflowItem]="action.id">
                        @if (action.divider) {
                            <kbq-divider class="example-divider-vertical" [vertical]="true" />
                        }
                        <button
                            color="contrast"
                            kbq-button
                            [class.layout-margin-left-xxs]="!$first"
                            (click)="onAction(action)"
                        >
                            <i [kbq-icon]="action.icon"></i>
                            {{ action.id }}
                        </button>
                    </div>
                }
            </div>

            @let hiddenItemIDs = kbqOverflowItems.hiddenItemIDs();
            <!-- ignores when only action.Counter is hidden -->
            @if (hiddenItemIDs.size > 1) {
                <button
                    kbqOverflowItemsResult
                    color="contrast"
                    kbq-button
                    aria-label="More"
                    [kbqDropdownTriggerFor]="dropdown"
                >
                    <i kbq-icon="kbq-ellipsis-vertical_16"></i>
                </button>
            }

            <kbq-dropdown #dropdown="kbqDropdown">
                <div class="example-counter-dropdown">
                    <div>Selected: {{ data.selected }}</div>
                    <span
                        aria-label="Total number of items in selected groups: +{{ data.counter }}"
                        class="example-counter-extra kbq-text-normal"
                        kbqTooltip="Total number of items in selected groups"
                        tabindex="0"
                        [kbqTooltipOffset]="22"
                    >
                        +{{ data.counter }}
                    </span>
                </div>
                <kbq-divider />

                @for (action of actions; track action.id) {
                    @if (hiddenItemIDs.has(action.id)) {
                        @if (action.divider && hiddenItemIDs.has(actions[$index - 1].id)) {
                            <kbq-divider />
                        }
                        <button kbq-dropdown-item (click)="onAction(action)">
                            <i [kbq-icon]="action.icon"></i>
                            {{ action.id }}
                        </button>
                    }
                }
            </kbq-dropdown>
        </ng-template>
    `,
    styles: `
        :host {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 64px;
            overflow: hidden;
        }

        .example-counter {
            margin: 0 var(--kbq-size-m);
        }

        .example-counter-extra {
            margin-left: var(--kbq-size-xxs);
            outline: none;
        }

        .example-counter-extra:focus-visible {
            outline: 2px solid var(--kbq-states-line-focus-theme);
        }

        .example-counter > .example-counter-extra {
            color: var(--kbq-foreground-on-contrast-secondary);
        }

        .example-counter,
        .example-counter-dropdown {
            display: flex;
            align-items: center;
            user-select: none;
            white-space: nowrap;
        }

        .example-counter-dropdown {
            font-weight: var(--kbq-typography-text-normal-strong-font-weight);
            margin: var(--kbq-size-s) var(--kbq-size-m);
        }

        .kbq-overflow-item {
            display: flex;
            align-items: center;
        }

        .example-divider-vertical {
            background-color: var(--kbq-actions-panel-vertical-divider-background-color);
            height: var(--kbq-actions-panel-vertical-divider-height) !important;
            margin: var(--kbq-actions-panel-vertical-divider-margin);
        }
    `,
    providers: [KbqActionsPanel],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ActionsPanelCustomCounterExample {
    protected readonly actions: ExampleAction[] = [
        { id: 'Responsible', icon: 'kbq-user_16' },
        { id: 'Link to incident', icon: 'kbq-link_16' },
        { id: 'Remove', icon: 'kbq-trash_16', divider: true }
    ];
    protected readonly action = { Counter: 'counter' };

    private readonly actionsPanel = inject(KbqActionsPanel, { self: true });
    private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
    private readonly templateRef = viewChild.required(TemplateRef);
    private readonly toast = inject(KbqToastService);
    private actionsPanelRef: KbqActionsPanelRef | null;

    constructor() {
        afterNextRender(() => this.open());
    }

    protected open(): void {
        if (this.actionsPanelRef) return;

        this.actionsPanelRef = this.actionsPanel.open(this.templateRef(), {
            data: { selected: 3, counter: 6 },
            overlayContainer: this.elementRef
        });

        this.actionsPanelRef.afterOpened.subscribe(() => {
            console.log('ActionsPanel opened');
        });

        this.actionsPanelRef.afterClosed.subscribe((result) => {
            console.log('ActionsPanel closed by action:', result);
            this.actionsPanelRef = null;
        });
    }

    protected onAction(action: ExampleAction): void {
        this.toast.show({ title: `Action initiated ${action.id}` });
    }
}
```

### Close the panel when an action is started

The panel usually remains open after the action is started. Sometimes there are commands that leave no selected items in the table, such as “Remove”. In such cases it is possible to close the panel.

**Example: Actions panel close** (`actions-panel-close`)

```ts actions-panel-close-example.ts
import {
    afterNextRender,
    ChangeDetectionStrategy,
    Component,
    ElementRef,
    inject,
    TemplateRef,
    viewChild
} from '@angular/core';
import { KbqActionsPanel, KbqActionsPanelRef } from '@koobiq/components/actions-panel';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqToastService } from '@koobiq/components/toast';

@Component({
    selector: 'actions-panel-close-example',
    imports: [KbqButtonModule, KbqIconModule],
    template: `
        <button kbq-button (click)="open()">open</button>

        <ng-template let-data>
            <button color="contrast" kbq-button (click)="onAction('Execute and close')">
                <i kbq-icon="kbq-user_16"></i>
                Execute and close
            </button>
        </ng-template>
    `,
    styles: `
        :host {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 64px;
            overflow: hidden;
        }
    `,
    providers: [KbqActionsPanel],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ActionsPanelCloseExample {
    private readonly actionsPanel = inject(KbqActionsPanel, { self: true });
    private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
    private readonly templateRef = viewChild.required(TemplateRef);
    private readonly toast = inject(KbqToastService);
    private actionsPanelRef: KbqActionsPanelRef | null;

    constructor() {
        afterNextRender(() => this.open());
    }

    protected open(): void {
        if (this.actionsPanelRef) return;

        this.actionsPanelRef = this.actionsPanel.open(this.templateRef(), {
            data: { length: 3 },
            overlayContainer: this.elementRef
        });

        this.actionsPanelRef.afterOpened.subscribe(() => {
            console.log('ActionsPanel opened');
        });

        this.actionsPanelRef.afterClosed.subscribe((result) => {
            console.log('ActionsPanel closed by action:', result);
            this.actionsPanelRef = null;
        });
    }

    protected onAction(action: string): void {
        this.toast.show({ title: `Action initiated ${action}` });
        this.actionsPanelRef?.close();
    }
}
```

### Keyboard interaction

By default, the `ESCAPE` key closes `KbqActionsPanel`. While you can disable this behavior via the `disableClose` property of `KbqActionsPanelConfig`. A key that a control inside the panel has already handled, for example by closing its tooltip, leaves the panel open.

### Sharing data with the component

You can use the `data` option to pass information to the component:

```ts
import { Component, inject } from '@angular/core';
import { KbqActionsPanel } from '@koobiq/components/actions-panel';

@Component({
    selector: 'your-component',
    templateUrl: './your-component.html',
    providers: [KbqActionsPanel]
})
export class YourComponent {
    private readonly actionsPanel = inject(KbqActionsPanel, { self: true });

    openActionsPanel() {
        this.actionsPanel.open(YourActionsPanelComponent, {
            data: { name: 'koobiq' }
        });
    }
}
```

Access to data in the component is performed using `KBQ_ACTIONS_PANEL_DATA` injection token:

```ts
import { Component, inject } from '@angular/core';
import { KBQ_ACTIONS_PANEL_DATA, KbqActionsPanelRef } from '@koobiq/components/actions-panel';

@Component({
    selector: 'your-actions-panel',
    template: `
        <div>{{ data.name }}</div>
        <button (click)="actionsPanelRef.close()">close</button>
    `
})
export class YourActionsPanelComponent {
    readonly data = inject(KBQ_ACTIONS_PANEL_DATA);
    readonly actionsPanelRef = inject(KbqActionsPanelRef);
}
```

If you are using a `TemplateRef` for your actions panel content, the `data` and `actionsPanelRef` is available in the template:

```html
<ng-template let-data let-actionsPanelRef="actionsPanelRef">
    <div>{{ data.name }}</div>
    <button (click)="actionsPanelRef.close()">close</button>
</ng-template>
```

### Managing OverlayContainer

`OverlayContainer` determines where in the DOM tree the actions panel will be displayed. By default it is the document
body (`document.body`).

#### Default container

Without `overlayContainer` the panel is rendered in the application-wide overlay container and pinned to the bottom of
the viewport, above the page, the way a dialog is. Use it when the actions apply to the whole screen rather than to a
single block on it.

**Example: Actions panel opened globally** (`actions-panel-global`)

```ts actions-panel-global-example.ts
import { ChangeDetectionStrategy, Component, inject, TemplateRef, viewChild } from '@angular/core';
import { KbqActionsPanel, KbqActionsPanelRef } from '@koobiq/components/actions-panel';
import { KbqButtonModule } from '@koobiq/components/button';

@Component({
    selector: 'actions-panel-global-example',
    imports: [KbqButtonModule],
    template: `
        <button kbq-button (click)="open()">open</button>

        <ng-template>
            <button color="contrast" kbq-button (click)="action('Action1')">Action 1</button>
            <button color="contrast" kbq-button class="layout-margin-left-xxs" (click)="action('Action2')">
                Action 2
            </button>
            <button color="contrast" kbq-button class="layout-margin-left-xxs" (click)="action('Action3')">
                Action 3
            </button>
        </ng-template>
    `,
    styles: `
        :host {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 64px;
        }
    `,
    providers: [KbqActionsPanel],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ActionsPanelGlobalExample {
    private readonly actionsPanel = inject(KbqActionsPanel, { self: true });
    private readonly templateRef = viewChild.required(TemplateRef);
    private actionsPanelRef?: KbqActionsPanelRef | null;

    protected action(action: string): void {
        this.actionsPanelRef?.close(action);
    }

    protected open(): void {
        if (this.actionsPanelRef) return;

        // No `overlayContainer`: the panel goes into the application-wide overlay container and is pinned to the
        // bottom of the viewport, above the page, the way a dialog is.
        this.actionsPanelRef = this.actionsPanel.open(this.templateRef());

        this.actionsPanelRef.afterClosed.subscribe(() => (this.actionsPanelRef = null));
    }
}
```

#### Configuring container for a specific panel

If you need to display the actions panel in a specific element (for example, inside a [Sidebar](https://koobiq.io/en/components/sidebar.md)), use the `overlayContainer` option:

```ts
import { Component, ElementRef, inject } from '@angular/core';
import { KbqActionsPanel } from '@koobiq/components/actions-panel';

@Component({
    selector: 'your-component',
    templateUrl: './your-component.html',
    providers: [KbqActionsPanel]
})
export class YourComponent {
    private readonly actionsPanel = inject(KbqActionsPanel, { self: true });
    private readonly customContainer = inject<ElementRef<HTMLElement>>(ElementRef);

    openActionsPanel() {
        this.actionsPanel.open(YourActionsPanelComponent, {
            overlayContainer: this.customContainer
        });
    }
}
```

The panel is rendered inside that element, pinned to its bottom center, and resizes with it. Its width is capped by the
element's width unless `maxWidth` says otherwise. An element that sets `overflow: hidden` clips the panel; at the
default `overflow: visible` the entrance animation renders it below the element.

The element is mutated for as long as the panel is open: it gains one child node holding the overlay, and an element
with `position: static` is promoted to `position: relative`, since the overlay is positioned against it.

#### Global container configuration

`OverlayContainer` can be replaced application-wide, which moves every overlay elsewhere in the DOM:

```ts
import { FullscreenOverlayContainer, OverlayContainer } from '@angular/cdk/overlay';
import { bootstrapApplication } from '@angular/platform-browser';

bootstrapApplication(AppComponent, {
    providers: [{ provide: OverlayContainer, useClass: FullscreenOverlayContainer }]
});
```

The `overlayContainer` option takes precedence: a panel opened with it does not use the global container.

## API

### Services

#### KbqActionsPanel (service)

Service for opening actions panel.

```ts
@Injectable({ providedIn: 'root' })
class KbqActionsPanel {
    open<T, D = unknown, R = unknown>(
        component: ComponentType<T>,
        config?: KbqActionsPanelConfig<D>
    ): KbqActionsPanelRef<T, R>;
    open<T, D = unknown, R = unknown>(
        template: TemplateRef<T>,
        config?: KbqActionsPanelConfig<D>
    ): KbqActionsPanelRef<T, R>;
    close<R>(result?: R): void;
}
```

- `open()`: `KbqActionsPanelRef<T, R>` — Opens actions panel.
  - `config`: `KbqActionsPanelConfig<D>` — Additional configuration options for the actions panel.
  - returns `KbqActionsPanelRef<T, R>` — A reference to the opened actions panel.
- `close()`: `void` — Closes the currently opened actions panel.

### Classes

#### KbqActionsPanelConfig (class)

Configuration for opened actions panel.
Based on cdk `DialogConfig`.

```ts
class KbqActionsPanelConfig<D = unknown> {
    injector?: Injector;
    data?: D | null;
    containerClass?: string | string[];
    closeOnNavigation?: boolean;
    overlayContainer?: ElementRef<HTMLElement>;
    overlayPanelClass?: string | string[];
    width?: string;
    minWidth?: number | string;
    maxWidth?: number | string;
    direction?: Direction;
    scrollStrategy?: (overlay: Overlay) => ScrollStrategy;
    disableClose?: boolean;
}
```

- `injector?`: `Injector` — Injector used for the instantiation of the component to be attached.
- `data?`: `D | null` — Data being injected into the child component with `KBQ_ACTIONS_PANEL_DATA`.
- `containerClass?`: `string | string[]` — Extra CSS classes to be added to the actions panel container.
- `closeOnNavigation?`: `boolean` — Whether the actions panel should be closed of when the user goes backwards/forwards in history.
  Defaults to `true`.
  NOTE! This does not apply to router navigation.
- `overlayContainer?`: `ElementRef<HTMLElement>` — Element the actions panel overlay is rendered into, instead of the application-wide overlay container. The panel
  is pinned to the bottom center of that element and resizes with it. An element that sets `overflow: hidden`
  clips it; at the default `overflow: visible` the entrance animation renders it below the element.

  The element is mutated for as long as the panel is open: it gains one child node holding the overlay, and a
  `static` element is promoted to `position: relative`, since the overlay is positioned against it.

  NOTE: the panel bypasses a globally provided `OverlayContainer` while this is set.
- `overlayPanelClass?`: `string | string[]` — Extra CSS classes to be added to the overlay panel.
- `width?`: `string` — Width of the actions panel.
- `minWidth?`: `number | string` — Min-width of the actions panel.
  If a number is provided, assumes pixel units.
- `maxWidth?`: `number | string` — Max-width of the actions panel.
  If a number is provided, assumes pixel units.

  When omitted, the panel is capped by the width of the overlay container it is rendered into.
- `direction?`: `Direction` — Direction of the text in the actions panel. Defaults to `'ltr'`.
- `scrollStrategy?`: `(overlay: Overlay) => ScrollStrategy` — Scroll strategy to be used for the actions panel. Defaults to `RepositionScrollStrategy`.
- `disableClose?`: `boolean` — Whether the user can use ESC or click on close button to close the actions panel. Defaults to `false`.

#### KbqActionsPanelRef (class)

Reference to actions panel opened by `KbqActionsPanel` service.

```ts
class KbqActionsPanelRef<I = unknown, R = unknown> {
    readonly keydownEvents: Observable<KeyboardEvent>;
    readonly beforeOpened: Observable<void>;
    readonly afterOpened: Observable<void>;
    readonly beforeClosed: Observable<R | undefined>;
    readonly afterClosed: Observable<R | undefined>;
    constructor(
        dialogRef: DialogRef<R, I>,
        containerInstance: KbqActionsPanelContainer
    );
    close(result?: R): void;
}
```

- `keydownEvents`: `Observable<KeyboardEvent>` — Emits when keydown events are targeted on the overlay.
- `beforeOpened`: `Observable<void>` — Emits when the actions panel starts opening, before its entrance animation plays.
- `afterOpened`: `Observable<void>` — Emits when the actions panel has opened and appeared, or once closing cuts its entrance short.
- `beforeClosed`: `Observable<R | undefined>` — Emits when the actions panel starts closing, before its exit animation plays.
- `afterClosed`: `Observable<R | undefined>` — Emits when the actions panel is finished closing.
- `close()`: `void` — Closes the actions panel.

### Types

#### KbqActionsPanelTemplateContext (type)

Context for actions panel template.

```ts
type KbqActionsPanelTemplateContext<T = unknown, D = unknown, R = unknown> = {
    $implicit?: D | null;
    data?: D | null;
    actionsPanelRef: KbqActionsPanelRef<T, R>;
};
```

- `$implicit?`: `D | null` — Data passed to actions panel.

  Example:

  ```html
  <ng-template #actionsPanel let-data>{{ data.KEY }}</ng-template>
  ````
- `data?`: `D | null` — Data passed to actions panel.

  Example:

  ```html
  <ng-template #actionsPanel let-data="data">{{ data.KEY }}</ng-template>
  ````
- `actionsPanelRef`: `KbqActionsPanelRef<T, R>` — Opened actions panel reference.

  Example:

  ```html
  <ng-template #actionsPanel let-actionsPanelRef="actionsPanelRef">
    <button (click)="actionsPanelRef.close()">close</button>
  </ng-template>
  ````

### Constants

#### KBQ_ACTIONS_PANEL_DATA (const)

Injection token that can be used to access the data that was passed in to actions panel.

```ts
const KBQ_ACTIONS_PANEL_DATA: InjectionToken<unknown>;
```

#### KBQ_ACTIONS_PANEL_DEFAULT_CONFIG (const)

Injection token that can be used to specify default actions panel config.

```ts
const KBQ_ACTIONS_PANEL_DEFAULT_CONFIG: InjectionToken<KbqActionsPanelConfig>;
```

#### KBQ_ACTIONS_PANEL_LOCALE_CONFIGURATION (const)

Localization configuration provider.

```ts
const KBQ_ACTIONS_PANEL_LOCALE_CONFIGURATION: InjectionToken<KbqActionsPanelLocaleConfiguration>;
```

#### kbqActionsPanelDefaultConfigProvider (const)

Utility for providing default actions panel config through `KBQ_ACTIONS_PANEL_DEFAULT_CONFIG`.

```ts
const kbqActionsPanelDefaultConfigProvider: <D>(
    config: KbqActionsPanelConfig<D>
) => Provider;
```

#### kbqActionsPanelLocaleConfigurationProvider (const)

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

```ts
const kbqActionsPanelLocaleConfigurationProvider: (
    configuration: KbqDeepPartial<KbqActionsPanelLocaleConfiguration>
) => Provider;
```

## Examples

### Actions panel with dropdown and popover

**Example: Actions panel with dropdown and popover** (`actions-panel-with-dropdown-and-popover`)

```ts actions-panel-with-dropdown-and-popover-example.ts
import {
    afterNextRender,
    ChangeDetectionStrategy,
    Component,
    ElementRef,
    inject,
    TemplateRef,
    viewChild
} from '@angular/core';
import { KbqActionsPanel, KbqActionsPanelRef } from '@koobiq/components/actions-panel';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqPopoverModule, KbqPopoverTrigger } from '@koobiq/components/popover';

@Component({
    selector: 'actions-panel-with-dropdown-and-popover-example',
    imports: [KbqButtonModule, KbqIconModule, KbqDropdownModule, KbqPopoverModule],
    template: `
        <button kbq-button (click)="open()">open</button>

        <ng-template let-data>
            <button
                #popover="kbqPopover"
                color="contrast"
                kbq-button
                kbqPopover
                kbqTrigger="none"
                [kbqDropdownTriggerFor]="dropdown"
                [kbqPopoverFooter]="popoverFooter"
            >
                <i kbq-icon="kbq-shield_16"></i>
                Protection
                <i kbq-icon="kbq-chevron-down-s_16"></i>
            </button>

            <kbq-dropdown #dropdown="kbqDropdown">
                <button kbq-dropdown-item (click)="enable(popover)">
                    <i kbq-icon="kbq-shield_16"></i>
                    Enable
                </button>
                <button kbq-dropdown-item (click)="disable(popover)">
                    <i kbq-icon="kbq-shield-slash_16"></i>
                    Disable
                </button>
            </kbq-dropdown>
        </ng-template>

        <ng-template #popoverFooter>
            <div class="example-popover-footer">
                <button kbq-button color="contrast" (click)="save()">Save</button>
                <button kbq-button (click)="cancel()">Cancel</button>
            </div>
        </ng-template>
    `,
    styles: `
        :host {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 64px;
            overflow: hidden;
        }

        .example-popover-footer {
            display: flex;
            gap: var(--kbq-size-s);
        }
    `,
    providers: [KbqActionsPanel],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ActionsPanelWithDropdownAndPopoverExample {
    private readonly actionsPanel = inject(KbqActionsPanel, { self: true });
    private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
    private readonly templateRef = viewChild.required(TemplateRef);
    private actionsPanelRef: KbqActionsPanelRef | null;
    private popoverTriggerRef: KbqPopoverTrigger | null;

    constructor() {
        afterNextRender(() => this.open());
    }

    protected open(): void {
        if (this.actionsPanelRef) return;

        this.actionsPanelRef = this.actionsPanel.open(this.templateRef(), {
            overlayContainer: this.elementRef
        });

        this.actionsPanelRef.afterClosed.subscribe(() => (this.actionsPanelRef = null));
    }

    protected disable(popoverTriggerRef: KbqPopoverTrigger): void {
        this.popoverTriggerRef = popoverTriggerRef;
        this.popoverTriggerRef.header = 'Disable protection?';
        this.popoverTriggerRef.show(0);
    }

    protected enable(popoverTriggerRef: KbqPopoverTrigger): void {
        this.popoverTriggerRef = popoverTriggerRef;
        this.popoverTriggerRef.header = 'Enable protection?';
        this.popoverTriggerRef.show(0);
    }

    protected cancel(): void {
        this.popoverTriggerRef?.hide(0);
        this.popoverTriggerRef = null;
    }

    protected save(): void {
        this.popoverTriggerRef?.hide(0);
        this.popoverTriggerRef = null;
    }
}
```
