File

projects/isy-angular-widgets/src/lib/wizard/components/wizard/wizard.component.ts

Index

Properties

Properties

disabled
disabled: boolean
Type : boolean
Optional

Explicitly disables a single step independent of autoDisableFutureSteps.

disabledScreenReaderText
disabledScreenReaderText: string
Type : string
Optional

Optional screen-reader text for explicitly disabled steps.

disabledTooltip
disabledTooltip: string
Type : string
Optional

Optional tooltip for explicitly disabled steps.

import {
  AfterContentInit,
  Component,
  ContentChild,
  ContentChildren,
  EventEmitter,
  inject,
  Input,
  OnChanges,
  OnInit,
  Output,
  QueryList,
  SimpleChanges
} from '@angular/core';
import {MenuItem, MessageService} from 'primeng/api';
import {WizardDirective} from '../../directives/wizard.directive';
import {WizardFooterDirective} from '../../directives/wizard-footer.directive';
import {WidgetsConfigService} from '../../../i18n/widgets-config.service';
import {CommonModule} from '@angular/common';
import {StepperModule} from 'primeng/stepper';
import {DialogModule} from 'primeng/dialog';
import {ButtonModule} from 'primeng/button';
import {ToastModule} from 'primeng/toast';
import {TooltipModule} from 'primeng/tooltip';

export interface WizardStepState {
  /**
   * Explicitly disables a single step independent of autoDisableFutureSteps.
   */
  disabled?: boolean;
  /**
   * Optional tooltip for explicitly disabled steps.
   */
  disabledTooltip?: string;
  /**
   * Optional screen-reader text for explicitly disabled steps.
   */
  disabledScreenReaderText?: string;
}

export interface WizardFooterContext {
  index: number;
  stepCount: number;
  isFirstStep: boolean;
  isLastStep: boolean;
  allowNext: boolean;
  isSaved: boolean;
  closable: boolean;
  showBack: boolean;
  showNext: boolean;
  showSave: boolean;
  canClose: boolean;
  next: () => void;
  previous: () => void;
  save: () => void;
  close: () => void;
}

/**
 * The width of the wizard of not otherwise specified by the user.
 * @internal
 */
const defaultWidth = 50;

/**
 * The height of the wizard of not otherwise specified by the user.
 * @internal
 */
const defaultHeight = 30;

/**
 * A wizard that guides the user step by step through series of forms.
 * Each side needs to have the {@link WizardDirective}.
 */
@Component({
  standalone: true,
  selector: 'isy-wizard',
  templateUrl: './wizard.component.html',
  styleUrls: ['./wizard.component.scss'],
  imports: [CommonModule, StepperModule, DialogModule, ButtonModule, ToastModule, TooltipModule],
  providers: [MessageService]
})
export class WizardComponent implements OnInit, AfterContentInit, OnChanges {
  /**
   * Stores the content that will be projected inside the template
   */
  @ContentChildren(WizardDirective) content?: QueryList<WizardDirective>;

  /**
   * Stores an optional projected custom footer template.
   */
  @ContentChild(WizardFooterDirective) footerTemplate?: WizardFooterDirective;

  /**
   * Emits the currently displayed page
   */
  @Output() indexChange = new EventEmitter<number>();

  /**
   * Emits when the user is currently trying to save to be handled from outside
   */
  @Output() savingChange = new EventEmitter<boolean>();

  /**
   * Emits the current visibility status
   */
  @Output() isVisibleChange = new EventEmitter<boolean>();

  /**
   * Determines whether the wizard is visible
   */
  @Input() isVisible: boolean = false;

  /**
   * Determines whether the wizard is draggable
   */
  @Input() draggable: boolean = false;

  /**
   * Determines whether the wizard is closable
   */
  @Input() closable: boolean = true;

  /**
   * Determines if the modal behind the wizard dialog is displayed
   */
  @Input() modal: boolean = true;

  /**
   * A title to show
   */
  @Input() headerTitle: string = '';

  /**
   * The wizard width in %.
   * Default is 50
   */
  @Input() width: number = defaultWidth;

  /**
   * The wizard height  in %.
   * Default is 30
   */
  @Input() height: number = defaultHeight;

  /**
   * The text of the back button
   */
  @Input() labelBackButton = 'Zurück';

  /**
   * The text of the next button
   */
  @Input() labelNextButton = 'Weiter';

  /**
   * The text of the save button
   */
  @Input() labelSaveButton = 'Speichern';

  /**
   * The text of the close button
   */
  @Input() labelCloseButton = 'Schließen';

  /**
   * Controls whether the next button is enabled which is to be controlled from the outside (e.g. for validation)
   */
  @Input() allowNext: boolean = false;

  /**
   * Determines if the system is saving now
   */
  @Input() isSaved: boolean = false;

  /**
   * The current wizard index
   */
  @Input() index: number = 0;

  /**
   * Breakpoint for PrimeNg dialog responsiveness
   */
  @Input() breaktpoints: {[key: string]: string} = {
    '3840px': '95vw',
    '1920px': '95vw',
    '1366px': '85vw',
    '768px': '95vw',
    '412px': '95vw'
  };

  @Input() allowFreeNavigation = false;

  /**
   * Automatically disables all following steps while the current step is not allowed to proceed.
   * Typical use:
   * <isy-wizard [autoDisableFutureSteps]="true"></isy-wizard>
   */
  @Input() autoDisableFutureSteps = false;

  /**
   * Tooltip text used for auto-disabled steps.
   * Example:
   * <isy-wizard [disabledStepTooltip]="'Bitte zuerst Pflichtfelder ausfüllen'"></isy-wizard>
   */
  @Input() disabledStepTooltip?: string;

  /**
   * Screenreader text used for auto-disabled steps.
   * Example:
   * <isy-wizard [disabledStepAriaText]="'Schritt deaktiviert'"></isy-wizard>
   */
  @Input() disabledStepAriaText?: string;

  /**
   * Optional states for individual steps. Array positions correspond to step indices.
   * Use this only when single steps need different disabled/tooltip/aria behavior.
   */
  @Input() stepStates: WizardStepState[] = [];

  /**
   * Stores the items of the wizard
   */
  items: MenuItem[] = [];

  /**
   * A service used to translate labels within the widgets library.
   */
  configService = inject(WidgetsConfigService);

  readonly messageService = inject(MessageService);

  get stepCount(): number {
    return this.items.length;
  }

  get isFirstStep(): boolean {
    return this.index === 0;
  }

  get isLastStep(): boolean {
    return this.index === this.stepCount - 1;
  }

  get showBackButton(): boolean {
    return !this.isSaved && !this.isFirstStep;
  }

  get showNextButton(): boolean {
    return !this.isSaved && !this.isLastStep;
  }

  get showSaveButton(): boolean {
    return this.isLastStep && !this.isSaved;
  }

  get canClose(): boolean {
    return !this.isSaved || this.closable;
  }

  get footerContext(): WizardFooterContext {
    return {
      index: this.index,
      stepCount: this.stepCount,
      isFirstStep: this.isFirstStep,
      isLastStep: this.isLastStep,
      allowNext: this.allowNext,
      isSaved: this.isSaved,
      closable: this.closable,
      showBack: this.showBackButton,
      showNext: this.showNextButton,
      showSave: this.showSaveButton,
      canClose: this.canClose,
      next: () => this.next(),
      previous: () => this.previous(),
      save: () => this.save(),
      close: () => this.closeDialog()
    };
  }

  /**
   * Fired on initialization
   */
  ngOnInit(): void {
    this.indexChange.emit(this.index);
  }

  /**
   * Fired after content initialization
   */
  ngAfterContentInit(): void {
    if (this.content) {
      this.items = this.content.map((item) => {
        return {
          label: item.isyWizardDirective
        };
      });
    }
  }

  /**
   * Fired on changes
   * @param changes Includes all DOM changes
   */
  ngOnChanges(changes: SimpleChanges): void {
    if (changes.isVisible?.previousValue === true && changes.isVisible?.currentValue === false) {
      this.resetWizard();
    }
  }

  /**
   * Moves the wizard to the next position
   */
  next(): void {
    this.index++;
    this.indexChange.emit(this.index);
  }

  /**
   * Moves the wizard to the previous position
   */
  previous(): void {
    this.index--;
    this.indexChange.emit(this.index);
  }

  /**
   * Is closing the dialog
   */
  closeDialog(): void {
    this.resetWizard();
    this.close();
  }

  /**
   * Resets the wizard position
   */
  private resetWizard(): void {
    this.index = 0;
  }

  /**
   * Is closing the wizard
   */
  private close(): void {
    this.isVisible = false;
    this.isVisibleChange.emit(this.isVisible);
  }

  /**
   * Informs about the save action
   */
  save(): void {
    this.savingChange.emit(true);
  }

  /**
   * Handles the change of the active index in the wizard component.
   * Updates the current index, emits the index change event, and displays
   * a toast message indicating the step change.
   * @param event - The new active index of the wizard.
   */
  onActiveIndexChange(event: number): void {
    if (event < 0 || event >= this.items.length) {
      return;
    }

    this.index = event;
    this.indexChange.emit(this.index);

    const label = this.items[event]?.label ?? '';

    this.messageService.add({
      severity: 'info',
      summary: this.configService.getTranslation('wizard.toast.stepChanged'),
      detail: label
    });
  }

  onStepperValueChange(stepValue: number | undefined): void {
    if (stepValue == null) {
      return;
    }

    const newIndex = stepValue - 1;

    if (newIndex < 0 || newIndex >= this.items.length) {
      return;
    }

    if (this.allowFreeNavigation && !this.isStepDisabled(newIndex)) {
      this.onActiveIndexChange(newIndex);
    }
  }

  onStepSelect(index: number, activateCallback: () => void): void {
    if (this.isStepDisabled(index)) {
      return;
    }

    activateCallback();
  }

  private isAutoDisabledStep(index: number): boolean {
    return this.autoDisableFutureSteps && !this.allowNext && index > this.index;
  }

  private isExplicitlyDisabledStep(index: number): boolean {
    return this.stepStates[index]?.disabled ?? false;
  }

  isStepDisabled(index: number): boolean {
    return this.isExplicitlyDisabledStep(index) || this.isAutoDisabledStep(index);
  }

  getStepTooltip(index: number): string | undefined {
    if (!this.isStepDisabled(index)) {
      return undefined;
    }

    if (this.isExplicitlyDisabledStep(index)) {
      return this.stepStates[index]?.disabledTooltip;
    }

    if (this.isAutoDisabledStep(index)) {
      return this.disabledStepTooltip;
    }

    return undefined;
  }

  getStepScreenReaderText(index: number): string {
    if (!this.isStepDisabled(index)) {
      return '';
    }

    const configuredText = this.isExplicitlyDisabledStep(index)
      ? this.stepStates[index]?.disabledScreenReaderText?.trim()
      : this.disabledStepAriaText?.trim();
    if (configuredText) {
      return configuredText;
    }

    const disabledStateText = this.configService.getTranslation('wizard.aria.disabledStep');
    const tooltip = this.getStepTooltip(index)?.trim();

    return tooltip ? `${disabledStateText}. ${tooltip}` : disabledStateText;
  }
}

results matching ""

    No results matching ""