@taiga-ui/kit
Version:
Taiga UI Angular main components kit
442 lines (431 loc) • 41.4 kB
JavaScript
import * as i0 from '@angular/core';
import { inject, Directive, ChangeDetectionStrategy, ViewEncapsulation, Component, INJECTOR, input, model, afterNextRender, effect, InjectionToken, ElementRef, ChangeDetectorRef, viewChild, contentChildren, TemplateRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { RouterLinkActive } from '@angular/router';
import { WaMutationObserverService, WA_MUTATION_OBSERVER_INIT } from '@ng-web-apis/mutation-observer';
import { tuiTypedFromEvent, tuiZonefree } from '@taiga-ui/cdk/observables';
import { tuiInjectElement, tuiIsElement } from '@taiga-ui/cdk/utils/dom';
import { tuiIsFocused, tuiMoveFocus, tuiGetClosestFocusable } from '@taiga-ui/cdk/utils/focus';
import * as i1 from '@taiga-ui/core/directives/icons';
import { TuiWithIcons } from '@taiga-ui/core/directives/icons';
import { filter, merge, EMPTY, switchMap, take, tap, debounceTime, startWith, map } from 'rxjs';
import { TuiItem } from '@taiga-ui/cdk/directives/item';
import { TUI_VERSION } from '@taiga-ui/cdk/constants';
import { tuiWithStyles, tuiPx } from '@taiga-ui/cdk/utils/miscellaneous';
import { tuiCreateOptions } from '@taiga-ui/cdk/utils/di';
import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
import { tuiToInt } from '@taiga-ui/cdk/utils/math';
import * as i1$1 from '@taiga-ui/core/portals/dropdown';
import { tuiDropdownOptionsProvider, TuiDropdown } from '@taiga-ui/core/portals/dropdown';
import { TuiChevron } from '@taiga-ui/kit/directives/chevron';
import { TUI_MORE_WORD } from '@taiga-ui/kit/tokens';
import { PolymorpheusOutlet } from '@taiga-ui/polymorpheus';
import { WaResizeObserverService } from '@ng-web-apis/resize-observer';
const TUI_TAB_ACTIVATE = 'tui-tab-activate';
class TuiTab {
constructor() {
this.el = tuiInjectElement();
this.rla = inject(RouterLinkActive, { optional: true });
this.observer = this.rla &&
inject(WaMutationObserverService, { optional: true })?.pipe(filter(() => !!this.rla?.isActive));
this.sub = merge(this.observer || EMPTY, this.rla?.isActiveChange.pipe(filter(Boolean)) || EMPTY, this.el.matches('button')
? tuiTypedFromEvent(this.el, 'click').pipe(
// Delaying execution until after all other click callbacks
switchMap(() => tuiTypedFromEvent(this.el.parentElement, 'click').pipe(take(1))))
: EMPTY)
.pipe(takeUntilDestroyed())
.subscribe(() => this.el.dispatchEvent(new CustomEvent(TUI_TAB_ACTIVATE, { bubbles: true })));
}
ngOnDestroy() {
if (tuiIsFocused(this.el)) {
this.el.blur();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiTab, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.21", type: TuiTab, isStandalone: true, selector: "a[tuiTab]:not([routerLink]), a[tuiTab][routerLink][routerLinkActive], button[tuiTab]", host: { attributes: { "type": "button" } }, hostDirectives: [{ directive: i1.TuiWithIcons }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiTab, decorators: [{
type: Directive,
args: [{
selector: 'a[tuiTab]:not([routerLink]), a[tuiTab][routerLink][routerLinkActive], button[tuiTab]',
hostDirectives: [TuiWithIcons],
host: { type: 'button' },
}]
}] });
const TUI_TABS_DEFAULT_OPTIONS = {
underline: true,
exposeActive: true,
itemsLimit: Infinity,
minMoreWidth: 0,
size: 'l',
};
const [TUI_TABS_OPTIONS, tuiTabsOptionsProvider] = tuiCreateOptions(TUI_TABS_DEFAULT_OPTIONS);
class Styles {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: Styles, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: Styles, isStandalone: true, selector: "ng-component", exportAs: ["tui-tabs-5.7.0"], ngImport: i0, template: '', isInline: true, styles: ["[tuiTab]:where(*[data-tui-version=\"5.7.0\"]){transition-property:color,box-shadow,opacity,background;transition-duration:var(--tui-duration, .3s);transition-timing-function:ease-in-out;-webkit-appearance:none;appearance:none;padding:0;border:0;background:none;font:inherit;line-height:inherit;text-decoration:none;position:relative;display:flex;flex-shrink:0;box-sizing:border-box;justify-content:space-between;line-height:1.5rem;align-items:center;white-space:nowrap;cursor:pointer;outline:none;color:inherit;margin-inline-start:var(--tui-tab-margin, 1.5rem)}[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):disabled{opacity:var(--tui-disabled-opacity);pointer-events:none}[tuiTab]:where(*[data-tui-version=\"5.7.0\"])._active{color:var(--tui-text-primary);box-shadow:none;anchor-name:--tui-tab-active}[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):focus-visible{outline:.125rem solid var(--tui-border-focus);outline-offset:-.125rem}tui-tabs._underline [tuiTab]:where(*[data-tui-version=\"5.7.0\"]):hover:not(._active){box-shadow:inset 0 -.125rem var(--tui-border-normal)}tui-tabs>[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):first-child,tui-tabs>:not(.t-overflown)>[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):first-child{margin-inline-start:0}tui-tabs>[tuiTab]~:not(.t-overflown)>[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):first-child{margin-inline-start:var(--tui-tab-margin, 1.5rem)}[tuiTab]:where(*[data-tui-version=\"5.7.0\"])[tuiIcons]:before{font-size:1rem;margin-inline-end:.5rem}[tuiTab]:where(*[data-tui-version=\"5.7.0\"])[tuiIcons]:after{font-size:1rem;margin-inline-start:.5rem}[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):empty:after,[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):empty:before{margin:.5rem}@media(hover:hover)and (pointer:fine){[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):hover{color:var(--tui-text-primary)}}tui-tabs:where(*[data-tui-version=\"5.7.0\"]){scrollbar-width:none;-ms-overflow-style:none;position:relative;display:flex;flex-shrink:0;font:var(--tui-typography-body-m);color:var(--tui-text-secondary);box-shadow:inset 0 -1px var(--tui-border-normal);overflow:auto;isolation:isolate;anchor-scope:--tui-tab-active}tui-tabs:where(*[data-tui-version=\"5.7.0\"])::-webkit-scrollbar,tui-tabs:where(*[data-tui-version=\"5.7.0\"])::-webkit-scrollbar-thumb{display:none}tui-tabs:where(*[data-tui-version=\"5.7.0\"])[data-size=m]{font:var(--tui-typography-body-s);--tui-tab-margin: 1rem}tui-tabs:where(*[data-tui-version=\"5.7.0\"])[data-size=l]:not([data-vertical]){block-size:var(--tui-height-l)}tui-tabs:where(*[data-tui-version=\"5.7.0\"])[data-size=m]:not([data-vertical]){block-size:var(--tui-height-m)}tui-tabs:where(*[data-tui-version=\"5.7.0\"]):before{transition-property:inline-size,inset-inline-start;transition-duration:var(--tui-duration, .3s);transition-timing-function:ease-in-out;position:absolute;inset-block-end:0;inset-inline-start:var(--t-left);block-size:.125rem;inline-size:var(--t-width);background:var(--t-color);animation:tuiPresent 1ms;contain:strict}@supports (anchor-name: --tui-tab-active){tui-tabs:where(*[data-tui-version=\"5.7.0\"]):before{inset-inline-start:anchor(start);inline-size:anchor-size(inline);position-anchor:--tui-tab-active}}tui-tabs:where(*[data-tui-version=\"5.7.0\"])._underline:before{content:\"\"}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"]){flex-direction:column;box-shadow:inset calc(-1px * var(--tui-inline)) 0 var(--tui-border-normal)}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"]) [tuiTab]{min-block-size:2.75rem;block-size:auto;white-space:normal;margin:0;text-align:start;padding:.25rem 1.25rem}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"]) [tuiTab]:after{transition-property:transform;transition-duration:var(--tui-duration, .3s);transition-timing-function:ease-in-out;content:\"\";position:absolute;display:block;inset-block-start:0;inset-inline-end:0;block-size:100%;inline-size:.125rem;background:var(--tui-background-accent-1);transform:scaleX(0);transform-origin:right;margin:0}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"]) [tuiTab]:hover{box-shadow:inset calc(-.125rem * var(--tui-inline)) 0 var(--tui-border-normal)}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"]) [tuiTab]._active:after{transform:none}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"])[data-size=m] [tuiTab]{min-block-size:2.25rem;font:var(--tui-typography-body-s)}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"])[data-vertical=end]{box-shadow:inset calc(1px * var(--tui-inline)) 0 var(--tui-border-normal)}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"])[data-vertical=end] [tuiTab]{text-align:end}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"])[data-vertical=end] [tuiTab]:after{inset-inline:0 auto;transform-origin:left}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"])[data-vertical=end] [tuiTab]:hover{box-shadow:inset calc(.125rem * var(--tui-inline)) 0 var(--tui-border-normal)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: Styles, decorators: [{
type: Component,
args: [{ template: '', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, exportAs: `tui-tabs-${TUI_VERSION}`, styles: ["[tuiTab]:where(*[data-tui-version=\"5.7.0\"]){transition-property:color,box-shadow,opacity,background;transition-duration:var(--tui-duration, .3s);transition-timing-function:ease-in-out;-webkit-appearance:none;appearance:none;padding:0;border:0;background:none;font:inherit;line-height:inherit;text-decoration:none;position:relative;display:flex;flex-shrink:0;box-sizing:border-box;justify-content:space-between;line-height:1.5rem;align-items:center;white-space:nowrap;cursor:pointer;outline:none;color:inherit;margin-inline-start:var(--tui-tab-margin, 1.5rem)}[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):disabled{opacity:var(--tui-disabled-opacity);pointer-events:none}[tuiTab]:where(*[data-tui-version=\"5.7.0\"])._active{color:var(--tui-text-primary);box-shadow:none;anchor-name:--tui-tab-active}[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):focus-visible{outline:.125rem solid var(--tui-border-focus);outline-offset:-.125rem}tui-tabs._underline [tuiTab]:where(*[data-tui-version=\"5.7.0\"]):hover:not(._active){box-shadow:inset 0 -.125rem var(--tui-border-normal)}tui-tabs>[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):first-child,tui-tabs>:not(.t-overflown)>[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):first-child{margin-inline-start:0}tui-tabs>[tuiTab]~:not(.t-overflown)>[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):first-child{margin-inline-start:var(--tui-tab-margin, 1.5rem)}[tuiTab]:where(*[data-tui-version=\"5.7.0\"])[tuiIcons]:before{font-size:1rem;margin-inline-end:.5rem}[tuiTab]:where(*[data-tui-version=\"5.7.0\"])[tuiIcons]:after{font-size:1rem;margin-inline-start:.5rem}[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):empty:after,[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):empty:before{margin:.5rem}@media(hover:hover)and (pointer:fine){[tuiTab]:where(*[data-tui-version=\"5.7.0\"]):hover{color:var(--tui-text-primary)}}tui-tabs:where(*[data-tui-version=\"5.7.0\"]){scrollbar-width:none;-ms-overflow-style:none;position:relative;display:flex;flex-shrink:0;font:var(--tui-typography-body-m);color:var(--tui-text-secondary);box-shadow:inset 0 -1px var(--tui-border-normal);overflow:auto;isolation:isolate;anchor-scope:--tui-tab-active}tui-tabs:where(*[data-tui-version=\"5.7.0\"])::-webkit-scrollbar,tui-tabs:where(*[data-tui-version=\"5.7.0\"])::-webkit-scrollbar-thumb{display:none}tui-tabs:where(*[data-tui-version=\"5.7.0\"])[data-size=m]{font:var(--tui-typography-body-s);--tui-tab-margin: 1rem}tui-tabs:where(*[data-tui-version=\"5.7.0\"])[data-size=l]:not([data-vertical]){block-size:var(--tui-height-l)}tui-tabs:where(*[data-tui-version=\"5.7.0\"])[data-size=m]:not([data-vertical]){block-size:var(--tui-height-m)}tui-tabs:where(*[data-tui-version=\"5.7.0\"]):before{transition-property:inline-size,inset-inline-start;transition-duration:var(--tui-duration, .3s);transition-timing-function:ease-in-out;position:absolute;inset-block-end:0;inset-inline-start:var(--t-left);block-size:.125rem;inline-size:var(--t-width);background:var(--t-color);animation:tuiPresent 1ms;contain:strict}@supports (anchor-name: --tui-tab-active){tui-tabs:where(*[data-tui-version=\"5.7.0\"]):before{inset-inline-start:anchor(start);inline-size:anchor-size(inline);position-anchor:--tui-tab-active}}tui-tabs:where(*[data-tui-version=\"5.7.0\"])._underline:before{content:\"\"}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"]){flex-direction:column;box-shadow:inset calc(-1px * var(--tui-inline)) 0 var(--tui-border-normal)}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"]) [tuiTab]{min-block-size:2.75rem;block-size:auto;white-space:normal;margin:0;text-align:start;padding:.25rem 1.25rem}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"]) [tuiTab]:after{transition-property:transform;transition-duration:var(--tui-duration, .3s);transition-timing-function:ease-in-out;content:\"\";position:absolute;display:block;inset-block-start:0;inset-inline-end:0;block-size:100%;inline-size:.125rem;background:var(--tui-background-accent-1);transform:scaleX(0);transform-origin:right;margin:0}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"]) [tuiTab]:hover{box-shadow:inset calc(-.125rem * var(--tui-inline)) 0 var(--tui-border-normal)}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"]) [tuiTab]._active:after{transform:none}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"])[data-size=m] [tuiTab]{min-block-size:2.25rem;font:var(--tui-typography-body-s)}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"])[data-vertical=end]{box-shadow:inset calc(1px * var(--tui-inline)) 0 var(--tui-border-normal)}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"])[data-vertical=end] [tuiTab]{text-align:end}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"])[data-vertical=end] [tuiTab]:after{inset-inline:0 auto;transform-origin:left}tui-tabs[data-vertical]:where(*[data-tui-version=\"5.7.0\"])[data-vertical=end] [tuiTab]:hover{box-shadow:inset calc(.125rem * var(--tui-inline)) 0 var(--tui-border-normal)}\n"] }]
}] });
class TuiTabsDirective {
constructor() {
this.el = tuiInjectElement();
this.injector = inject(INJECTOR);
this.nothing = tuiWithStyles(Styles);
this.size = input(inject(TUI_TABS_OPTIONS).size);
this.activeItemIndex = model(0);
}
get tabs() {
return Array.from(this.el.querySelectorAll('[tuiTab]'));
}
get activeElement() {
return this.tabs[this.activeItemIndex()] || null;
}
moveFocus(current, step) {
const { tabs } = this;
tuiMoveFocus(tabs.indexOf(current), tabs, step);
}
ngAfterViewChecked() {
afterNextRender(() => {
this.markTabAsActive();
}, { injector: this.injector });
}
onActivate(element) {
this.activeItemIndex.set(this.tabs.findIndex((tab) => tab === element));
}
markTabAsActive() {
const { tabs, activeElement } = this;
tabs.forEach((nativeElement) => {
const active = nativeElement === activeElement;
nativeElement.classList.toggle('_active', active);
nativeElement.setAttribute('tabIndex', active ? '0' : '-1');
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiTabsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.21", type: TuiTabsDirective, isStandalone: true, inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, activeItemIndex: { classPropertyName: "activeItemIndex", publicName: "activeItemIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeItemIndex: "activeItemIndexChange" }, host: { attributes: { "data-tui-version": "5.7.0" }, listeners: { "tui-tab-activate.stop": "onActivate($event.target)" }, properties: { "attr.data-size": "size()" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiTabsDirective, decorators: [{
type: Directive,
args: [{
host: {
'data-tui-version': TUI_VERSION,
'[attr.data-size]': 'size()',
[`(${TUI_TAB_ACTIVATE}.stop)`]: 'onActivate($event.target)',
},
}]
}] });
class TuiTabsHorizontal {
constructor() {
this.el = tuiInjectElement();
this.options = inject(TUI_TABS_OPTIONS);
this.tabs = inject(TuiTabsDirective);
this.sub = inject(WaMutationObserverService, { self: true })
.pipe(tuiZonefree(), takeUntilDestroyed())
.subscribe(() => this.refresh());
this.underline = input(this.options.underline);
effect(() => {
const index = this.tabs.activeItemIndex();
const element = this.tabs.tabs[index];
if (!element) {
return;
}
const { offsetLeft, offsetWidth } = element;
if (offsetLeft < this.el.scrollLeft) {
this.el.scrollLeft = offsetLeft;
}
if (offsetLeft + offsetWidth > this.el.scrollLeft + this.el.offsetWidth) {
this.el.scrollLeft = offsetLeft + offsetWidth - this.el.offsetWidth;
}
});
}
ngAfterViewChecked() {
this.refresh();
}
onKeyDownArrow(current, step) {
this.tabs.moveFocus(current, step);
}
// TODO: Remove when anchor positioning will be available in all modern browsers: https://caniuse.com/css-anchor-positioning
refresh() {
if ('anchorName' in this.el.style) {
return;
}
const { activeElement } = this.tabs;
if (activeElement && !activeElement.isConnected) {
return;
}
const { offsetLeft = 0, offsetWidth = 0 } = activeElement || {};
this.el.style.setProperty('--t-left', tuiPx(offsetLeft));
this.el.style.setProperty('--t-width', tuiPx(offsetWidth));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiTabsHorizontal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.21", type: TuiTabsHorizontal, isStandalone: true, selector: "tui-tabs:not([vertical])", inputs: { underline: { classPropertyName: "underline", publicName: "underline", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "animationend": "refresh()", "keydown.arrowLeft.prevent": "onKeyDownArrow($event.target, -1)", "keydown.arrowRight.prevent": "onKeyDownArrow($event.target, 1)" }, properties: { "class._underline": "underline()", "style.--t-color": "underline() === true ? 'var(--tui-background-accent-1)' : underline()" } }, providers: [
WaMutationObserverService,
{
provide: WA_MUTATION_OBSERVER_INIT,
useValue: {
childList: true,
characterData: true,
subtree: true,
},
},
], hostDirectives: [{ directive: TuiTabsDirective, inputs: ["activeItemIndex", "activeItemIndex", "size", "size"], outputs: ["activeItemIndexChange", "activeItemIndexChange"] }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiTabsHorizontal, decorators: [{
type: Directive,
args: [{
selector: 'tui-tabs:not([vertical])',
providers: [
WaMutationObserverService,
{
provide: WA_MUTATION_OBSERVER_INIT,
useValue: {
childList: true,
characterData: true,
subtree: true,
},
},
],
hostDirectives: [
{
directive: TuiTabsDirective,
inputs: ['activeItemIndex', 'size'],
outputs: ['activeItemIndexChange'],
},
],
host: {
'[class._underline]': 'underline()',
'[style.--t-color]': "underline() === true ? 'var(--tui-background-accent-1)' : underline()",
'(animationend)': 'refresh()',
'(keydown.arrowLeft.prevent)': 'onKeyDownArrow($event.target, -1)',
'(keydown.arrowRight.prevent)': 'onKeyDownArrow($event.target, 1)',
},
}]
}], ctorParameters: () => [] });
class TuiTabsVertical {
constructor() {
this.tabs = inject(TuiTabsDirective);
this.vertical = input('start');
}
onKeyDownArrow(current, step) {
this.tabs.moveFocus(current, step);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiTabsVertical, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.21", type: TuiTabsVertical, isStandalone: true, selector: "tui-tabs[vertical]", inputs: { vertical: { classPropertyName: "vertical", publicName: "vertical", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown.arrowDown.prevent": "onKeyDownArrow($event.target, 1)", "keydown.arrowUp.prevent": "onKeyDownArrow($event.target, -1)" }, properties: { "attr.data-vertical": "vertical()" } }, hostDirectives: [{ directive: TuiTabsDirective, inputs: ["activeItemIndex", "activeItemIndex", "size", "size"], outputs: ["activeItemIndexChange", "activeItemIndexChange"] }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiTabsVertical, decorators: [{
type: Directive,
args: [{
selector: 'tui-tabs[vertical]',
hostDirectives: [
{
directive: TuiTabsDirective,
inputs: ['activeItemIndex', 'size'],
outputs: ['activeItemIndexChange'],
},
],
host: {
'[attr.data-vertical]': 'vertical()',
'(keydown.arrowDown.prevent)': 'onKeyDownArrow($event.target, 1)',
'(keydown.arrowUp.prevent)': 'onKeyDownArrow($event.target, -1)',
},
}]
}] });
const TUI_TABS_REFRESH = new InjectionToken(ngDevMode ? 'TUI_TABS_REFRESH' : '');
const TUI_TABS_PROVIDERS = [
WaResizeObserverService,
WaMutationObserverService,
tuiDropdownOptionsProvider({ align: 'end' }),
{
provide: WA_MUTATION_OBSERVER_INIT,
useValue: {
childList: true,
subtree: true,
characterData: true,
},
},
{
provide: TUI_TABS_REFRESH,
deps: [
WaResizeObserverService,
WaMutationObserverService,
DOCUMENT,
ElementRef,
ChangeDetectorRef,
],
useFactory: (resize$, mutations$, { body }, { nativeElement }, cdr) => merge(resize$, mutations$.pipe(tap(() => cdr.detectChanges()))).pipe(
// Ignoring cases when host is detached from DOM
filter(() => body.contains(nativeElement)), debounceTime(0), startWith(null), takeUntilDestroyed()),
},
];
class TuiTabsWithMore {
constructor() {
this.moreButton = viewChild(TuiTab, { read: ElementRef });
this.dir = viewChild(TuiTabsHorizontal, { read: ElementRef });
this.options = inject(TUI_TABS_OPTIONS);
this.refresh$ = inject(TUI_TABS_REFRESH);
this.el = tuiInjectElement();
this.cdr = inject(ChangeDetectorRef);
this.maxIndex = Infinity;
this.items = contentChildren(TuiItem, { read: TemplateRef });
this.moreWord = inject(TUI_MORE_WORD);
this.sync = effect(() => {
this.activeItemIndex();
this.maxIndex = this.getMaxIndex();
this.open = false;
});
this.open = false;
this.activeItemIndex = model(0);
this.size = input(this.options.size);
this.underline = input(this.options.underline);
this.itemsLimit = input(this.options.itemsLimit);
this.moreContent = input();
this.dropdownContent = input();
}
get lastVisibleIndex() {
if (this.itemsLimit() + 1 >= this.items().length) {
return this.maxIndex;
}
const offset = this.itemsLimit() - 1 > this.activeItemIndex() || !this.options.exposeActive
? 1
: 2;
return Math.min(this.itemsLimit() - offset, this.maxIndex);
}
isOverflown(index) {
return index !== this.activeItemIndex() || !this.options.exposeActive;
}
shouldShow(index) {
return index > this.lastVisibleIndex && this.isOverflown(index);
}
ngAfterViewInit() {
this.refresh$
.pipe(map(() => this.getMaxIndex()), tap(() => this.refresh()), filter((maxIndex) => this.maxIndex !== maxIndex))
.subscribe((maxIndex) => {
this.maxIndex = maxIndex;
this.cdr.detectChanges();
});
}
ngAfterViewChecked() {
this.refresh();
}
// TODO: Improve performance
get tabs() {
return Array.from(this.el.querySelectorAll('[tuiTab]'));
}
get activeElement() {
return this.tabs.find((tab) => tab.classList.contains('_active')) ?? null;
}
get isMoreAlone() {
return this.lastVisibleIndex < 0 && !this.options.exposeActive;
}
get isMoreVisible() {
return this.lastVisibleIndex < this.items().length - 1;
}
get isMoreFocusable() {
return tuiIsFocused(this.moreButton()?.nativeElement);
}
get isMoreActive() {
return (this.open ||
(!this.options.exposeActive && this.lastVisibleIndex < this.activeItemIndex()));
}
onClick(index) {
this.open = false;
this.focusMore();
this.activeItemIndex.set(index);
}
onArrowRight(event) {
if (tuiIsElement(event.target) && tuiIsFocused(event.target)) {
this.focusMore();
}
}
onArrowLeft() {
const { tabs } = this;
let index = tabs.length - 2;
while (index >= 0) {
tabs[index]?.focus();
if (tuiIsFocused(tabs[index])) {
return;
}
index--;
}
}
onWrapperArrow(event, wrapper, previous) {
const button = event.target;
const target = tuiGetClosestFocusable({ initial: button, root: wrapper, previous });
if (target) {
target.focus();
}
}
get margin() {
return this.size() === 'l' ? 24 : 16;
}
focusMore() {
this.moreButton()?.nativeElement.focus();
}
getMaxIndex() {
const { tabs, activeItemIndex, margin } = this;
if (tabs.length < 2) {
return 0;
}
const { exposeActive, minMoreWidth } = this.options;
const { clientWidth } = this.el;
const active = tabs[activeItemIndex()];
const activeWidth = active?.scrollWidth ?? 0;
const moreWidth = Math.max(tabs[tabs.length - 1]?.scrollWidth ?? 0, minMoreWidth);
let maxIndex = tabs.length - 2;
let total = tabs.reduce((acc, { scrollWidth }) => acc + scrollWidth, 0) +
maxIndex * margin -
(tabs[tabs.length - 1]?.scrollWidth ?? 0);
if (Number.isNaN(total) || total <= clientWidth) {
return Infinity;
}
while (maxIndex) {
total -= (tabs[maxIndex]?.scrollWidth ?? 0) + margin;
maxIndex--;
const activeDisplaced = exposeActive && activeItemIndex() > maxIndex;
const activeOffset = activeDisplaced ? activeWidth + margin : 0;
const currentWidth = total + activeOffset + moreWidth + margin;
// Needed for different rounding of visible and hidden elements scrollWidth
const safetyOffset = tuiToInt(this.maxIndex === maxIndex - 1);
if (currentWidth + safetyOffset < clientWidth) {
return maxIndex;
}
}
return -1;
}
// TODO: Remove when anchor positioning will be available in all modern browsers: https://caniuse.com/css-anchor-positioning
refresh() {
if ('anchorName' in this.el.style) {
return;
}
const { offsetLeft = 0, offsetWidth = 0 } = this.activeElement || {};
this.dir()?.nativeElement.style.setProperty('--t-left', tuiPx(offsetLeft));
this.dir()?.nativeElement.style.setProperty('--t-width', tuiPx(offsetWidth));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiTabsWithMore, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: TuiTabsWithMore, isStandalone: true, selector: "tui-tabs-with-more", inputs: { activeItemIndex: { classPropertyName: "activeItemIndex", publicName: "activeItemIndex", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, underline: { classPropertyName: "underline", publicName: "underline", isSignal: true, isRequired: false, transformFunction: null }, itemsLimit: { classPropertyName: "itemsLimit", publicName: "itemsLimit", isSignal: true, isRequired: false, transformFunction: null }, moreContent: { classPropertyName: "moreContent", publicName: "moreContent", isSignal: true, isRequired: false, transformFunction: null }, dropdownContent: { classPropertyName: "dropdownContent", publicName: "dropdownContent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeItemIndex: "activeItemIndexChange" }, host: { properties: { "attr.data-size": "size()" } }, providers: TUI_TABS_PROVIDERS, queries: [{ propertyName: "items", predicate: TuiItem, read: TemplateRef, isSignal: true }], viewQueries: [{ propertyName: "moreButton", first: true, predicate: TuiTab, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "dir", first: true, predicate: TuiTabsHorizontal, descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<tui-tabs\n class=\"t-tabs\"\n [size]=\"size()\"\n [underline]=\"underline()\"\n [(activeItemIndex)]=\"activeItemIndex\"\n (keydown.arrowRight)=\"onArrowRight($event)\"\n>\n @for (item of items(); track item) {\n @if ($index <= lastVisibleIndex) {\n <ng-container [ngTemplateOutlet]=\"item\" />\n } @else {\n <div\n class=\"t-flex\"\n [class.t-overflown]=\"isOverflown($index)\"\n >\n <ng-container [ngTemplateOutlet]=\"item\" />\n </div>\n }\n }\n</tui-tabs>\n\n@if (moreContent()) {\n <button\n tuiTab\n type=\"button\"\n class=\"t-more\"\n [class._active]=\"isMoreActive\"\n [class.t-no-margin]=\"isMoreAlone\"\n [class.t-overflown]=\"!isMoreVisible\"\n [tabIndex]=\"isMoreFocusable ? 0 : -1\"\n [tuiDropdown]=\"dropdownContent() || dropdown\"\n [(tuiDropdownOpen)]=\"open\"\n (keydown.arrowLeft.prevent)=\"onArrowLeft()\"\n >\n <ng-container *polymorpheusOutlet=\"moreContent() as text\">\n {{ text }}\n </ng-container>\n </button>\n} @else {\n <button\n tuiChevron\n tuiTab\n type=\"button\"\n class=\"t-more\"\n [class._active]=\"isMoreActive\"\n [class.t-no-margin]=\"isMoreAlone\"\n [class.t-overflown]=\"!isMoreVisible\"\n [tabIndex]=\"isMoreFocusable ? 0 : -1\"\n [tuiDropdown]=\"dropdownContent() || dropdown\"\n [(tuiDropdownOpen)]=\"open\"\n (keydown.arrowLeft.prevent)=\"onArrowLeft()\"\n >\n {{ moreWord() }}\n </button>\n}\n<ng-template #dropdown>\n <div\n #element\n class=\"t-dropdown\"\n [attr.data-size]=\"size()\"\n (keydown.arrowDown.prevent)=\"onWrapperArrow($event, element, false)\"\n (keydown.arrowUp.prevent)=\"onWrapperArrow($event, element, true)\"\n >\n @for (item of items(); track item) {\n <div\n class=\"t-dropdown-item\"\n (tui-tab-activate)=\"onClick($index)\"\n >\n @if (shouldShow($index)) {\n <ng-container *polymorpheusOutlet=\"item\" />\n }\n </div>\n }\n </div>\n</ng-template>\n\n<ng-content />\n", styles: [":host{position:relative;display:flex;flex-shrink:0;font:var(--tui-typography-body-m);box-sizing:border-box;color:var(--tui-text-secondary);box-shadow:inset 0 -1px var(--tui-border-normal);overflow:hidden}:host[data-size=m]{font:var(--tui-typography-body-s)}.t-tabs{block-size:inherit;font:inherit;overflow:visible;box-shadow:none;color:inherit}.t-flex{display:flex}.t-overflown{margin:0;inline-size:0;max-inline-size:0;overflow:hidden;visibility:hidden}.t-icon{transition-property:transform;transition-duration:var(--tui-duration, .3s);transition-timing-function:ease-in-out;margin-inline-end:-.25rem;vertical-align:bottom}.t-icon_rotated{transform:rotate(180deg)}.t-dropdown{padding:.25rem 0}.t-dropdown ::ng-deep *[tuiTab]{inline-size:calc(100% - .75rem);block-size:2.75rem;justify-content:flex-start;margin:.125rem .375rem;padding:0 .625rem;border-radius:var(--tui-radius-s);font:var(--tui-typography-body-m);line-height:1.5rem;color:var(--tui-text-primary)}.t-dropdown ::ng-deep *[tuiTab]:before{display:none}.t-dropdown ::ng-deep *[tuiTab]:hover,.t-dropdown ::ng-deep *[tuiTab]:focus,.t-dropdown ::ng-deep *[tuiTab]._active{box-shadow:none;outline:none;background:var(--tui-background-neutral-1)}.t-dropdown[data-size=m] ::ng-deep *[tuiTab]{block-size:2.25rem;font:var(--tui-typography-body-s)}.t-dropdown-item{display:flex;flex-direction:column}.t-no-margin{margin-inline-start:0}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: PolymorpheusOutlet, selector: "[polymorpheusOutlet]", inputs: ["polymorpheusOutlet", "polymorpheusOutletContext"] }, { kind: "directive", type: TuiChevron, selector: "[tuiChevron]", inputs: ["tuiChevron"] }, { kind: "directive", type: i1$1.TuiDropdownDirective, selector: "[tuiDropdown]:not(ng-container):not(ng-template)", inputs: ["tuiDropdown"], exportAs: ["tuiDropdown"] }, { kind: "directive", type: i1$1.TuiDropdownOpen, selector: "[tuiDropdown][tuiDropdownAuto],[tuiDropdown][tuiDropdownOpen],[tuiDropdown][tuiDropdownOpenChange]", inputs: ["tuiDropdownEnabled", "tuiDropdownOpen"], outputs: ["tuiDropdownOpenChange"] }, { kind: "directive", type: TuiTab, selector: "a[tuiTab]:not([routerLink]), a[tuiTab][routerLink][routerLinkActive], button[tuiTab]" }, { kind: "directive", type: TuiTabsHorizontal, selector: "tui-tabs:not([vertical])", inputs: ["underline"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TuiTabsWithMore, decorators: [{
type: Component,
args: [{ selector: 'tui-tabs-with-more', imports: [
NgTemplateOutlet,
PolymorpheusOutlet,
TuiChevron,
TuiDropdown,
TuiTab,
TuiTabsHorizontal,
], changeDetection: ChangeDetectionStrategy.OnPush, providers: TUI_TABS_PROVIDERS, host: { '[attr.data-size]': 'size()' }, template: "<tui-tabs\n class=\"t-tabs\"\n [size]=\"size()\"\n [underline]=\"underline()\"\n [(activeItemIndex)]=\"activeItemIndex\"\n (keydown.arrowRight)=\"onArrowRight($event)\"\n>\n @for (item of items(); track item) {\n @if ($index <= lastVisibleIndex) {\n <ng-container [ngTemplateOutlet]=\"item\" />\n } @else {\n <div\n class=\"t-flex\"\n [class.t-overflown]=\"isOverflown($index)\"\n >\n <ng-container [ngTemplateOutlet]=\"item\" />\n </div>\n }\n }\n</tui-tabs>\n\n@if (moreContent()) {\n <button\n tuiTab\n type=\"button\"\n class=\"t-more\"\n [class._active]=\"isMoreActive\"\n [class.t-no-margin]=\"isMoreAlone\"\n [class.t-overflown]=\"!isMoreVisible\"\n [tabIndex]=\"isMoreFocusable ? 0 : -1\"\n [tuiDropdown]=\"dropdownContent() || dropdown\"\n [(tuiDropdownOpen)]=\"open\"\n (keydown.arrowLeft.prevent)=\"onArrowLeft()\"\n >\n <ng-container *polymorpheusOutlet=\"moreContent() as text\">\n {{ text }}\n </ng-container>\n </button>\n} @else {\n <button\n tuiChevron\n tuiTab\n type=\"button\"\n class=\"t-more\"\n [class._active]=\"isMoreActive\"\n [class.t-no-margin]=\"isMoreAlone\"\n [class.t-overflown]=\"!isMoreVisible\"\n [tabIndex]=\"isMoreFocusable ? 0 : -1\"\n [tuiDropdown]=\"dropdownContent() || dropdown\"\n [(tuiDropdownOpen)]=\"open\"\n (keydown.arrowLeft.prevent)=\"onArrowLeft()\"\n >\n {{ moreWord() }}\n </button>\n}\n<ng-template #dropdown>\n <div\n #element\n class=\"t-dropdown\"\n [attr.data-size]=\"size()\"\n (keydown.arrowDown.prevent)=\"onWrapperArrow($event, element, false)\"\n (keydown.arrowUp.prevent)=\"onWrapperArrow($event, element, true)\"\n >\n @for (item of items(); track item) {\n <div\n class=\"t-dropdown-item\"\n (tui-tab-activate)=\"onClick($index)\"\n >\n @if (shouldShow($index)) {\n <ng-container *polymorpheusOutlet=\"item\" />\n }\n </div>\n }\n </div>\n</ng-template>\n\n<ng-content />\n", styles: [":host{position:relative;display:flex;flex-shrink:0;font:var(--tui-typography-body-m);box-sizing:border-box;color:var(--tui-text-secondary);box-shadow:inset 0 -1px var(--tui-border-normal);overflow:hidden}:host[data-size=m]{font:var(--tui-typography-body-s)}.t-tabs{block-size:inherit;font:inherit;overflow:visible;box-shadow:none;color:inherit}.t-flex{display:flex}.t-overflown{margin:0;inline-size:0;max-inline-size:0;overflow:hidden;visibility:hidden}.t-icon{transition-property:transform;transition-duration:var(--tui-duration, .3s);transition-timing-function:ease-in-out;margin-inline-end:-.25rem;vertical-align:bottom}.t-icon_rotated{transform:rotate(180deg)}.t-dropdown{padding:.25rem 0}.t-dropdown ::ng-deep *[tuiTab]{inline-size:calc(100% - .75rem);block-size:2.75rem;justify-content:flex-start;margin:.125rem .375rem;padding:0 .625rem;border-radius:var(--tui-radius-s);font:var(--tui-typography-body-m);line-height:1.5rem;color:var(--tui-text-primary)}.t-dropdown ::ng-deep *[tuiTab]:before{display:none}.t-dropdown ::ng-deep *[tuiTab]:hover,.t-dropdown ::ng-deep *[tuiTab]:focus,.t-dropdown ::ng-deep *[tuiTab]._active{box-shadow:none;outline:none;background:var(--tui-background-neutral-1)}.t-dropdown[data-size=m] ::ng-deep *[tuiTab]{block-size:2.25rem;font:var(--tui-typography-body-s)}.t-dropdown-item{display:flex;flex-direction:column}.t-no-margin{margin-inline-start:0}\n"] }]
}] });
const TuiTabs = [
TuiItem,
TuiTab,
TuiTabsDirective,
TuiTabsHorizontal,
TuiTabsVertical,
TuiTabsWithMore,
];
/**
* Generated bundle index. Do not edit.
*/
export { TUI_TABS_DEFAULT_OPTIONS, TUI_TABS_OPTIONS, TUI_TABS_PROVIDERS, TUI_TABS_REFRESH, TUI_TAB_ACTIVATE, TuiTab, TuiTabs, TuiTabsDirective, TuiTabsHorizontal, TuiTabsVertical, TuiTabsWithMore, tuiTabsOptionsProvider };
//# sourceMappingURL=taiga-ui-kit-components-tabs.mjs.map