All files / filtered-search/values si-filtered-search-option-value.base.ts

100% Statements 40/40
92.59% Branches 25/27
100% Functions 15/15
100% Lines 35/35

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                                      1x   103x 103x 103x 103x 103x 103x 103x 103x   103x   103x 103x   103x 54x       103x 103x 103x 108x 108x 98x         10x               101x 6x       13x         13x     13x         95x 95x     101x   165x 126x   165x                     103x   67x   67x              
/**
 * Copyright (c) Siemens 2016 - 2025
 * SPDX-License-Identifier: MIT
 */
import { computed, DestroyRef, Directive, inject, input } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { injectSiTranslateService } from '@siemens/element-translate-ng/translate';
import { BehaviorSubject, Observable, of, switchMap } from 'rxjs';
import { debounceTime, first, map, tap } from 'rxjs/operators';
 
import {
  InternalCriterionDefinition,
  toOptionCriteria,
  TypeaheadOptionCriterion
} from '../si-filtered-search-helper';
import { OptionCriterion, OptionType } from '../si-filtered-search.model';
import { SiFilteredSearchValueBase } from './si-filtered-search-value.base';
 
@Directive()
export abstract class SiFilteredSearchOptionValueBase extends SiFilteredSearchValueBase {
  readonly lazyValueProvider =
    input<(criterionName: string, typed: string | string[]) => Observable<OptionType[]>>();
  readonly searchDebounceTime = input.required<number>();
  readonly onlySelectValue = input.required<boolean>();
  readonly maxCriteriaOptions = input.required<number>();
  readonly optionsInScrollableView = input.required<number>();
  readonly readonly = input.required<boolean>();
  readonly disableSelectionByColonAndSemicolon = input.required<boolean>();
  readonly isStrictOrOnlySelectValue = input.required<boolean>();
 
  protected readonly inputChange = new BehaviorSubject('');
 
  private readonly destroyRef = inject(DestroyRef);
  protected readonly translateService = injectSiTranslateService();
 
  readonly inputType = computed(() =>
    this.definition().validationType === 'integer' || this.definition().validationType === 'float'
      ? 'number'
      : 'text'
  );
  readonly step = computed(() => (this.definition().validationType === 'integer' ? '1' : 'any'));
  readonly options = computed(() => this.buildOptions());
  override readonly validValue = computed(() => {
    const config = this.definition();
    if (!this.isStrictOrOnlySelectValue() && !config.strictValue && !config.onlySelectValue) {
      return true;
    }
 
    // TODO: this never worked with lazy options. We should fix that.
    // TODO: checking if options are empty is also questionable. Should be changed v47.
    return (
      (config.options?.length && this.hasOptionValue()) ||
      (!config.options?.length && !!this.criterionValue().value)
    );
  });
 
  protected buildOptions(): Observable<TypeaheadOptionCriterion[]> | undefined {
    let optionsStream: Observable<OptionCriterion[]> | undefined;
    if (this.lazyValueProvider()) {
      optionsStream = this.inputChange.pipe(
        debounceTime(this.searchDebounceTime()),
        takeUntilDestroyed(this.destroyRef),
        switchMap(value => {
          return this.lazyValueProvider()!(
            this.definition().name,
            // TODO: fix lazy loading for multi-select. Seems to be not needed, but it should work.
            this.definition().multiSelect ? '' : (value ?? '')
          ).pipe(
            map(options => toOptionCriteria(options)),
            tap(
              options =>
                ((this.definition() ?? ({} as InternalCriterionDefinition)).options = options)
            )
          );
        })
      );
    } else if (this.definition()) {
      optionsStream = of(toOptionCriteria(this.definition().options));
    }
 
    return optionsStream?.pipe(
      switchMap(options => {
        const keys: string[] = options.map(option => option.label!).filter(label => !!label);
        return this.translateService.translateAsync(keys).pipe(
          map(translations =>
            options.map(option => ({
              ...option,
              translatedLabel: translations[option.label!] ?? option.label ?? option.value
            }))
          )
        );
      })
    );
  }
 
  protected buildOptionValue(): void {
    if (this.criterionValue().value?.length) {
      // resolve options for initial values
      this.options()!
        .pipe(first())
        .subscribe(options => this.processTypeaheadOptions(options));
    }
  }
 
  protected abstract processTypeaheadOptions(value: TypeaheadOptionCriterion[]): void;
  protected abstract hasOptionValue(): boolean;
}