All files / tabs si-tabset.component.ts

100% Statements 20/20
85.71% Branches 6/7
100% Functions 6/6
100% Lines 17/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103                                                                                                      1x   58x     17x     17x         17x     10x                   2x   2x 2x 2x 2x 1x   2x 1x   2x             18x       3x      
/**
 * Copyright (c) Siemens 2016 - 2025
 * SPDX-License-Identifier: MIT
 */
import { FocusKeyManager } from '@angular/cdk/a11y';
import { CdkMenuTrigger } from '@angular/cdk/menu';
import { NgTemplateOutlet } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  contentChildren,
  inject,
  INJECTOR,
  signal
} from '@angular/core';
import { RouterLink } from '@angular/router';
import { isRTL } from '@siemens/element-ng/common';
import { SiMenuDirective, SiMenuItemComponent } from '@siemens/element-ng/menu';
import { SiResizeObserverModule } from '@siemens/element-ng/resize-observer';
 
import { SiTabBadgeComponent } from './si-tab-badge.component';
import { SiTabBaseDirective } from './si-tab-base.directive';
import { SiTabLinkComponent } from './si-tab-link.component';
import { SI_TABSET } from './si-tabs-tokens';
 
/**
 * A component to group multiple tabs together.
 * Can either be used with {@link SiTabLinkComponent} or {@link SiTabComponent} components.
 */
@Component({
  selector: 'si-tabset',
  imports: [
    SiMenuDirective,
    SiMenuItemComponent,
    CdkMenuTrigger,
    NgTemplateOutlet,
    SiResizeObserverModule,
    RouterLink,
    SiTabBadgeComponent
  ],
  templateUrl: './si-tabset.component.html',
  styleUrl: './si-tabset.component.scss',
  providers: [
    {
      provide: SI_TABSET,
      useExisting: SiTabsetComponent
    }
  ],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SiTabsetComponent {
  /** @internal */
  readonly activeTab = computed(() => this.tabPanels().find(tab => tab.active()));
 
  /** @internal */
  readonly tabPanels = contentChildren(SiTabBaseDirective);
 
  /** @internal */
  focusKeyManager = new FocusKeyManager(this.tabPanels, inject(INJECTOR))
    .withHorizontalOrientation(isRTL() ? 'rtl' : 'ltr')
    .withWrap(true);
 
  /** @internal */
  protected readonly showMenuButton = signal(false);
 
  protected tabIsLink(tab: unknown): tab is SiTabLinkComponent {
    return tab instanceof SiTabLinkComponent;
  }
 
  /** @internal */
  removedTabByUser(index: number, active?: boolean): void {
    // The tab was already removed from the tabPanels list when this function is called.
    // We need to:
    // - focus another tab if the closed one was focused
    // - activate another tab if the closed one was active
    // If the closed tab was not focussed, there is no need to restore the focus as it could only be closed by mouse.
    for (let i = 0; i < this.tabPanels().length; i++) {
      // Get the actual index using modulo to wrap around
      const checkIndex = (index + i) % this.tabPanels().length;
      const checkTab = this.tabPanels()[checkIndex];
      if (!checkTab.disabledTab()) {
        if (this.focusKeyManager.activeItemIndex === index) {
          this.focusKeyManager.setActiveItem(checkIndex);
        }
        if (active) {
          checkTab.selectTab(true);
        }
        return;
      }
    }
  }
 
  protected resizeContainer(width: number, scrollWidth: number): void {
    // 48px is the width of the menu button.
    this.showMenuButton.set(scrollWidth > width + (this.showMenuButton() ? 48 : 0));
  }
 
  protected keydown(event: KeyboardEvent): void {
    this.focusKeyManager.onKeydown(event);
  }
}