projects/isy-angular-widgets/src/lib/wizard/components/wizard/wizard.component.ts
Properties |
|
| disabled |
disabled:
|
Type : boolean
|
| Optional |
|
Explicitly disables a single step independent of autoDisableFutureSteps. |
| disabledScreenReaderText |
disabledScreenReaderText:
|
Type : string
|
| Optional |
|
Optional screen-reader text for explicitly disabled steps. |
| disabledTooltip |
disabledTooltip:
|
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;
}
}