All files / select/options si-select-options-strategy.base.ts

100% Statements 29/29
100% Branches 16/16
100% Functions 9/9
100% Lines 27/27

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                          1x                         132x 132x             132x 43x 43x 6x 6x 27x   6x 25x 8x 17x 2x 1x   1x   1x 1x         25x     37x       132x 159x 159x 545x   586x       192x       21x      
/**
 * Copyright (c) Siemens 2016 - 2025
 * SPDX-License-Identifier: MIT
 */
import { computed, Directive, InputSignal, Signal, signal } from '@angular/core';
 
import { SelectItem, SelectOption } from '../si-select.types';
import { SiSelectOptionsStrategy } from './si-select-options-strategy';
 
/**
 * Input options strategy base class, for eagerly fetched options.
 */
@Directive()
export abstract class SiSelectOptionsStrategyBase<T> implements SiSelectOptionsStrategy<T> {
  /**
   * Function to compare two values on equality which is used to match/filter options.
   */
  abstract optionsEqual: InputSignal<(a: T, b: T) => boolean>;
 
  /**
   * All group and option items in the dropdown.
   *
   * @defaultValue []
   */
  abstract readonly allRows: Signal<(SelectItem<T> | SelectOption<T>)[]>;
 
  private readonly value = signal<T[]>([]);
  private readonly filterValue = signal('');
 
  /**
   * Rows that should be shown.
   *
   * @defaultValue []
   */
  readonly rows = computed(() => {
    const filterValue = this.filterValue();
    if (filterValue) {
      const filterValueLC = filterValue.toLowerCase();
      const checkRow: (row: SelectOption<T>) => boolean = (row: SelectOption<T>) =>
        (row.typeaheadLabel ?? row.label)!.toLowerCase().includes(filterValueLC!);
 
      return this.allRows().reduce((rows, row) => {
        if (row.type === 'option' && checkRow(row)) {
          rows.push(row);
        } else if (row.type === 'group') {
          if (row.label!.toLowerCase().includes(filterValueLC!)) {
            rows.push(row);
          } else {
            const options = row.options.filter(checkRow);
 
            if (options.length) {
              rows.push({ ...row, options });
            }
          }
        }
 
        return rows;
      }, [] as SelectItem<T>[]);
    } else {
      return this.allRows();
    }
  });
 
  readonly selectedRows = computed(() => {
    const values = this.value();
    return this.allRows()
      .map(row => (row.type === 'group' ? row.options : row))
      .flat()
      .filter(option => values.some(value => this.optionsEqual()(value, option.value)));
  });
 
  onValueChange(value: T[]): void {
    this.value.set(value);
  }
 
  onFilter(filterValue?: string): void {
    this.filterValue.set(filterValue ?? '');
  }
}