# Tooltip

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

## Overview

Tooltip — a hint that appears on hover or focus. The tooltip closes when the cursor is moved away, focus is removed, the page is scrolled, or `Esc` is pressed.

**Example: Tooltip** (`tooltip-overview`)

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

@Component({
    selector: 'tooltip-overview-example',
    imports: [
        KbqButtonModule,
        KbqToolTipModule,
        KbqIconModule
    ],
    template: `
        <button kbq-button aria-label="Add" kbqTooltip="Create">
            <i kbq-icon="kbq-plus_16"></i>
        </button>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-row layout-align-center-center'
    }
})
export class TooltipOverviewExample {}
```

### Width

The tooltip size is determined by its content, but the component width cannot exceed 300px.

**Example: Tooltip width** (`tooltip-width`)

```ts tooltip-width-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { PopUpPlacements } from '@koobiq/components/core';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-width-example',
    imports: [
        KbqButtonModule,
        KbqToolTipModule
    ],
    template: `
        <button kbq-button kbqTooltip="Tooltip">Tooltip</button>
        <button
            kbq-button
            kbqTooltip="A tooltip is a hint that appears when the pointer hovers over or focuses on an interface element. A tooltip cannot contain interactive elements."
        >
            Max Width
        </button>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-row layout-align-center-center layout-gap-l'
    }
})
export class TooltipWidthExample {
    placement: PopUpPlacements = PopUpPlacements.Top;
}
```

Sometimes it's more convenient if the text in the tooltip doesn't wrap and is displayed on a single line. For example, when comparing checksums, outputting email addresses, or file and folder paths. For such cases, don't limit the tooltip width.

**Example: Tooltip wide width** (`tooltip-wide-width`)

```ts tooltip-wide-width-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-wide-width-example',
    imports: [KbqToolTipModule],
    template: `
        <span
            kbqTooltip="9f1c2e4a8b7d3f6c5e2a1d9b0c4e7f8a6d3c2b1a9e8f7d6c5b4a3e2d1c0f9a8"
            [kbqTooltipClass]="'example__wide-tooltip'"
        >
            9f1c...f9a8
        </span>
    `,
    styles: `
        ::ng-deep .example__wide-tooltip.kbq-tooltip {
            --kbq-tooltip-size-max-width: 454px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-row layout-align-center-center layout-gap-l'
    }
})
export class TooltipWideWidthExample {}
```

#### Style

**Example: Tooltip style** (`tooltip-style`)

```ts tooltip-style-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqComponentColors } from '@koobiq/components/core';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-style-example',
    imports: [
        KbqButtonModule,
        KbqToolTipModule
    ],
    template: `
        <button kbq-button kbqTooltip="Tooltip" [kbqTooltipColor]="KbqComponentColors.Contrast">
            Contrast (default)
        </button>

        <button kbq-button kbqTooltip="Tooltip" [kbqTooltipColor]="KbqComponentColors.ContrastFade">
            Contrast Fade
        </button>

        <button kbq-button kbqTooltip="Tooltip" [kbqTooltipColor]="KbqComponentColors.Error">Error</button>

        <button kbq-button kbqTooltip="Tooltip" [kbqTooltipColor]="KbqComponentColors.Warning">Warning</button>

        <button kbq-button kbqTooltip="Tooltip" [kbqTooltipColor]="KbqComponentColors.Theme">Theme</button>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-row layout-align-center-center layout-gap-l'
    }
})
export class TooltipStyleExample {
    KbqComponentColors = KbqComponentColors;
}
```

#### Arrow

Add a pointer arrow when it can be confusing which element the hint relates to.

**Example: Tooltip arrow** (`tooltip-arrow`)

```ts tooltip-arrow-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-arrow-example',
    imports: [
        KbqButtonModule,
        KbqToolTipModule
    ],
    template: `
        <button kbq-button kbqTooltip="Tooltip without arrow">No arrow</button>
        <button kbq-button kbqTooltip="Tooltip with arrow" [kbqTooltipArrow]="true">With arrow</button>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-row layout-align-center-center layout-gap-l'
    }
})
export class TooltipArrowExample {}
```

### Positioning

#### Relative to Element

In normal circumstances, the tooltip opens at the top centered. The position near the trigger element can be adjusted manually.

Choose a position where the tooltip won't cover content that the user will interact with next. For example, in a vertical list, open tooltips to the side so the hint doesn't interfere with viewing the neighboring element in the reading direction.

**Example: Tooltip placements** (`tooltip-placements`)

```ts tooltip-placements-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-placements-example',
    imports: [KbqToolTipModule, KbqButtonModule],
    template: `
        <div class="layout-row layout-gap-l layout-align-center-center">
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'topLeft'" [kbqTooltipArrow]="true">
                topLeft
            </button>
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'top'" [kbqTooltipArrow]="true">
                top
            </button>
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'topRight'" [kbqTooltipArrow]="true">
                topRight
            </button>
        </div>

        <br />

        <div class="layout-row layout-gap-l layout-align-space-between">
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'leftTop'" [kbqTooltipArrow]="true">
                leftTop
            </button>
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'rightTop'" [kbqTooltipArrow]="true">
                rightTop
            </button>
        </div>

        <br />

        <div class="layout-row layout-gap-l layout-align-space-between">
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'left'" [kbqTooltipArrow]="true">
                left
            </button>
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'right'" [kbqTooltipArrow]="true">
                right
            </button>
        </div>

        <br />

        <div class="layout-row layout-gap-l layout-align-space-between">
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'leftBottom'" [kbqTooltipArrow]="true">
                leftBottom
            </button>
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'rightBottom'" [kbqTooltipArrow]="true">
                rightBottom
            </button>
        </div>

        <br />

        <div class="layout-row layout-gap-l layout-align-center-center">
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'bottomLeft'" [kbqTooltipArrow]="true">
                bottomLeft
            </button>
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'bottom'" [kbqTooltipArrow]="true">
                bottom
            </button>
            <button kbqTooltip="{{ tooltipText }}" kbq-button [kbqPlacement]="'bottomRight'" [kbqTooltipArrow]="true">
                bottomRight
            </button>
        </div>
    `,
    styles: `
        :host {
            display: block;

            max-width: 734px;
            margin: 0 auto;

            .kbq-button {
                width: 134px;
            }
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TooltipPlacementsExample {
    tooltipText =
        'A tooltip is a hint that appears when the pointer hovers over or focuses on an interface element. A tooltip cannot contain interactive elements.';
}
```

#### Position Priority

If there isn't enough space to display the tooltip, it will open on the opposite side. The kbqPlacementPriority attribute helps configure fallback positions if they need to differ from the default order.

#### Near the Cursor

When configuring kbqRelativeToPointer="true", the tooltip is positioned relative to the cursor. This is often needed for long strings (checksums, email, paths) so the hint appears right at the cursor.

**Example: Tooltip relative to pointer** (`tooltip-relative-to-pointer`)

```ts tooltip-relative-to-pointer-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-relative-to-pointer-example',
    imports: [
        KbqButtonModule,
        KbqToolTipModule
    ],
    template: `
        <button kbq-button kbqTooltip="relativeToPointer" [kbqRelativeToPointer]="true">
            Button with a tooltip positioned relative to the cursor
        </button>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-row layout-align-center-center'
    }
})
export class TooltipRelativeToPointerExample {}
```

#### Near the Caret

The tooltip can open next to the text caret in an editable field. Its position updates as the user types, moves the caret or scrolls the field. When text is selected, the tooltip anchors to the selection.

**Example: Tooltip relative to caret** (`tooltip-relative-to-caret`)

```ts tooltip-relative-to-caret-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqFormFieldModule } from '@koobiq/components/form-field';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqTextareaModule } from '@koobiq/components/textarea';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-relative-to-caret-example',
    imports: [
        KbqFormFieldModule,
        KbqInputModule,
        KbqTextareaModule,
        KbqToolTipModule
    ],
    template: `
        <kbq-form-field>
            <input
                #inputTooltip="kbqTooltip"
                kbqInput
                placeholder="Type here"
                [kbqPlacement]="'top'"
                [kbqRelativeToCaret]="true"
                [kbqTooltip]="'Follows the caret'"
                [kbqTrigger]="'manual'"
                (input)="inputTooltip.show()"
                (blur)="inputTooltip.hide()"
            />
        </kbq-form-field>

        <kbq-form-field>
            <textarea
                #textareaTooltip="kbqTooltip"
                kbqTextarea
                placeholder="Type here"
                [kbqPlacement]="'top'"
                [kbqRelativeToCaret]="true"
                [kbqTooltip]="'Follows the caret'"
                [kbqTrigger]="'manual'"
                (input)="textareaTooltip.show()"
                (blur)="textareaTooltip.hide()"
            ></textarea>
        </kbq-form-field>
    `,
    styles: `
        kbq-form-field {
            width: 320px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-column layout-align-center-center layout-gap-l'
    }
})
export class TooltipRelativeToCaretExample {}
```

Set `kbqRelativeToCaret="true"` to enable this mode. Use `kbqRelativeToCaretVertical` to control the tooltip's vertical position: `field` places it above or below the whole field, while `line` places it next to the line containing the caret. By default, `auto` uses `field` for a single-line `input` and `line` for a `textarea` and `contenteditable`. Horizontally, the tooltip follows the caret in both modes. All tooltip placements remain available.

If the browser cannot determine the caret's position, the tooltip anchors to the field. This may happen with `password`, `email` and `number` inputs, right-to-left text, or centered and right-aligned text. When the tooltip is attached to a container, it uses the first editable element inside it.

### Offset

**Example: Tooltip offset** (`tooltip-offset`)

```ts tooltip-offset-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-offset-example',
    imports: [
        KbqButtonModule,
        KbqToolTipModule
    ],
    template: `
        <button kbq-button kbqTooltip="Tooltip">Default</button>
        <button kbq-button kbqTooltip="Tooltip" [kbqTooltipArrow]="true">Default with arrow</button>
        <button kbq-button kbqTooltip="Tooltip" [kbqTooltipOffset]="0">0px</button>
        <button kbq-button kbqTooltip="Tooltip" [kbqTooltipOffset]="32">32px</button>
        <button kbq-button kbqTooltip="Tooltip" [kbqTooltipOffset]="-8">-8px</button>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-row layout-align-center-center layout-gap-l'
    }
})
export class TooltipOffsetExample {}
```

### Delay

When hovering over an element, the tooltip appears with a 0.4 second delay and disappears instantly.

When switching focus or hovering over another element with a tooltip, the delay works as follows:

- The first tooltip appears with standard delay,
- Subsequent tooltips appear instantly,
- If the previous element's tooltip has disappeared and more than 2 seconds have passed, the 0.4 second delay applies again.

**Example: tooltip-hide-with-timeout** (`tooltip-hide-with-timeout`)

```ts tooltip-hide-with-timeout-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-hide-with-timeout-example',
    imports: [
        KbqButtonModule,
        KbqToolTipModule,
        KbqIconModule
    ],
    template: `
        @for (item of [1, 2, 3, 4, 5, 6]; track item) {
            <button kbqTooltip="Action{{ item }}" kbq-button aria-label="Diamond">
                <i kbq-icon="kbq-diamond_16"></i>
            </button>
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-row layout-align-center-center layout-gap-m'
    }
})
export class TooltipHideWithTimeoutExample {}
```

### Moving the Cursor onto the Tooltip

The tooltip receives pointer events, so moving the cursor from the trigger onto the tooltip itself does not
close it. This matters for users who read at high magnification, where the pointer often ends up over the
tooltip, and for tooltips that take a while to read (WCAG 1.4.13 "Content on Hover or Focus"). Combine it with
`[hideWithTimeout]="true"` and `kbqLeaveDelay` to give the cursor time to travel to the tooltip — see the
Interactive Tooltip example.

Set `[ignoreTooltipPointerEvents]="true"` for the opposite behavior: the tooltip becomes transparent to the
pointer and clicks pass through to whatever is underneath it. Use it for tooltips that float over other click
targets — for example the overflow hints of a scrolling option list, where a pointer-capturing tooltip would
swallow the click that selects the neighboring option.

Before v20 the tooltip ignored pointer events by default, so this reverses on upgrade: markup that relied on
clicks passing through now has to ask for it. The built-in overflow hints (`kbq-title`, `kbqEllipsisCenter`,
the option and timezone-option hints) already opt out on their own, and so does any tooltip whose `kbqTrigger`
is `manual` or `none` — hovering such a pane neither keeps it open nor closes it, so there is nothing for a
pointer-capturing pane to protect.

### Only One Tooltip at a Time

At most one tooltip is on screen: showing a new one closes the previously opened tooltip. The rule applies to tooltips opened by `hover`, `focus`, `click` or `keydown` — including the `kbq-title` and `kbqEllipsisCenter` hints.

Tooltips driven programmatically (`kbqTrigger="manual"`) stay out of the rule: they neither close other tooltips nor get closed. Validation hints work this way.

To opt a single tooltip out, use `[kbqTooltipSingleInstance]="false"`; to change the default for the whole application, provide the `KBQ_TOOLTIP_SINGLE_INSTANCE_DEFAULT` token:

```ts
providers: [{ provide: KBQ_TOOLTIP_SINGLE_INSTANCE_DEFAULT, useValue: false }];
```

### Usage Examples

#### Tooltip for Disabled Button

If a certain button or function is unavailable, the tooltip can explain why.

**Example: Tooltip disabled** (`tooltip-disabled`)

```ts tooltip-disabled-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-disabled-example',
    imports: [KbqToolTipModule, KbqButtonModule, KbqIconModule],
    template: `
        <div kbqTooltip="This item is locked and cannot be deleted" [forDisabledComponent]="button">
            <button #button kbq-button [disabled]="true">
                <i kbq-icon="kbq-trash_16"></i>
                Delete
            </button>
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-row layout-align-center-center'
    }
})
export class TooltipDisabledExample {}
```

### Custom Hints

For special cases, you can change the tooltip dimensions and place more than just text inside it. Use the Contrast-fade style, which is similar to the application background, to ensure content contrast against the tooltip background.

**Example: Tooltip extended** (`tooltip-extended`)

```ts tooltip-extended-example.ts
import { NgOptimizedImage } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqComponentColors } from '@koobiq/components/core';
import { KbqDivider } from '@koobiq/components/divider';
import { KbqLink } from '@koobiq/components/link';
import { KbqTableModule } from '@koobiq/components/table';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-extended-example',
    imports: [
        KbqButtonModule,
        KbqToolTipModule,
        KbqDivider,
        KbqTableModule,
        KbqLink,
        NgOptimizedImage
    ],
    template: `
        <p class="layout-margin-no-top layout-margin-no-bottom">
            A low Earth orbit (LEO) is an
            <a
                kbqLink
                href="https://en.wikipedia.org/wiki/Geocentric_orbit"
                [kbqTooltip]="tooltipContent"
                [kbqTooltipClass]="'example__custom-tooltip'"
                [kbqTooltipArrow]="true"
                [kbqTooltipColor]="extendedTooltipPreferredColor"
            >
                orbit around
            </a>
            Earth with a period of 128 minutes or less (making at least 11.25 orbits per day) and an eccentricity less
            than 0.25. Most of the artificial objects in outer space are in LEO, peaking in number at an altitude around
            800 km (500 mi), while the farthest in LEO, before medium Earth orbit (MEO), have an altitude of 2,000
            kilometers, about one-third of the radius of Earth and near the beginning of the inner Van Allen radiation
            belt.
        </p>

        <button
            kbq-button
            [kbqTooltip]="tableContent"
            [kbqTooltipClass]="'example__custom-extended-tooltip'"
            [kbqTooltipColor]="extendedTooltipPreferredColor"
        >
            Table Data
        </button>

        <ng-template #tooltipContent>
            <img
                class="example__custom-tooltip-img"
                ngSrc="./assets/images/tooltip/tooltip-example.webp"
                srcset="./assets/images/tooltip/tooltip-example@2x.webp 2x"
                width="296"
                height="222"
                alt="tooltip-example"
            />
            <div>
                A geocentric orbit, also known as an Earth-centered orbit, is the curved trajectory of an object, such
                as the Moon or thousands of artificial satellites and debris, around the planet Earth.
            </div>
        </ng-template>

        <ng-template #tableContent>
            <div
                class="layout-row layout-gap-xs layout-padding-top-m layout-padding-bottom-s layout-padding-left-l layout-padding-right-l"
            >
                <span class="kbq-text-normal-strong">Confidence</span>
                <span class="kbq-mono-normal" style="color: var(--kbq-foreground-warning)">0,0001</span>
            </div>
            <kbq-divider />
            <table kbq-table class="layout-padding-bottom-l">
                <thead>
                    <tr>
                        <th class="layout-padding-left-l">Incident Type</th>
                        <th>Detected</th>
                        <th class="layout-padding-right-l">Updated</th>
                    </tr>
                </thead>
                <tbody>
                    @for (row of data; track $index) {
                        <tr>
                            <td class="layout-padding-left-l">{{ row.type }}</td>
                            <td class="kbq-tabular-normal">{{ row.detected }}</td>
                            <td class="kbq-tabular-normal layout-padding-right-l">{{ row.updated }}</td>
                        </tr>
                    }
                </tbody>
            </table>
        </ng-template>
    `,
    styles: `
        ::ng-deep .example__custom-tooltip.kbq-tooltip {
            --kbq-tooltip-size-max-width: 320px;
            width: 320px;
        }

        ::ng-deep .example__custom-tooltip-img {
            border-radius: var(--kbq-size-xxs);
            background: #000101;
            min-width: 296px;
            min-height: 222px;
        }

        ::ng-deep .example__custom-extended-tooltip.kbq-tooltip {
            --kbq-tooltip-size-max-width: 480px;
            --kbq-tooltip-size-padding-vertical: 0;
            --kbq-tooltip-size-padding-horizontal: 0;
            width: 480px;
        }

        table {
            width: 100%;
        }

        tr > td {
            font-variant-numeric: slashed-zero;

            &:first-child {
                width: 45%;
            }
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-column layout-align-center-start layout-gap-l'
    }
})
export class TooltipExtendedExample {
    protected readonly extendedTooltipPreferredColor = KbqComponentColors.ContrastFade;

    protected readonly data = [
        {
            type: 'Malware Infection',
            detected: 'Feb 18, 15:30',
            updated: 'Feb 18, 15:33'
        },
        {
            type: 'Phishing Attack',
            detected: 'Feb 18, 15:33',
            updated: 'Feb 18, 15:33'
        },
        {
            type: 'Data Breach',
            detected: 'Feb 18, 15:33',
            updated: 'Feb 18, 15:33'
        },
        {
            type: 'DoS/DDoS Attack',
            detected: 'Feb 18, 15:33',
            updated: 'Feb 18, 15:33'
        }
    ];
}
```

### Recommendations

- If you need to use interactive elements (buttons, links, etc.) inside the tooltip, use [Popover](https://koobiq.io/en/components/popover.md) instead.
- The Tooltip component is always preferable to the system one. Don't use both methods simultaneously in the same interface.
- For tooltips to work effectively, it's important to keep them brief and clear. Long explanations can be difficult to read and distracting, so it's best to avoid them.
- The tooltip height depends on the content. The recommended maximum height is 480px (may be increased at the designer's discretion).

## API

### Components

#### KbqTooltipComponent (component)

```ts
@Component({ selector: 'kbq-tooltip-component' })
class KbqTooltipComponent extends KbqPopUp {
    prefix: string;
    get elementRef(): ElementRef<HTMLElement>;
    readonly destroyRef: DestroyRef;
    readonly hovered: Signal<boolean>;
    trigger: KbqPopUpTrigger<unknown>;
    header: string | TemplateRef<unknown>;
    content: string | TemplateRef<unknown>;
    context: { $implicit: unknown } | null;
    classMap: {};
    arrow: boolean;
    defaultPaddings: boolean;
    offset: number | null;
    visibility: PopUpVisibility;
    visibleChange: EventEmitter<boolean>;
    show(delay: number): void;
    updateClassMap(
        placement: string,
        customClass: string,
        { modifier }: { modifier: KbqEnumValues<TooltipModifier> }
    ): void;
    isTemplateRef(value: any): boolean;
    hide(delay: number): void;
    isVisible(): boolean;
    afterHidden(): Observable<void>;
    detectChanges(): void;
}
```

- `elementRef`: `ElementRef<HTMLElement>` — The tooltip panel, which the base pop-up measures and decorates instead of the host.
- `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.
- `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

#### KbqTooltipTrigger (directive)

Trigger directive that shows a `KbqTooltipComponent` for its host element. Extends `KbqPopUpTrigger` with
tooltip-specific inputs (content, placement, color, arrow, enter/leave delays, modifier and header) and
behavior such as opening on keyboard focus only and positioning relative to the mouse cursor.

```ts
@Directive({ selector: '[kbqTooltip]', exportAs: 'kbqTooltip' })
class KbqTooltipTrigger extends KbqPopUpTrigger<KbqTooltipComponent> implements KbqExclusiveTooltip {
    readonly forDisabledComponent = input<Record<'disabledSignal', WritableSignal<boolean>>>();
    readonly ignoreTooltipPointerEvents = input<boolean>(false);
    readonly singleInstance = input<boolean>(
        inject(KBQ_TOOLTIP_SINGLE_INSTANCE_DEFAULT),
        { alias: 'kbqTooltipSingleInstance' }
    );
    hideWithTimeout = input<boolean | undefined>();
    tooltipVisible: boolean;
    tooltipPlacement: KbqPopUpPlacementValues;
    relativeToPointer: boolean;
    relativeToCaret: boolean;
    relativeToCaretVertical: KbqCaretVerticalAnchor;
    tooltipPlacementPriority: string | string[] | null;
    content: string | TemplateRef<unknown>;
    disabled: boolean;
    enterDelay: number;
    leaveDelay: number;
    trigger: string;
    customClass: string;
    context: unknown;
    color: KbqComponentColors | string;
    arrow: boolean;
    offset: number | null;
    readonly placementChange: EventEmitter<KbqPopUpPlacementValues>;
    readonly visibleChange: EventEmitter<boolean>;
    modifier: KbqEnumValues<TooltipModifier>;
    header: string | TemplateRef<unknown>;
    isOpen: boolean;
    get isAttached(): boolean;
    get openedChange(): Observable<boolean>;
    show(delay?: number): void;
    hide(delay?: number): void;
    showForMouseEvent(event: MouseEvent): void;
    showForElement(element: HTMLElement): void;
    updateOrigin(origin: KbqOverlayOrigin | null): void;
}
```

- `[forDisabledComponent]`: `Record<'disabledSignal', WritableSignal<boolean>>` — Input for controlling the disabled state of a component.

  The input expects a component containing `disabledSignal` property, which is
  a writable signal emitting boolean values.
- `[ignoreTooltipPointerEvents]`: `boolean` — Determines whether pointer events should be ignored on tooltips.

  When set to `true`, the tooltip pane does not receive pointer events, so clicks and hovers pass through
  to whatever is underneath it. Defaults to `false`, which keeps the tooltip hoverable: a user who needs
  more time to read it — or who reads at high magnification, where the pointer often ends up over the
  tooltip itself — can move the pointer onto it without it disappearing (WCAG 1.4.13 "Content on Hover").

  Set it to `true` for tooltips that float over other click targets, such as the overflow hints of a
  scrolling option list.
- `[kbqTooltipSingleInstance]`: `boolean` — Whether the tooltip takes part in the "only one tooltip is visible at a time" group: showing it
  hides the previously visible tooltip, and it is hidden when another tooltip is shown.

  Defaults to `true`; the default can be changed application-wide with the
  `KBQ_TOOLTIP_SINGLE_INSTANCE_DEFAULT` token.

  Tooltips without an interactive `kbqTrigger` (`manual`, `none`, …) never take part in the group,
  regardless of this input — they are shown imperatively and are expected to stay pinned.
- `[hideWithTimeout]`: `boolean | undefined` — Changes hiding behavior. By default, tooltip is hidden on mouseleave from trigger.
  Setting hideWithTimeout to true will delay tooltip hiding and will not hide when the mouse moves from trigger
  to tooltip.
- `tooltipVisible`: `boolean` — Input (`kbqVisible`) that programmatically shows or hides the tooltip; reflects the current `visible` state.
- `tooltipPlacement`: `KbqPopUpPlacementValues` — Input (`kbqPlacement`) that sets the tooltip placement relative to its trigger; reflects the current `placement`.
- `relativeToPointer`: `boolean` — Positions the tooltip relative to the mouse cursor. Only available for top and bottom kbqPlacement.
  Does not work with kbqPlacementPriority.
- `relativeToCaret`: `boolean` — Positions the tooltip relative to the text caret of the field it is attached to, following it while the
  user types, moves the caret and scrolls the field. Anchors to the selection whenever there is one.

  Every placement is available, unlike with `kbqRelativeToPointer`: the caret is anchored as a rectangle,
  so the usual fallback placements keep the tooltip on screen. Wins when both inputs are enabled.

  The field is the host element itself when it is editable, otherwise the first editable it wraps.
- `relativeToCaretVertical`: `KbqCaretVerticalAnchor` — What the tooltip is vertically anchored to while `kbqRelativeToCaret` is enabled.
- `tooltipPlacementPriority`: `string | string[] | null` — Input (`kbqPlacementPriority`) that sets the ordered fallback placements; reflects the current `placementPriority`.
- `content`: `string | TemplateRef<unknown>` — Input (`kbqTooltip`) with the tooltip content — a string or a template. Updating it refreshes an open tooltip.
- `disabled`: `boolean` — Input (`kbqTooltipDisabled`) controlling whether the tooltip is disabled; setting it to `true` hides it.
  `undefined` leaves the decision to the trigger, like an unset input.

  Reads back the *effective* state, which for triggers that derive one of their own (see
  `derivedDisabledWins`) is not necessarily the value that was written.
- `enterDelay`: `number` — Input (`kbqEnterDelay`) — delay in milliseconds before the tooltip is shown. Defaults to `400`.
- `leaveDelay`: `number` — Input (`kbqLeaveDelay`) — delay in milliseconds before the tooltip is hidden. Defaults to `0`.
- `trigger`: `string` — Input (`kbqTrigger`) with the comma-separated trigger events. An empty value resets to hover + focus and rebinds listeners.
- `customClass`: `string` — Input (`kbqTooltipClass`) with an extra CSS class applied to the tooltip; updating it refreshes the class map.
- `context`: `unknown` — Input (`kbqTooltipContext`) with the context object passed to a template tooltip; updating it refreshes the open tooltip.
- `color`: `KbqComponentColors | string` — Input (`kbqTooltipColor`) with the tooltip color theme. Defaults to `KbqComponentColors.Contrast`.
- `arrow`: `boolean` — Input (`kbqTooltipArrow`) — whether to render the tooltip's arrow/pointer. Defaults to `false`.
- `offset`: `number | null` — Input (`kbqTooltipOffset`) — distance in pixels between the tooltip and its trigger. `null`, the default, keeps
  the gap of the stylesheet.
- `placementChange`: `EventEmitter<KbqPopUpPlacementValues>` — Output (`kbqPlacementChange`) that emits the new placement whenever the tooltip repositions.
- `visibleChange`: `EventEmitter<boolean>` — Output (`kbqVisibleChange`) that emits when the tooltip's visibility changes.
- `modifier`: `KbqEnumValues<TooltipModifier>` — Visual variant of the tooltip. Accepts `'default'` | `'warning'` | `'extended'`.
  Replaces the removed `KbqWarningTooltipTrigger` and `KbqExtendedTooltipTrigger`
  subclasses — to render a warning tooltip use `kbqTooltipModifier="warning"`,
  and `kbqTooltipModifier="extended"` for the extended variant (combine with `kbqTooltipHeader`).
- `header`: `string | TemplateRef<unknown>` — Header text or template, rendered above the tooltip content. Only meaningful with
  `kbqTooltipModifier="extended"`. Replaces the removed `KbqExtendedTooltipTrigger.header`.
- `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.
- `show()`: `void` — Shows the tooltip after `delay` ms. Suppresses showing while a pop-up on the same element holds the
  anchor or on a focus trigger that did not originate from the keyboard, and applies cursor-relative
  positioning when `relativeToPointer` is enabled.
- `hide()`: `void` — Hides the tooltip after `delay` ms and, when the user has genuinely left the host, un-mutes a tooltip
  that a pop-up on the same element had muted.

  `mouseleave` and `blur` are the release signals because the base class binds `hide` to both regardless
  of whether the tooltip is currently visible. A pop-up that is still attached does not count: its
  backdrop steals the hover from the trigger while the pointer stands still, and that `mouseleave` must
  not be mistaken for the user leaving.
- `showForMouseEvent()`: `void` — Shows the tooltip next to the cursor position of the given mouse event, anchoring it to the event's
  `currentTarget` instead of the directive's own host element.

  Use it when the trigger cannot be declared in the template — for instance for a tooltip shared by the
  cells of a virtualized table. The directive still has to be created in an injection context.
- `showForElement()`: `void` — Shows the tooltip anchored to the given element instead of the directive's own host element.

  Use it when the trigger cannot be declared in the template. The directive still has to be created in an
  injection context:

  ```ts
  const tooltip = runInInjectionContext(injector, () => new KbqTooltipTrigger());
  tooltip.showForElement(element);
  ```

  Does nothing while the tooltip is disabled or has no content — there is no overlay to anchor then.
- `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.

### Services

#### KbqTooltipDelayTracker (service)

Remembers when a tooltip was last shown, so that moving between neighbouring triggers does not replay the
enter delay for every one of them: the first tooltip waits `kbqEnterDelay`, the ones following it within
`KBQ_TOOLTIP_INSTANT_SHOW_WINDOW` appear instantly.

Application-wide by design — the behavior is part of the component's documented delay rules.

```ts
@Injectable({ providedIn: 'root' })
class KbqTooltipDelayTracker {
    shouldSkipEnterDelay(): boolean;
    markShown(): void;
    reset(): void;
}
```

- `shouldSkipEnterDelay()`: `boolean` — Whether the tooltip that is about to be shown should skip its enter delay.
- `markShown()`: `void` — Records that a tooltip has just been shown.
- `reset()`: `void` — Forgets the last shown tooltip, so the next one appears with its full enter delay.

#### KbqTooltipRegistry (service)

Keeps track of the single tooltip that is currently visible, so that showing a new one hides the
previous. Holds the *trigger*, not the overlay component — the latter is re-created on every show.

```ts
@Injectable({ providedIn: 'root' })
class KbqTooltipRegistry {
    setVisible(tooltip: KbqExclusiveTooltip): void;
    clearVisible(tooltip: KbqExclusiveTooltip): void;
}
```

- `setVisible()`: `void` — Marks the tooltip as the only visible one, hiding the previously visible tooltip.
- `clearVisible()`: `void` — Clears the registry, but only when the given tooltip is still the registered one. The guard
  matters because hiding the previous tooltip is asynchronous: its `visibleChange(false)` arrives
  after the new tooltip has already been registered, and must not wipe that newer entry.

### Types

#### KbqCaretVerticalAnchor (type)

What the tooltip is vertically anchored to when it is positioned relative to the caret:

- `'line'` — the line of text the caret is on.
- `'field'` — the whole field, so the tooltip never covers it; horizontally it still follows the caret.
- `'auto'` — `'field'` for a single-line `<input>`, `'line'` for everything else.

```ts
type KbqCaretVerticalAnchor = 'auto' | 'line' | 'field';
```

### Enums

#### TooltipModifier (enum)

```ts
enum TooltipModifier {
    Default = 'default',
    Warning = 'warning',
    Extended = 'extended'
}
```

### Constants

#### KBQ_TOOLTIP_INSTANT_SHOW_WINDOW (const)

Length in milliseconds of the window during which a tooltip shown right after another one appears
instantly. Defaults to `MIN_TIME_FOR_DELAY`.

```ts
const KBQ_TOOLTIP_INSTANT_SHOW_WINDOW: InjectionToken<number>;
```

#### KBQ_TOOLTIP_OPEN_TIME (const)

Deprecated: Replaced by `KbqTooltipDelayTracker`, which owns the shared timestamp behind a service
instead of a mutable `useValue` object. The tooltip no longer reads this token; providing it has no effect. Will be
removed in 22.0.0.

```ts
const KBQ_TOOLTIP_OPEN_TIME: InjectionToken<{ value: number }>;
```

#### KBQ_TOOLTIP_SCROLL_STRATEGY (const)

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

#### KBQ_TOOLTIP_SINGLE_INSTANCE_DEFAULT (const)

Default value of the `kbqTooltipSingleInstance` input. Provide `false` at the application level to
globally disable the "only one tooltip is visible at a time" behavior.

```ts
const KBQ_TOOLTIP_SINGLE_INSTANCE_DEFAULT: InjectionToken<boolean>;
```

#### MIN_TIME_FOR_DELAY (const)

Default length of the window during which a following tooltip is shown without its enter delay.

```ts
const MIN_TIME_FOR_DELAY: number;
```

## Examples

### Interactive Tooltip

In this example, the tooltip hides with a delay and stays open if the user moves the cursor from the trigger to the tooltip in time.

**Example: tooltip-interactive** (`tooltip-interactive`)

```ts tooltip-interactive-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-interactive-example',
    imports: [
        KbqButtonModule,
        KbqToolTipModule,
        KbqIconModule
    ],
    template: `
        <div class="layout-column" style="gap: 16px; align-items: flex-start">
            <button kbq-button kbqTooltip="Тултип" [hideWithTimeout]="true">Кнопка с тултипом</button>
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TooltipInteractiveExample {}
```

### Dynamic Tooltip

In this example, the tooltip is created dynamically on mouseenter.

**Example: tooltip-dynamic-example** (`tooltip-dynamic`)

```ts tooltip-dynamic-example.ts
import { ChangeDetectionStrategy, Component, inject, Injector, runInInjectionContext } from '@angular/core';
import { PopUpPlacements } from '@koobiq/components/core';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqTagsModule } from '@koobiq/components/tags';
import { KbqToolTipModule, KbqTooltipTrigger } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-dynamic-example',
    imports: [
        KbqToolTipModule,
        KbqIconModule,
        KbqTagsModule
    ],
    template: `
        <div
            class="layout-column"
            style="border: 1px solid var(--kbq-line-contrast-fade); padding: 16px; width: 300px; overflow: hidden; overflow-x: auto"
        >
            <kbq-tag style="min-width: 1000px" (mouseenter)="onMouseenter($event)" (mouseleave)="onMouseleave()">
                (users.name ILIKE '%john%' OR users.email ILIKE '%john%') OR (users.name ILIKE '%doe%' OR users.email
                ILIKE '%doe%') OR (users.name ILIKE '%developer%' OR users.bio ILIKE '%developer%')
            </kbq-tag>
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TooltipDynamicExample {
    private readonly injector = inject(Injector);

    private dynamicTooltip: KbqTooltipTrigger;

    protected onMouseenter(event) {
        if (!this.dynamicTooltip) {
            runInInjectionContext(this.injector, () => (this.dynamicTooltip = new KbqTooltipTrigger()));
        }

        this.dynamicTooltip.content = event.currentTarget.innerText;
        this.dynamicTooltip.tooltipPlacement = PopUpPlacements.Top;
        this.dynamicTooltip.showForMouseEvent(event);
    }

    protected onMouseleave() {
        this.dynamicTooltip.hide();
    }
}
```

### Disabled state

**Example: Tooltip disabled** (`tooltip-disabled-for-component`)

```ts tooltip-disabled-for-component-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqButtonModule } from '@koobiq/components/button';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqLink } from '@koobiq/components/link';
import { KbqToolTipModule } from '@koobiq/components/tooltip';

@Component({
    selector: 'tooltip-disabled-for-component-example',
    imports: [KbqToolTipModule, KbqButtonModule, KbqIconModule, KbqLink],
    template: `
        <button kbq-button (click)="disableState = !disableState">toggle</button>

        <div kbqTooltip="kbq-button" [forDisabledComponent]="button">
            <button #button kbq-button aria-label="Add" [disabled]="disableState">
                <i kbq-icon="kbq-plus_16"></i>
            </button>
        </div>

        <div kbqTooltip="kbq-icon-button" [forDisabledComponent]="iconButton">
            <i #iconButton #button kbq-icon-button="kbq-plus_16" color="theme" [disabled]="disableState"></i>
        </div>

        <div kbqTooltip="kbqLink" [forDisabledComponent]="link">
            <a
                #link="kbqLink"
                kbqLink
                kbqTooltip="Create"
                href="https://koobiq.io"
                target="_blank"
                [disabled]="disableState"
            >
                kbq-link
            </a>
        </div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
    host: {
        class: 'layout-margin-5xl layout-row layout-align-center-center layout-gap-l'
    }
})
export class TooltipDisabledForComponentExample {
    disableState: boolean = false;
}
```
