UNPKG

@taiga-ui/kit

Version:

Taiga UI Angular main components kit

442 lines (431 loc) 41.4 kB
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