All files / content-action-bar si-content-action-bar.component.ts

88.23% Statements 45/51
83.33% Branches 10/12
78.57% Functions 11/14
91.11% Lines 41/45

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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181                                                                                                      1x       39x       39x           39x           39x                 39x                         39x           39x   39x       39x       39x 4x 4x 4x 4x 4x 1x 3x 2x 1x 1x   4x 4x         4x   39x 1x 1x           1x   39x 39x     39x 39x     39x 39x 39x           1x 1x                     208x       9x 9x     9x          
/**
 * Copyright (c) Siemens 2016 - 2025
 * SPDX-License-Identifier: MIT
 */
import { CdkMenuBar, CdkMenuModule } from '@angular/cdk/menu';
import {
  AfterViewInit,
  booleanAttribute,
  Component,
  computed,
  ElementRef,
  inject,
  input,
  linkedSignal,
  viewChild
} from '@angular/core';
import { RouterLink } from '@angular/router';
import { SiAutoCollapsableListModule } from '@siemens/element-ng/auto-collapsable-list';
import { MenuItem as MenuItemLegacy } from '@siemens/element-ng/common';
import { addIcons, elementCancel, elementOptionsVertical } from '@siemens/element-ng/icon';
import { SiLinkModule } from '@siemens/element-ng/link';
import {
  MenuItem,
  MenuItemAction,
  MenuItemCheckbox,
  MenuItemRadio,
  SiMenuActionService,
  SiMenuModule
} from '@siemens/element-ng/menu';
import { SiTranslatePipe, t } from '@siemens/element-translate-ng/translate';
 
import { SiContentActionBarToggleComponent } from './si-content-action-bar-toggle.component';
import { ContentActionBarMainItem, ViewType } from './si-content-action-bar.model';
 
@Component({
  selector: 'si-content-action-bar',
  imports: [
    SiMenuModule,
    CdkMenuModule,
    SiAutoCollapsableListModule,
    SiTranslatePipe,
    SiLinkModule,
    SiContentActionBarToggleComponent,
    RouterLink
  ],
  templateUrl: './si-content-action-bar.component.html',
  styleUrl: './si-content-action-bar.component.scss',
  host: {
    '[class]': 'viewType()'
  }
})
export class SiContentActionBarComponent implements AfterViewInit {
  /**
   * List of primary actions. Supports up to **4** actions and omits additional ones.
   */
  readonly primaryActions = input<readonly (MenuItemLegacy | ContentActionBarMainItem)[]>();
  /**
   * List of secondary actions.
   */
  readonly secondaryActions = input<readonly (MenuItemLegacy | MenuItem)[]>();
  /**
   * A param that will be passed to the `action` in the primary/secondary actions.
   * This allows to re-use the same primary/secondary action arrays across rows
   * in a table.
   */
  readonly actionParam = input<any>();
  /**
   * Selection of view type as 'collapsible', 'expanded' or 'mobile'.
   *
   * @defaultValue 'expanded'
   */
  readonly viewType = input<ViewType>('expanded');
  /**
   * Toggle icon aria-label, required for a11y
   *
   * @defaultValue
   * ```
   * t(() => $localize`:@@SI_CONTENT_ACTION_BAR.TOGGLE:Toggle`)
   * ```
   */
  readonly toggleItemLabel = input(t(() => $localize`:@@SI_CONTENT_ACTION_BAR.TOGGLE:Toggle`));
  /**
   * Option to remove all icons from dropdown menus of the content action bar.
   *
   * Some apps provide only few actions with icons, located in the set of primary actions.
   * The icons are visible in the `collapsible` and `expanded` view type. On reduced space,
   * primary actions are relocated in the same dropdown menu as the secondary actions. The
   * dropdown menu can look unbalanced, if a large number of secondary actions without
   * icons are presented with few actions with icons. This option balances the look and feel
   * by removing all icons from actions in the dropdown menu.
   *
   * @defaultValue false
   */
  readonly preventIconsInDropdownMenus = input(false, { transform: booleanAttribute });
  /**
   * Disables the whole content-action-bar.
   *
   * @defaultValue false
   */
  readonly disabled = input(false, { transform: booleanAttribute });
 
  private readonly expandElement = viewChild<
    SiContentActionBarToggleComponent,
    ElementRef<HTMLElement>
  >('expandElement', { read: ElementRef });
  private readonly menuBarElement = viewChild<CdkMenuBar, ElementRef<HTMLDivElement>>(CdkMenuBar, {
    read: ElementRef
  });
 
  protected readonly mobileActions = computed(() => {
    const primaryActions = this.primaryActions();
    const secondaryActions = this.secondaryActions();
    const preventIcons = this.preventIconsInDropdownMenus();
    let actions: readonly (MenuItemLegacy | MenuItem)[] = [];
    if (primaryActions?.length && secondaryActions?.length) {
      actions = [...primaryActions, { title: '-' }, ...secondaryActions];
    } else if (primaryActions?.length) {
      actions = primaryActions;
    } else if (secondaryActions?.length) {
      actions = secondaryActions;
    }
    if (preventIcons) {
      actions = actions.map(action => ({
        ...action,
        icon: undefined
      }));
    }
    return actions;
  });
  protected readonly secondaryActionsInternal = computed(() => {
    let secondaryActions = this.secondaryActions();
    Iif (this.preventIconsInDropdownMenus()) {
      secondaryActions = secondaryActions?.map(action => ({
        ...action,
        icon: undefined
      }));
    }
    return secondaryActions;
  });
  protected readonly icons = addIcons({ elementCancel, elementOptionsVertical });
  protected readonly expanded = linkedSignal(() => this.viewType() === 'expanded');
  protected parentElement?: HTMLElement | null;
 
  private elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
  private menuActionService = inject(SiMenuActionService, { optional: true });
 
  ngAfterViewInit(): void {
    setTimeout(() => {
      if (this.parentElement !== this.elementRef.nativeElement.parentElement) {
        this.parentElement = this.elementRef.nativeElement.parentElement;
      }
    });
  }
 
  protected expand(): void {
    this.expanded.set(true);
    setTimeout(() => this.menuBarElement()?.nativeElement.focus());
  }
 
  protected collapse(): void {
    this.expanded.set(false);
    setTimeout(() => this.expandElement()?.nativeElement.focus());
  }
 
  protected isNewItemStyle(
    item: MenuItemLegacy | ContentActionBarMainItem
  ): item is ContentActionBarMainItem {
    return 'label' in item;
  }
 
  protected runAction(item: MenuItemAction | MenuItemRadio | MenuItemCheckbox): void {
    if (typeof item.action === 'function') {
      item.action(this.actionParam(), item as any); // typescript cannot level down the item type properly
    }
 
    Iif (typeof item.action === 'string') {
      this.menuActionService?.actionTriggered(item, this.actionParam());
    }
  }
}