Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions projects/element-ng/wizard/si-wizard.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,18 @@
(siResizeObserver)="updateVisibleSteps()"
>
@for (item of activeSteps(); track item.index) {
@let meta = stepsMetadata()[item.index];
<div class="step">
<div
[class]="['line', 'previous', getStateClass(item.index)]"
[class]="['line', 'previous', meta.stateClass]"
[class.dashed]="$first && item.index > 0"
[class.vertical]="verticalLayout()"
[class.spacer]="$first && item.index === 0"
></div>
<a
[class]="['focus-none', getStateClass(item.index)]"
[attr.aria-current]="getAriaCurrent(item.index)"
[attr.href]="!canActivate(item.index) || !currentStep?.isNextNavigable() ? null : '#'"
[class]="['focus-none', meta.stateClass]"
[attr.aria-current]="meta.ariaCurrent"
[attr.href]="!meta.canActivate || !currentStep?.isNextNavigable() ? null : '#'"
(click)="activateStep($event, item.index)"
>
@if (showStepNumbers() && !item.step.failed()) {
Expand All @@ -83,20 +84,20 @@
<si-icon
class="icon-lg step-icon"
[class.status-warning]="item.step.failed()"
[icon]="getState(item.step!, item.index)"
[icon]="meta.icon"
/>
}
<div
class="title si-h5"
[class.text-center]="!verticalLayout()"
[class.px-6]="!verticalLayout()"
[attr.aria-disabled]="getAriaDisabled(item.index)"
[attr.aria-disabled]="meta.ariaDisabled"
>{{ item.step.heading() | translate }}</div
>
</a>
@if (item.index + 1 < stepCount) {
<div
[class]="['line', getStateClass(item.index + 1)]"
[class]="['line', stepsMetadata()[item.index + 1].stateClass]"
[class.vertical]="verticalLayout()"
[class.dashed]="$last"
></div>
Expand Down
109 changes: 60 additions & 49 deletions projects/element-ng/wizard/si-wizard.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ interface StepItem {
step: SiWizardStepComponent;
}

interface StepMetadata {
canActivate: boolean;
stateClass: string;
ariaDisabled: 'true' | 'false';
ariaCurrent: 'step' | 'false';
icon: string;
}

@Component({
selector: 'si-wizard',
imports: [SiIconComponent, SiResizeObserverDirective, SiTranslatePipe, NgTemplateOutlet],
Expand Down Expand Up @@ -240,31 +248,59 @@ export class SiWizardComponent {
elementWarningFilled
});

protected canActivate(stepIndex: number): boolean {
if (stepIndex < 0) {
return false;
}
// Can always activate previous steps
if (stepIndex < this.index) {
return true;
}
// We are already in the step. Nothing to activate.
if (stepIndex === this.index) {
return false;
}
// Fast-forward: check all steps if they are valid
for (let i = this.index; i < stepIndex; i++) {
const theStep = this.steps()[i];
if (!theStep.isValid()) {
return false;
protected readonly stepsMetadata = computed((): StepMetadata[] => {
const index = this._index();
const steps = this.steps();
const stepFailedIcon = this.stepFailedIcon();
const stepActiveIcon = this.stepActiveIcon();
const stepIcon = this.stepIcon();
const stepCompletedIcon = this.stepCompletedIcon();

// O(N) pre-calculation: find the first invalid step from the current index
let firstInvalidIndex = steps.length;
for (let i = index; i < steps.length; i++) {
if (!steps[i].isValid()) {
firstInvalidIndex = i;
break;
}
}
return true;
}

return steps.map((step, stepIndex) => {
// canActivate: O(1) per step using pre-calculated firstInvalidIndex
let canActivate: boolean;
if (stepIndex < index) {
canActivate = true;
} else if (stepIndex === index) {
canActivate = false;
} else {
canActivate = firstInvalidIndex >= stepIndex;
}

// stateClass
const stateClass = this.getStateClass(stepIndex, canActivate);

// ariaDisabled
const ariaDisabled = !canActivate ? 'true' : 'false';

// ariaCurrent
const ariaCurrent = stepIndex === index ? 'step' : 'false';

// icon
let icon: string;
if (step.failed()) {
icon = stepFailedIcon;
} else {
const txtStyle = step.isActive() ? stepActiveIcon : stepIcon;
icon = stepIndex >= index ? txtStyle : stepCompletedIcon;
}
Comment on lines +289 to +295
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here as well I guess you can use the original function to extract value


return { canActivate, stateClass, ariaDisabled, ariaCurrent, icon };
});
});

protected activateStep(event: Event, stepIndex: number): void {
event.preventDefault();
if (this.canActivate(stepIndex)) {
if (this.stepsMetadata()[stepIndex].canActivate) {
if (stepIndex > this.index) {
this.next(stepIndex - this.index);
}
Expand All @@ -274,11 +310,11 @@ export class SiWizardComponent {
}
}

protected getStateClass(stepIndex: number): string {
protected getStateClass(stepIndex: number, canActivate: boolean): string {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
protected getStateClass(stepIndex: number, canActivate: boolean): string {
private getStateClass(stepIndex: number, canActivate: boolean): string {

if (stepIndex === this.index) {
return 'active';
}
if (!this.canActivate(stepIndex)) {
if (!canActivate) {
return 'disabled';
}
if (stepIndex < this.index) {
Expand All @@ -287,32 +323,15 @@ export class SiWizardComponent {
return '';
}

protected getAriaDisabled(stepIndex: number): string {
if (!this.canActivate(stepIndex)) {
return 'true';
}
return 'false';
}

protected getAriaCurrent(stepIndex: number): string {
if (stepIndex === this.index) {
return 'step';
}
return 'false';
}

/**
* Go to the next wizard step.
* @param delta - optional number of steps to move forward.
*/
next(delta: number = 1): void {
const steps = this.steps();
if (this.index === steps.length - 1) {
return;
}
const stepIndex = this.index + delta;
const nextStep = steps[stepIndex];
if (this.canActivate(stepIndex)) {
if (stepIndex < steps.length && this.stepsMetadata()[stepIndex].canActivate) {
const nextStep = steps[stepIndex];
this.currentStep?.next.emit();
if (this.currentStep?.isNextNavigable()) {
this.activate(nextStep);
Expand Down Expand Up @@ -347,14 +366,6 @@ export class SiWizardComponent {
}
}

protected getState(step: SiWizardStepComponent, stepIndex: number): string {
if (step.failed() === true) {
return this.stepFailedIcon();
}
const txtStyle = step.isActive() ? this.stepActiveIcon() : this.stepIcon();
return stepIndex >= this.index ? txtStyle : this.stepCompletedIcon();
}

private activate(step: SiWizardStepComponent): void {
if (this.currentStep) {
this.currentStep.isActive.set(false);
Expand Down
Loading