UNPKG

@taiga-ui/core

Version:

Core library for creating Angular components and applications using Taiga UI

1,059 lines 56.8 kB
import * as i0 from '@angular/core';
import { Injectable, Directive, InjectionToken, Optional, Self, SkipSelf, inject, Input, EventEmitter, Output, ChangeDetectorRef, INJECTOR, signal, TemplateRef, computed, Component, ChangeDetectionStrategy, ElementRef, ContentChild, ViewContainerRef } from '@angular/core';
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
import { EMPTY_CLIENT_RECT, TUI_TRUE_HANDLER, CHAR_ZERO_WIDTH_SPACE, CHAR_NO_BREAK_SPACE } from '@taiga-ui/cdk/constants';
import * as i1 from '@taiga-ui/cdk/directives/active-zone';
import { TuiActiveZone } from '@taiga-ui/cdk/directives/active-zone';
import * as i2 from '@taiga-ui/cdk/directives/animated';
import { TuiAnimated } from '@taiga-ui/cdk/directives/animated';
import { tuiInjectElement, tuiGetActualTarget, tuiPointToClientRect, tuiIsElement, tuiIsHTMLElement, tuiIsElementEditable, tuiIsTextNode, tuiIsTextfield } from '@taiga-ui/cdk/utils/dom';
import { tuiClamp } from '@taiga-ui/cdk/utils/math';
import { tuiProvide, tuiPure, tuiPx, tuiProvideOptions, tuiIsString, tuiDirectiveBinding } from '@taiga-ui/cdk/utils/miscellaneous';
import { TuiDriverDirective, TuiPositionAccessor, tuiFallbackAccessor, TuiRectAccessor, tuiAsRectAccessor, tuiAsVehicle, tuiPositionAccessorFor, tuiRectAccessorFor, tuiAsDriver, TuiDriver, tuiAsPositionAccessor } from '@taiga-ui/core/classes';
import { TuiScrollbar } from '@taiga-ui/core/components/scrollbar';
import { TuiVisualViewportService, TuiPositionService } from '@taiga-ui/core/services';
import { TUI_VIEWPORT, TUI_DARK_MODE, TUI_SELECTION_STREAM } from '@taiga-ui/core/tokens';
import { PolymorpheusComponent, PolymorpheusTemplate, PolymorpheusOutlet } from '@taiga-ui/polymorpheus';
import { BehaviorSubject, Subject, throttleTime, takeWhile, map, merge, filter, fromEvent, switchMap, delay, startWith, takeUntil, distinctUntilChanged, of, tap, share, combineLatest } from 'rxjs';
import { coerceArray } from '@angular/cdk/coercion';
import { tuiZonefreeScheduler, tuiTypedFromEvent, tuiZonefree, tuiIfMap, tuiCloseWatcher, tuiZonefull, tuiWatch, tuiZoneOptimized } from '@taiga-ui/cdk/observables';
import { tuiOverrideOptions, tuiCheckFixedPosition, tuiGetWordRange } from '@taiga-ui/core/utils';
import { TuiPortalService, TuiPortals, tuiAsPortal } from '@taiga-ui/cdk/classes';
import { __decorate } from 'tslib';
import { DOCUMENT } from '@angular/common';
import { TUI_IS_TOUCH, TUI_RANGE } from '@taiga-ui/cdk/tokens';
import { shouldCall } from '@taiga-ui/event-plugins';
import * as i1$1 from '@taiga-ui/cdk/directives/obscured';
import { TuiObscured } from '@taiga-ui/cdk/directives/obscured';
import { tuiIsNativeKeyboardFocusable, tuiGetClosestFocusable, tuiIsNativeFocusedIn, tuiGetNativeFocused } from '@taiga-ui/cdk/utils/focus';
import { tuiIsEditingKey, tuiOverrideOptions as tuiOverrideOptions$1 } from '@taiga-ui/core/utils/miscellaneous';

class TuiDropdownDriver extends BehaviorSubject {
    constructor() {
        super(false);
        this.type = 'dropdown';
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownDriver, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownDriver }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownDriver, decorators: [{
            type: Injectable
        }], ctorParameters: function () { return []; } });
class TuiDropdownDriverDirective extends TuiDriverDirective {
    constructor() {
        super(...arguments);
        this.type = 'dropdown';
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownDriverDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownDriverDirective, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownDriverDirective, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                }]
        }] });

/**
 * A component to display a dropdown
 */
const TUI_DROPDOWN_COMPONENT = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_COMPONENT' : '', {
    factory: () => TuiDropdownComponent,
});
const TUI_DROPDOWN_CONTEXT = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_CONTEXT' : '');

class TuiDropdownService extends TuiPortalService {
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }] });

/** Default values for dropdown options */
const TUI_DROPDOWN_DEFAULT_OPTIONS = {
    align: 'left',
    direction: null,
    limitWidth: 'auto',
    maxHeight: 400,
    minHeight: 80,
    offset: 4,
    appearance: '',
};
/**
 * Default parameters for dropdown directive
 */
const TUI_DROPDOWN_OPTIONS = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_OPTIONS' : '', {
    factory: () => TUI_DROPDOWN_DEFAULT_OPTIONS,
});
const tuiDropdownOptionsProvider = (override) => ({
    provide: TUI_DROPDOWN_OPTIONS,
    deps: [
        [new Optional(), new Self(), TuiDropdownOptionsDirective],
        [new Optional(), new SkipSelf(), TUI_DROPDOWN_OPTIONS],
    ],
    useFactory: tuiOverrideOptions(override, TUI_DROPDOWN_DEFAULT_OPTIONS),
});
class TuiDropdownOptionsDirective {
    constructor() {
        this.options = inject(TUI_DROPDOWN_OPTIONS, { skipSelf: true });
        this.align = this.options.align;
        this.appearance = this.options.appearance;
        this.direction = this.options.direction;
        this.limitWidth = this.options.limitWidth;
        this.minHeight = this.options.minHeight;
        this.maxHeight = this.options.maxHeight;
        this.offset = this.options.offset;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownOptionsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownOptionsDirective, isStandalone: true, selector: "[tuiDropdownAlign], [tuiDropdownAppearance], [tuiDropdownDirection], [tuiDropdownLimitWidth], [tuiDropdownMinHeight], [tuiDropdownMaxHeight], [tuiDropdownOffset]", inputs: { align: ["tuiDropdownAlign", "align"], appearance: ["tuiDropdownAppearance", "appearance"], direction: ["tuiDropdownDirection", "direction"], limitWidth: ["tuiDropdownLimitWidth", "limitWidth"], minHeight: ["tuiDropdownMinHeight", "minHeight"], maxHeight: ["tuiDropdownMaxHeight", "maxHeight"], offset: ["tuiDropdownOffset", "offset"] }, providers: [tuiProvide(TUI_DROPDOWN_OPTIONS, TuiDropdownOptionsDirective)], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownOptionsDirective, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[tuiDropdownAlign], [tuiDropdownAppearance], [tuiDropdownDirection], [tuiDropdownLimitWidth], [tuiDropdownMinHeight], [tuiDropdownMaxHeight], [tuiDropdownOffset]',
                    providers: [tuiProvide(TUI_DROPDOWN_OPTIONS, TuiDropdownOptionsDirective)],
                }]
        }], propDecorators: { align: [{
                type: Input,
                args: ['tuiDropdownAlign']
            }], appearance: [{
                type: Input,
                args: ['tuiDropdownAppearance']
            }], direction: [{
                type: Input,
                args: ['tuiDropdownDirection']
            }], limitWidth: [{
                type: Input,
                args: ['tuiDropdownLimitWidth']
            }], minHeight: [{
                type: Input,
                args: ['tuiDropdownMinHeight']
            }], maxHeight: [{
                type: Input,
                args: ['tuiDropdownMaxHeight']
            }], offset: [{
                type: Input,
                args: ['tuiDropdownOffset']
            }] } });

class TuiDropdownPosition extends TuiPositionAccessor {
    constructor() {
        super(...arguments);
        this.el = tuiInjectElement();
        this.options = inject(TUI_DROPDOWN_OPTIONS);
        this.viewport = inject(TUI_VIEWPORT);
        this.directionChange = new EventEmitter();
        this.type = 'dropdown';
        this.accessor = tuiFallbackAccessor('dropdown')(inject(TuiRectAccessor), inject(TuiDropdownDirective, { optional: true }));
    }
    emitDirection(direction) {
        this.directionChange.emit(direction);
    }
    getPosition({ width, height }) {
        if (!width && !height) {
            this.previous = undefined;
        }
        const hostRect = this.accessor?.getClientRect() ?? EMPTY_CLIENT_RECT;
        const viewportRect = this.viewport.getClientRect();
        const { minHeight, direction, offset, limitWidth } = this.options;
        const align = this.getAlign(this.options.align);
        const viewport = {
            top: viewportRect.top - offset,
            bottom: viewportRect.bottom + offset,
            right: viewportRect.right - offset,
            left: viewportRect.left + offset,
        };
        const previous = this.previous || direction || 'bottom';
        const available = {
            top: hostRect.top - 2 * offset - viewport.top,
            bottom: viewport.bottom - hostRect.bottom - 2 * offset,
        };
        const rectWidth = limitWidth === 'fixed' ? hostRect.width : width;
        const right = Math.max(hostRect.right - rectWidth, offset);
        const left = hostRect.left + width < viewport.right ? hostRect.left : right;
        const position = {
            top: hostRect.top - offset - height,
            bottom: hostRect.bottom + offset,
            right: Math.max(viewport.left, right),
            center: hostRect.left + hostRect.width / 2 + width / 2 < viewport.right
                ? hostRect.left + hostRect.width / 2 - width / 2
                : right,
            left: Math.max(viewport.left, left),
        };
        const better = available.top > available.bottom ? 'top' : 'bottom';
        if ((available[previous] > minHeight && direction) ||
            available[previous] > height) {
            this.emitDirection(previous);
            return [position[previous], position[align]];
        }
        this.previous = better;
        this.emitDirection(better);
        return [position[better], position[align]];
    }
    getAlign(align) {
        const rtl = this.el.matches('[dir="rtl"] :scope');
        if (rtl && align === 'left') {
            return 'right';
        }
        return rtl && align === 'right' ? 'left' : align;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownPosition, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownPosition, isStandalone: true, outputs: { directionChange: "tuiDropdownDirectionChange" }, usesInheritance: true, ngImport: i0 }); }
}
__decorate([
    tuiPure
], TuiDropdownPosition.prototype, "emitDirection", null);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownPosition, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                }]
        }], propDecorators: { directionChange: [{
                type: Output,
                args: ['tuiDropdownDirectionChange']
            }], emitDirection: [] } });

class TuiDropdownDirective {
    constructor() {
        this.refresh$ = new Subject();
        this.service = inject(TuiDropdownService);
        this.cdr = inject(ChangeDetectorRef);
        // TODO: think of a better solution later
        this.drivers = coerceArray(inject(TuiDropdownDriver, { self: true, optional: true }));
        this.sub = this.refresh$
            .pipe(throttleTime(0, tuiZonefreeScheduler()), takeUntilDestroyed())
            .subscribe(() => {
            this.ref()?.changeDetectorRef.detectChanges();
            this.ref()?.changeDetectorRef.markForCheck();
        });
        this.el = tuiInjectElement();
        this.type = 'dropdown';
        this.component = new PolymorpheusComponent(inject(TUI_DROPDOWN_COMPONENT), inject(INJECTOR));
        this.ref = signal(null);
        // TODO(v5): rename to `content`
        // eslint-disable-next-line @typescript-eslint/naming-convention
        this._content = signal(null);
    }
    set tuiDropdown(content) {
        this._content.set(content instanceof TemplateRef
            ? new PolymorpheusTemplate(content, this.cdr)
            : content);
        if (!this._content()) {
            this.toggle(false);
        }
    }
    get position() {
        return tuiCheckFixedPosition(this.el) ? 'fixed' : 'absolute';
    }
    // TODO(v5): delete
    get content() {
        return this._content();
    }
    // TODO(v5): delete
    set content(x) {
        this._content.set(x);
    }
    ngAfterViewChecked() {
        this.refresh$.next();
    }
    ngOnDestroy() {
        this.toggle(false);
    }
    getClientRect() {
        return this.el.getBoundingClientRect();
    }
    toggle(show) {
        const ref = this.ref();
        if (show && this._content() && !ref) {
            this.ref.set(this.service.add(this.component));
        }
        else if (!show && ref) {
            this.ref.set(null);
            this.service.remove(ref);
        }
        this.drivers.forEach((driver) => driver?.next(show));
        // TODO: Remove in v5, only needed in Angular 16
        this.cdr.markForCheck();
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownDirective, isStandalone: true, selector: "[tuiDropdown]:not(ng-container):not(ng-template)", inputs: { tuiDropdown: "tuiDropdown" }, host: { properties: { "class.tui-dropdown-open": "ref()" } }, providers: [
            tuiAsRectAccessor(TuiDropdownDirective),
            tuiAsVehicle(TuiDropdownDirective),
        ], exportAs: ["tuiDropdown"], hostDirectives: [{ directive: TuiDropdownDriverDirective }, { directive: TuiDropdownPosition, outputs: ["tuiDropdownDirectionChange", "tuiDropdownDirectionChange"] }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownDirective, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[tuiDropdown]:not(ng-container):not(ng-template)',
                    providers: [
                        tuiAsRectAccessor(TuiDropdownDirective),
                        tuiAsVehicle(TuiDropdownDirective),
                    ],
                    exportAs: 'tuiDropdown',
                    hostDirectives: [
                        TuiDropdownDriverDirective,
                        {
                            directive: TuiDropdownPosition,
                            outputs: ['tuiDropdownDirectionChange'],
                        },
                    ],
                    host: {
                        '[class.tui-dropdown-open]': 'ref()',
                    },
                }]
        }], propDecorators: { tuiDropdown: [{
                type: Input
            }] } });

/**
 * @description:
 * This component is used to show template in a portal
 * using default style of white rounded box with a shadow
 */
class TuiDropdownComponent {
    constructor() {
        this.el = tuiInjectElement();
        this.accessor = inject(TuiRectAccessor);
        this.viewport = inject(TUI_VIEWPORT);
        this.vvs = inject(TuiVisualViewportService);
        this.styles$ = inject(TuiPositionService).pipe(takeWhile(() => this.directive.el.isConnected &&
            !!this.directive.el.getBoundingClientRect().height), map((v) => (this.position === 'fixed' ? this.vvs.correct(v) : v)), map(([top, left]) => this.getStyles(left, top)), takeUntilDestroyed());
        this.options = inject(TUI_DROPDOWN_OPTIONS);
        this.directive = inject(TuiDropdownDirective);
        this.context = inject(TUI_DROPDOWN_CONTEXT, { optional: true });
        this.darkMode = inject(TUI_DARK_MODE);
        this.position = this.directive.position;
        this.theme = computed((_ = this.darkMode()) => this.directive.el.closest('[tuiTheme]')?.getAttribute('tuiTheme'));
        this.close = () => this.directive.toggle(false);
    }
    ngAfterViewInit() {
        this.styles$.subscribe({
            next: (styles) => Object.assign(this.el.style, styles),
            complete: () => this.close?.(),
        });
    }
    getStyles(x, y) {
        const { maxHeight, minHeight, offset, limitWidth } = this.options;
        const parent = this.el.offsetParent?.getBoundingClientRect() || EMPTY_CLIENT_RECT;
        const { left = 0, top = 0 } = this.position === 'fixed' ? {} : parent;
        const rect = this.accessor.getClientRect();
        const viewport = this.viewport.getClientRect();
        const above = rect.top - viewport.top - 2 * offset;
        const below = viewport.top + viewport.height - y - offset;
        const available = y > rect.bottom ? below : above;
        const height = this.el.getBoundingClientRect().right <= rect.left || x >= rect.right
            ? maxHeight
            : tuiClamp(available, minHeight, maxHeight);
        y -= top;
        x -= left;
        return {
            position: this.position,
            top: tuiPx(Math.round(Math.max(y, offset - top))),
            left: tuiPx(Math.round(x)),
            maxHeight: tuiPx(Math.round(height)),
            width: limitWidth === 'fixed' ? tuiPx(Math.round(rect.width)) : '',
            minWidth: limitWidth === 'min' ? tuiPx(Math.round(rect.width)) : '',
            maxWidth: tuiPx(Math.round(viewport.width) - 16), // 8px min gap from each side
        };
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownComponent, isStandalone: true, selector: "tui-dropdown", host: { properties: { "attr.data-appearance": "options.appearance", "attr.tuiTheme": "theme()" } }, providers: [
            TuiPositionService,
            tuiPositionAccessorFor('dropdown', TuiDropdownPosition),
            tuiRectAccessorFor('dropdown', TuiDropdownDirective),
        ], hostDirectives: [{ directive: i1.TuiActiveZone }, { directive: i2.TuiAnimated }], ngImport: i0, template: "<tui-scrollbar class=\"t-scroll\">\n    <div\n        *polymorpheusOutlet=\"directive._content() as text; context: {$implicit: close}\"\n        class=\"t-primitive\"\n    >\n        {{ text }}\n    </div>\n</tui-scrollbar>\n", styles: [":host{position:absolute;display:flex;box-shadow:var(--tui-shadow-medium);color:var(--tui-text-primary);background:var(--tui-background-elevation-3);border-radius:var(--tui-radius-m);overflow:hidden;border:1px solid var(--tui-border-normal);box-sizing:border-box;isolation:isolate;pointer-events:auto;--tui-from: translateY(-1rem)}:host.tui-enter,:host.tui-leave{animation-name:tuiFade,tuiSlide}:host:not([style*=top]){visibility:hidden}.t-scroll{flex-grow:1;max-inline-size:100%;inline-size:-webkit-max-content;inline-size:max-content;overscroll-behavior:none}.t-primitive{padding:1rem}\n"], dependencies: [{ kind: "directive", type: PolymorpheusOutlet, selector: "[polymorpheusOutlet]", inputs: ["polymorpheusOutlet", "polymorpheusOutletContext"] }, { kind: "component", type: TuiScrollbar, selector: "tui-scrollbar", inputs: ["hidden"] }], changeDetection: i0.ChangeDetectionStrategy.Default }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownComponent, decorators: [{
            type: Component,
            args: [{ standalone: true, selector: 'tui-dropdown', imports: [PolymorpheusOutlet, TuiScrollbar], changeDetection: ChangeDetectionStrategy.Default, providers: [
                        TuiPositionService,
                        tuiPositionAccessorFor('dropdown', TuiDropdownPosition),
                        tuiRectAccessorFor('dropdown', TuiDropdownDirective),
                    ], hostDirectives: [TuiActiveZone, TuiAnimated], host: {
                        '[attr.data-appearance]': 'options.appearance',
                        '[attr.tuiTheme]': 'theme()',
                    }, template: "<tui-scrollbar class=\"t-scroll\">\n    <div\n        *polymorpheusOutlet=\"directive._content() as text; context: {$implicit: close}\"\n        class=\"t-primitive\"\n    >\n        {{ text }}\n    </div>\n</tui-scrollbar>\n", styles: [":host{position:absolute;display:flex;box-shadow:var(--tui-shadow-medium);color:var(--tui-text-primary);background:var(--tui-background-elevation-3);border-radius:var(--tui-radius-m);overflow:hidden;border:1px solid var(--tui-border-normal);box-sizing:border-box;isolation:isolate;pointer-events:auto;--tui-from: translateY(-1rem)}:host.tui-enter,:host.tui-leave{animation-name:tuiFade,tuiSlide}:host:not([style*=top]){visibility:hidden}.t-scroll{flex-grow:1;max-inline-size:100%;inline-size:-webkit-max-content;inline-size:max-content;overscroll-behavior:none}.t-primitive{padding:1rem}\n"] }]
        }] });

function activeZoneFilter(event) {
    return (!event ||
        (this.driver.value && !this.activeZone.contains(tuiGetActualTarget(event))));
}
class TuiDropdownContext extends TuiRectAccessor {
    constructor() {
        super(...arguments);
        this.isTouch = inject(TUI_IS_TOUCH);
        this.currentRect = EMPTY_CLIENT_RECT;
        this.userSelect = computed(() => (this.isTouch() ? 'none' : null));
        this.activeZone = inject(TuiActiveZone);
        this.driver = inject(TuiDropdownDriver);
        this.doc = inject(DOCUMENT);
        this.sub = merge(tuiTypedFromEvent(this.doc, 'pointerdown'), tuiTypedFromEvent(this.doc, 'contextmenu', { capture: true }))
            .pipe(tuiZonefree(), takeUntilDestroyed())
            .subscribe((event) => this.closeDropdown(event));
        this.type = 'dropdown';
    }
    getClientRect() {
        return this.currentRect;
    }
    closeDropdown(_event) {
        this.driver.next(false);
        this.currentRect = EMPTY_CLIENT_RECT;
    }
    onContextMenu(x, y) {
        this.currentRect = tuiPointToClientRect(x, y);
        this.driver.next(true);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownContext, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownContext, isStandalone: true, selector: "[tuiDropdownContext]", host: { listeners: { "document:keydown.esc": "closeDropdown()", "longtap": "onContextMenu($event.detail.clientX, $event.detail.clientY)" }, properties: { "style.user-select": "userSelect()", "style.-webkit-user-select": "userSelect()", "style.-webkit-touch-callout": "userSelect()" } }, providers: [
            TuiActiveZone,
            TuiDropdownDriver,
            tuiAsDriver(TuiDropdownDriver),
            tuiAsRectAccessor(TuiDropdownContext),
        ], usesInheritance: true, ngImport: i0 }); }
}
__decorate([
    shouldCall(activeZoneFilter)
], TuiDropdownContext.prototype, "closeDropdown", null);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownContext, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[tuiDropdownContext]',
                    providers: [
                        TuiActiveZone,
                        TuiDropdownDriver,
                        tuiAsDriver(TuiDropdownDriver),
                        tuiAsRectAccessor(TuiDropdownContext),
                    ],
                    host: {
                        '[style.user-select]': 'userSelect()',
                        '[style.-webkit-user-select]': 'userSelect()',
                        '[style.-webkit-touch-callout]': 'userSelect()',
                        '(document:keydown.esc)': 'closeDropdown()',
                        '(longtap)': 'onContextMenu($event.detail.clientX, $event.detail.clientY)',
                    },
                }]
        }], propDecorators: { closeDropdown: [] } });

/** Default values for hint options */
const TUI_DROPDOWN_HOVER_DEFAULT_OPTIONS = {
    showDelay: 200,
    hideDelay: 500,
};
/**
 * Default parameters for dropdown hover directive
 */
const TUI_DROPDOWN_HOVER_OPTIONS = new InjectionToken(ngDevMode ? 'TUI_DROPDOWN_HOVER_OPTIONS' : '', {
    factory: () => TUI_DROPDOWN_HOVER_DEFAULT_OPTIONS,
});
function tuiDropdownHoverOptionsProvider(options) {
    return tuiProvideOptions(TUI_DROPDOWN_HOVER_OPTIONS, options, TUI_DROPDOWN_HOVER_DEFAULT_OPTIONS);
}

function shouldClose(event) {
    return (
    // @ts-ignore
    typeof CloseWatcher === 'undefined' &&
        // ?. for auto fill events
        event.key?.toLowerCase() === 'escape' &&
        this.tuiDropdownEnabled &&
        !!this.tuiDropdownOpen &&
        !this['dropdown']()?.nextElementSibling);
}
class TuiDropdownOpen {
    constructor() {
        this.directive = inject(TuiDropdownDirective);
        this.el = tuiInjectElement();
        this.obscured = inject(TuiObscured);
        this.activeZone = inject(TuiActiveZone);
        this.dropdown = computed(() => this.directive.ref()?.location.nativeElement);
        this.tuiDropdownEnabled = true;
        this.tuiDropdownOpen = false;
        this.tuiDropdownOpenChange = new EventEmitter();
        // TODO: make it private when all legacy controls will be deleted from @taiga-ui/legacy (5.0)
        this.driver = inject(TuiDropdownDriver);
        this.sub = this.driver
            .pipe(tuiIfMap(() => merge(tuiCloseWatcher(), this.obscured.tuiObscured.pipe(filter(Boolean)), this.activeZone.tuiActiveZoneChange.pipe(filter((a) => !a)), fromEvent(this.el, 'focusin').pipe(filter((event) => !this.host.contains(tuiGetActualTarget(event)) ||
            !this.directive.ref())))), tuiZonefull(), tuiWatch(), takeUntilDestroyed())
            .subscribe(() => this.toggle(false));
        this.sync = this.driver.pipe(takeUntilDestroyed()).subscribe((open) => {
            if (open !== this.tuiDropdownOpen) {
                this.update(open);
            }
        });
    }
    ngOnChanges() {
        this.drive(!!this.tuiDropdownOpen);
        this.tuiDropdownOpenChange.emit(!!this.tuiDropdownOpen);
    }
    toggle(open) {
        if (this.focused && !open) {
            this.host.focus({ preventScroll: true });
        }
        this.update(open);
    }
    onEsc(event) {
        event.preventDefault();
        this.toggle(false);
    }
    onClick(target) {
        if (!this.editable && this.host.contains(target)) {
            this.update(!this.tuiDropdownOpen);
        }
    }
    onArrow(event, up) {
        if (!tuiIsElement(event.target) ||
            !this.host.contains(event.target) ||
            !this.tuiDropdownEnabled ||
            !this.directive._content()) {
            return;
        }
        event.preventDefault();
        this.focusDropdown(up);
    }
    onKeydown(event) {
        const target = tuiGetActualTarget(event);
        if (!event.defaultPrevented &&
            tuiIsEditingKey(event.key) &&
            this.editable &&
            this.focused &&
            tuiIsHTMLElement(target) &&
            !tuiIsElementEditable(target)) {
            this.host.focus({ preventScroll: true });
        }
    }
    get host() {
        const initial = this.dropdownHost?.nativeElement || this.el;
        const focusable = tuiIsNativeKeyboardFocusable(initial)
            ? initial
            : tuiGetClosestFocusable({ initial, root: this.el });
        return this.dropdownHost?.nativeElement || focusable || this.el;
    }
    get editable() {
        return tuiIsElementEditable(this.host);
    }
    get focused() {
        return tuiIsNativeFocusedIn(this.host) || tuiIsNativeFocusedIn(this.dropdown());
    }
    update(open) {
        if (open && !this.tuiDropdownEnabled) {
            return this.drive();
        }
        this.tuiDropdownOpen = open;
        this.tuiDropdownOpenChange.emit(open);
        this.drive();
    }
    drive(open = !!this.tuiDropdownOpen && this.tuiDropdownEnabled) {
        this.obscured.tuiObscuredEnabled = open;
        this.driver.next(open);
    }
    focusDropdown(previous) {
        const root = this.dropdown();
        if (!root) {
            this.update(true);
            return;
        }
        const doc = this.el.ownerDocument;
        const child = root.appendChild(doc.createElement('div'));
        const initial = previous ? child : root;
        const focusable = tuiGetClosestFocusable({ initial, previous, root });
        child.remove();
        focusable?.focus();
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownOpen, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownOpen, isStandalone: true, selector: "[tuiDropdown][tuiDropdownOpen],[tuiDropdown][tuiDropdownOpenChange]", inputs: { tuiDropdownEnabled: "tuiDropdownEnabled", tuiDropdownOpen: "tuiDropdownOpen" }, outputs: { tuiDropdownOpenChange: "tuiDropdownOpenChange" }, host: { listeners: { "click": "onClick($event.target)", "keydown.arrowDown": "onArrow($event, false)", "keydown.arrowUp": "onArrow($event, true)", "document:keydown.zoneless.capture": "onEsc($event)", "document:keydown.zoneless": "onKeydown($event)", "tuiActiveZoneChange": "0" } }, providers: [TuiDropdownDriver, tuiAsDriver(TuiDropdownDriver)], queries: [{ propertyName: "dropdownHost", first: true, predicate: ["tuiDropdownHost"], descendants: true, read: ElementRef }], usesOnChanges: true, hostDirectives: [{ directive: i1$1.TuiObscured }, { directive: i1.TuiActiveZone, inputs: ["tuiActiveZoneParent", "tuiActiveZoneParent"], outputs: ["tuiActiveZoneChange", "tuiActiveZoneChange"] }], ngImport: i0 }); }
}
__decorate([
    shouldCall(shouldClose)
], TuiDropdownOpen.prototype, "onEsc", null);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownOpen, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[tuiDropdown][tuiDropdownOpen],[tuiDropdown][tuiDropdownOpenChange]',
                    providers: [TuiDropdownDriver, tuiAsDriver(TuiDropdownDriver)],
                    hostDirectives: [
                        TuiObscured,
                        {
                            directive: TuiActiveZone,
                            inputs: ['tuiActiveZoneParent'],
                            outputs: ['tuiActiveZoneChange'],
                        },
                    ],
                    host: {
                        '(click)': 'onClick($event.target)',
                        '(keydown.arrowDown)': 'onArrow($event, false)',
                        '(keydown.arrowUp)': 'onArrow($event, true)',
                        '(document:keydown.zoneless.capture)': 'onEsc($event)',
                        '(document:keydown.zoneless)': 'onKeydown($event)',
                        // TODO: Necessary because startWith(false) + distinctUntilChanged() in TuiActiveZone, think of better solution
                        '(tuiActiveZoneChange)': '0',
                    },
                }]
        }], propDecorators: { dropdownHost: [{
                type: ContentChild,
                args: ['tuiDropdownHost', { descendants: true, read: ElementRef }]
            }], tuiDropdownEnabled: [{
                type: Input
            }], tuiDropdownOpen: [{
                type: Input
            }], tuiDropdownOpenChange: [{
                type: Output
            }], onEsc: [] } });

class TuiDropdownHover extends TuiDriver {
    constructor() {
        super((subscriber) => this.stream$.subscribe(subscriber));
        this.el = tuiInjectElement();
        this.doc = inject(DOCUMENT);
        this.options = inject(TUI_DROPDOWN_HOVER_OPTIONS);
        this.activeZone = inject(TuiActiveZone);
        this.open = inject(TuiDropdownOpen, { optional: true });
        /**
         * Dropdown can be removed not only via click/touch –
         * swipe on mobile devices removes dropdown sheet without triggering new mouseover / mouseout events.
         */
        this.dropdownExternalRemoval$ = toObservable(inject(TuiDropdownDirective).ref).pipe(filter((x) => !x && this.hovered));
        this.stream$ = merge(this.dropdownExternalRemoval$.pipe(switchMap(() => tuiTypedFromEvent(this.doc, 'pointerdown').pipe(map(tuiGetActualTarget), delay(this.hideDelay), startWith(null), takeUntil(fromEvent(this.doc, 'mouseover'))))), tuiTypedFromEvent(this.doc, 'mouseover').pipe(map(tuiGetActualTarget)), tuiTypedFromEvent(this.doc, 'mouseout').pipe(map((e) => e.relatedTarget))).pipe(map((element) => tuiIsElement(element) && this.isHovered(element)), distinctUntilChanged(), switchMap((v) => of(v).pipe(delay(v ? this.showDelay : this.hideDelay))), tuiZoneOptimized(), tap((hovered) => {
            this.hovered = hovered;
            this.open?.toggle(hovered);
        }), share());
        this.showDelay = this.options.showDelay;
        this.hideDelay = this.options.hideDelay;
        this.hovered = false;
        this.type = 'dropdown';
    }
    onClick(event) {
        if (this.hovered && this.open) {
            event.preventDefault();
        }
    }
    isHovered(element) {
        const host = this.dropdownHost?.nativeElement || this.el;
        const hovered = host.contains(element);
        const child = !this.el.contains(element) && this.activeZone.contains(element);
        return hovered || child;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownHover, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownHover, isStandalone: true, selector: "[tuiDropdownHover]", inputs: { showDelay: ["tuiDropdownShowDelay", "showDelay"], hideDelay: ["tuiDropdownHideDelay", "hideDelay"] }, host: { listeners: { "click.capture": "onClick($event)" } }, providers: [TuiActiveZone, tuiAsDriver(TuiDropdownHover)], queries: [{ propertyName: "dropdownHost", first: true, predicate: ["tuiDropdownHost"], descendants: true, read: ElementRef }], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownHover, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[tuiDropdownHover]',
                    providers: [TuiActiveZone, tuiAsDriver(TuiDropdownHover)],
                    host: {
                        '(click.capture)': 'onClick($event)',
                    },
                }]
        }], ctorParameters: function () { return []; }, propDecorators: { dropdownHost: [{
                type: ContentChild,
                args: ['tuiDropdownHost', { descendants: true, read: ElementRef }]
            }], showDelay: [{
                type: Input,
                args: ['tuiDropdownShowDelay']
            }], hideDelay: [{
                type: Input,
                args: ['tuiDropdownHideDelay']
            }] } });

class TuiDropdownManual {
    constructor() {
        this.driver = inject(TuiDropdownDriver);
        this.tuiDropdownManual = false;
    }
    ngOnChanges() {
        this.driver.next(!!this.tuiDropdownManual);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownManual, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownManual, isStandalone: true, selector: "[tuiDropdownManual]", inputs: { tuiDropdownManual: "tuiDropdownManual" }, providers: [TuiDropdownDriver, tuiAsDriver(TuiDropdownDriver)], usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownManual, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[tuiDropdownManual]',
                    providers: [TuiDropdownDriver, tuiAsDriver(TuiDropdownDriver)],
                }]
        }], propDecorators: { tuiDropdownManual: [{
                type: Input
            }] } });

/**
 * @deprecated TODO: remove in v.5 when legacy controls are dropped
 */
class TuiDropdownOpenLegacy {
    constructor() {
        this.openStateSub = new Subject();
        this.tuiDropdownOpenChange = this.openStateSub.pipe(distinctUntilChanged());
    }
    set tuiDropdownOpen(open) {
        this.emitOpenChange(open);
    }
    emitOpenChange(open) {
        this.openStateSub.next(open);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownOpenLegacy, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownOpenLegacy, isStandalone: true, selector: "[tuiDropdownOpen]:not([tuiDropdown]),[tuiDropdownOpenChange]:not([tuiDropdown])", inputs: { tuiDropdownOpen: "tuiDropdownOpen" }, outputs: { tuiDropdownOpenChange: "tuiDropdownOpenChange" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownOpenLegacy, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[tuiDropdownOpen]:not([tuiDropdown]),[tuiDropdownOpenChange]:not([tuiDropdown])',
                }]
        }], propDecorators: { tuiDropdownOpenChange: [{
                type: Output
            }], tuiDropdownOpen: [{
                type: Input
            }] } });

/**
 * @deprecated use {@link TuiPopup} directive instead
 */
class TuiDropdownPortal {
    constructor() {
        this.template = inject(TemplateRef);
        this.service = inject(TuiDropdownService);
    }
    set tuiDropdown(show) {
        this.viewRef?.destroy();
        if (show) {
            this.viewRef = this.service.addTemplate(this.template);
        }
    }
    ngOnDestroy() {
        this.viewRef?.destroy();
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownPortal, isStandalone: true, selector: "ng-template[tuiDropdown]", inputs: { tuiDropdown: "tuiDropdown" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownPortal, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: 'ng-template[tuiDropdown]',
                }]
        }], propDecorators: { tuiDropdown: [{
                type: Input
            }] } });

class TuiDropdownPositionSided extends TuiPositionAccessor {
    constructor() {
        super(...arguments);
        this.options = inject(TUI_DROPDOWN_OPTIONS);
        this.viewport = inject(TUI_VIEWPORT);
        this.vertical = inject(TuiDropdownPosition);
        this.previous = this.options.direction || 'bottom';
        this.tuiDropdownSided = '';
        this.tuiDropdownSidedOffset = 4;
        this.type = 'dropdown';
    }
    getPosition(rect) {
        if (this.tuiDropdownSided === false) {
            return this.vertical.getPosition(rect);
        }
        const { height, width } = rect;
        const hostRect = this.vertical.accessor?.getClientRect() ?? EMPTY_CLIENT_RECT;
        const viewport = this.viewport.getClientRect();
        const { direction, offset } = this.options;
        const adjusted = this.vertical.getAlign(this.options.align);
        const align = adjusted === 'center' ? 'left' : adjusted;
        const available = {
            top: hostRect.bottom - viewport.top,
            left: hostRect.left - offset - viewport.left,
            right: viewport.right - hostRect.right - offset,
            bottom: viewport.bottom - hostRect.top,
        };
        const position = {
            top: hostRect.bottom - height + this.tuiDropdownSidedOffset + 1,
            left: hostRect.left - width - offset,
            right: hostRect.right + offset,
            bottom: hostRect.top - this.tuiDropdownSidedOffset - 1, // 1 for border
        };
        const better = available.top > available.bottom ? 'top' : 'bottom';
        const maxLeft = available.left > available.right ? position.left : position.right;
        const left = available[align] > width ? position[align] : maxLeft;
        if ((available[this.previous] > height && direction) ||
            this.previous === better) {
            this.vertical.emitDirection(this.previous);
            return [position[this.previous], left];
        }
        this.previous = better;
        this.vertical.emitDirection(better);
        return [position[better], left];
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownPositionSided, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownPositionSided, isStandalone: true, selector: "[tuiDropdownSided]", inputs: { tuiDropdownSided: "tuiDropdownSided", tuiDropdownSidedOffset: "tuiDropdownSidedOffset" }, providers: [TuiDropdownPosition, tuiAsPositionAccessor(TuiDropdownPositionSided)], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownPositionSided, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[tuiDropdownSided]',
                    providers: [TuiDropdownPosition, tuiAsPositionAccessor(TuiDropdownPositionSided)],
                }]
        }], propDecorators: { tuiDropdownSided: [{
                type: Input
            }], tuiDropdownSidedOffset: [{
                type: Input
            }] } });

class TuiDropdownSelection extends TuiDriver {
    constructor() {
        super((subscriber) => this.stream$.subscribe(subscriber));
        this.doc = inject(DOCUMENT);
        this.vcr = inject(ViewContainerRef);
        this.dropdown = inject(TuiDropdownDirective);
        this.el = tuiInjectElement();
        this.handler$ = new BehaviorSubject(TUI_TRUE_HANDLER);
        this.stream$ = combineLatest([
            this.handler$,
            inject(TUI_SELECTION_STREAM).pipe(map(() => this.getRange()), filter((range) => this.isValid(range)), distinctUntilChanged((x, y) => x.startOffset === y.startOffset &&
                x.endOffset === y.endOffset &&
                x.commonAncestorContainer === y.commonAncestorContainer)),
        ]).pipe(map(([handler, range]) => {
            const contained = this.el.contains(range.commonAncestorContainer);
            this.range =
                contained && tuiIsTextNode(range.commonAncestorContainer)
                    ? range
                    : this.range;
            return (contained && handler(this.range)) || this.inDropdown(range);
        }));
        this.range = inject(TUI_RANGE);
        this.position = 'selection';
        this.type = 'dropdown';
    }
    set tuiDropdownSelection(visible) {
        if (!tuiIsString(visible)) {
            this.handler$.next(visible);
        }
    }
    getClientRect() {
        switch (this.position) {
            case 'tag': {
                const { commonAncestorContainer } = this.range;
                const element = tuiIsElement(commonAncestorContainer)
                    ? commonAncestorContainer
                    : commonAncestorContainer.parentNode;
                return element && tuiIsElement(element)
                    ? element.getBoundingClientRect()
                    : EMPTY_CLIENT_RECT;
            }
            case 'word':
                return tuiGetWordRange(this.range).getBoundingClientRect();
            default:
                return this.range.getBoundingClientRect();
        }
    }
    ngOnDestroy() {
        if (this.ghost) {
            this.ghostHost.removeChild(this.ghost);
        }
    }
    get ghostHost() {
        return this.el.querySelector('tui-textfield .t-ghost') || this.el;
    }
    getRange() {
        const active = tuiGetNativeFocused(this.doc);
        const selection = this.doc.getSelection();
        const range = active && tuiIsTextfield(active) && this.el.contains(active)
            ? this.veryVerySadInputFix(active)
            : (selection?.rangeCount && selection.getRangeAt(0)) || this.range;
        return range.cloneRange();
    }
    /**
     * Check if given range is at least partially inside dropdown
     */
    inDropdown(range) {
        const { startContainer, endContainer } = range;
        const inDropdown = this.boxContains(range.commonAncestorContainer);
        const hostToDropdown = this.boxContains(endContainer) && this.el.contains(startContainer);
        const dropdownToHost = this.boxContains(startContainer) && this.el.contains(endContainer);
        return inDropdown || hostToDropdown || dropdownToHost;
    }
    /**
     * Check if Node is inside dropdown
     */
    boxContains(node) {
        return !!this.dropdown.ref()?.location.nativeElement.contains(node);
    }
    /**
     * Check if range is not inside tui-textfield's DOM elements
     */
    isValid(range) {
        return (!this.el.contains(range.commonAncestorContainer) ||
            !this.el.closest('tui-textfield') ||
            range.intersectsNode(this.ghost || this.el));
    }
    veryVerySadInputFix(element) {
        const { ghost = this.initGhost(this.ghostHost) } = this;
        const { top, left, width, height } = this.ghostHost.getBoundingClientRect();
        const { selectionStart, selectionEnd, value } = element;
        const range = this.doc.createRange();
        const hostRect = this.ghostHost.getBoundingClientRect();
        ghost.style.top = tuiPx(top - hostRect.top);
        ghost.style.left = tuiPx(left - hostRect.left);
        ghost.style.width = tuiPx(width);
        ghost.style.height = tuiPx(height);
        ghost.textContent = CHAR_ZERO_WIDTH_SPACE + value + CHAR_NO_BREAK_SPACE;
        range.setStart(ghost.firstChild, selectionStart || 0);
        range.setEnd(ghost.firstChild, selectionEnd || 0);
        return range;
    }
    /**
     * Create an invisible DIV styled exactly like input/textarea element inside directive
     */
    initGhost(element) {
        const ghost = this.doc.createElement('div');
        const { font, letterSpacing, textTransform, padding, borderTop } = getComputedStyle(element);
        ghost.style.position = 'absolute';
        ghost.style.pointerEvents = 'none';
        ghost.style.opacity = '0';
        ghost.style.whiteSpace = 'pre-wrap';
        ghost.style.boxSizing = 'border-box';
        ghost.style.borderTop = borderTop;
        ghost.style.font = font;
        ghost.style.letterSpacing = letterSpacing;
        ghost.style.textTransform = textTransform;
        ghost.style.padding = padding;
        this.ghostHost.appendChild(ghost);
        this.ghost = ghost;
        return ghost;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownSelection, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownSelection, isStandalone: true, selector: "[tuiDropdownSelection]", inputs: { position: ["tuiDropdownSelectionPosition", "position"], tuiDropdownSelection: "tuiDropdownSelection" }, providers: [
            tuiAsDriver(TuiDropdownSelection),
            tuiAsRectAccessor(TuiDropdownSelection),
        ], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownSelection, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[tuiDropdownSelection]',
                    providers: [
                        tuiAsDriver(TuiDropdownSelection),
                        tuiAsRectAccessor(TuiDropdownSelection),
                    ],
                }]
        }], ctorParameters: function () { return []; }, propDecorators: { position: [{
                type: Input,
                args: ['tuiDropdownSelectionPosition']
            }], tuiDropdownSelection: [{
                type: Input
            }] } });

const TuiDropdown = [
    TuiDropdownOptionsDirective,
    TuiDropdownDriverDirective,
    TuiDropdownDirective,
    TuiDropdownComponent,
    TuiDropdownOpen,
    TuiDropdownOpenLegacy,
    TuiDropdownPortal,
    TuiDropdownManual,
    TuiDropdownHover,
    TuiDropdownContext,
    TuiDropdownPosition,
    TuiDropdownPositionSided,
    TuiDropdownSelection,
];

function tuiDropdown(value) {
    return tuiDirectiveBinding(TuiDropdownDirective, 'tuiDropdown', value, {});
}
function tuiDropdownEnabled(value) {
    return tuiDirectiveBinding(TuiDropdownOpen, 'tuiDropdownEnabled', value, {});
}
function tuiDropdownOpen() {
    const open = tuiDirectiveBinding(TuiDropdownOpen, 'tuiDropdownOpen', false, {});
    inject(TuiDropdownOpen)
        .tuiDropdownOpenChange.pipe(takeUntilDestroyed())
        .subscribe((value) => open.set(value));
    return open;
}

class TuiDropdownFixed {
    constructor() {
        const override = tuiOverrideOptions$1({ limitWidth: 'fixed' }, TUI_DROPDOWN_DEFAULT_OPTIONS);
        override(inject(TUI_DROPDOWN_OPTIONS, { self: true, optional: true }), null);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownFixed, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownFixed, isStandalone: true, providers: [tuiDropdownOptionsProvider({})], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownFixed, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    providers: [tuiDropdownOptionsProvider({})],
                }]
        }], ctorParameters: function () { return []; } });
class TuiDropdownAuto {
    constructor() {
        /**
         * Update directive props with new defaults before inputs are processed
         * TODO: find better way to override TuiDropdownFixed host directive from parent component
         */
        inject(TUI_DROPDOWN_OPTIONS).limitWidth = 'auto';
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownAuto, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdownAuto, isStandalone: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdownAuto, decorators: [{
            type: Directive,
            args: [{ standalone: true }]
        }], ctorParameters: function () { return []; } });

/**
 * Host element for dynamically created portals, for example using {@link TuiDropdownDirective}.
 */
class TuiDropdowns extends TuiPortals {
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdowns, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: TuiDropdowns, isStandalone: true, selector: "tui-dropdowns", host: { styleAttribute: "position: absolute; width: 100%; top: 0" }, providers: [tuiAsPortal(TuiDropdownService)], usesInheritance: true, ngImport: i0, template: '<ng-container #viewContainer />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiDropdowns, decorators: [{
            type: Component,
            args: [{
                    standalone: true,
                    selector: 'tui-dropdowns',
                    template: '<ng-container #viewContainer />',
                    changeDetection: ChangeDetectionStrategy.OnPush,
                    providers: [tuiAsPortal(TuiDropdownService)],
                    host: { style: 'position: absolute; width: 100%; top: 0' },
                }]
        }] });

class TuiWithDropdownOpen {
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiWithDropdownOpen, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiWithDropdownOpen, isStandalone: true, hostDirectives: [{ directive: TuiDropdownOpen, inputs: ["tuiDropdownOpen", "open", "tuiDropdownEnabled", "tuiDropdownEnabled"], outputs: ["tuiDropdownOpenChange", "openChange"] }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiWithDropdownOpen, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    hostDirectives: [
                        {
                            directive: TuiDropdownOpen,
                            inputs: ['tuiDropdownOpen: open', 'tuiDropdownEnabled'],
                            outputs: ['tuiDropdownOpenChange: openChange'],
                        },
                    ],
                }]
        }] });

/**
 * Generated bundle index. Do not edit.
 */

export { TUI_DROPDOWN_COMPONENT, TUI_DROPDOWN_CONTEXT, TUI_DROPDOWN_DEFAULT_OPTIONS, TUI_DROPDOWN_HOVER_DEFAULT_OPTIONS, TUI_DROPDOWN_HOVER_OPTIONS, TUI_DROPDOWN_OPTIONS, TuiDropdown, TuiDropdownAuto, TuiDropdownComponent, TuiDropdownContext, TuiDropdownDirective, TuiDropdownDriver, TuiDropdownDriverDirective, TuiDropdownFixed, TuiDropdownHover, TuiDropdownManual, TuiDropdownOpen, TuiDropdownOpenLegacy, TuiDropdownOptionsDirective, TuiDropdownPortal, TuiDropdownPosition, TuiDropdownPositionSided, TuiDropdownSelection, TuiDropdownService, TuiDropdowns, TuiWithDropdownOpen, tuiDropdown, tuiDropdownEnabled, tuiDropdownHoverOptionsProvider, tuiDropdownOpen, tuiDropdownOptionsProvider };
//# sourceMappingURL=taiga-ui-core-directives-dropdown.mjs.map