Skip to content

Commit

Permalink
Merge branch 'develop' into feature/CXSPA-9050
Browse files Browse the repository at this point in the history
  • Loading branch information
petarmarkov9449 authored Jan 9, 2025
2 parents 068c080 + 89151c1 commit b3189bc
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 175 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ChangeDetectionStrategy, Component, Input, Type } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import {
ComponentFixture,
fakeAsync,
TestBed,
tick,
waitForAsync,
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import { ActiveCartFacade } from '@spartacus/cart/base/root';
Expand Down Expand Up @@ -547,4 +553,25 @@ describe('CheckoutDeliveryAddressComponent', () => {
expect(getSpinner()).toBeFalsy();
});
});

describe('focusCardAfterSelecting', () => {
it('should refocus the selected card after updating', fakeAsync(() => {
const card = document.createElement('cx-card');
const selectButton = document.createElement('button');
card.appendChild(selectButton);
card.tabIndex = 0;
document.body.appendChild(card);
selectButton.focus();
component['isUpdating$'] = of(false);
spyOn(card, 'focus');
spyOn(component['focusService'], 'findFirstFocusable').and.returnValue(
card
);

component.focusCardAfterSelecting();
tick(16); // Wait for requestAnimationFrame

expect(card.focus).toHaveBeenCalled();
}));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,21 @@ import {
GlobalMessageType,
TranslationService,
UserAddressService,
WindowRef,
getLastValueSync,
} from '@spartacus/core';
import { Card, getAddressNumbers } from '@spartacus/storefront';
import {
Card,
SelectFocusUtility,
getAddressNumbers,
} from '@spartacus/storefront';
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
import {
distinctUntilChanged,
filter,
map,
switchMap,
take,
tap,
} from 'rxjs/operators';
import { CheckoutConfigService } from '../services';
Expand Down Expand Up @@ -79,6 +85,9 @@ export class CheckoutDeliveryAddressComponent implements OnInit {
);
}

@Optional() protected focusService = inject(SelectFocusUtility);
@Optional() protected windowRef = inject(WindowRef);

constructor(
protected userAddressService: UserAddressService,
protected checkoutDeliveryAddressFacade: CheckoutDeliveryAddressFacade,
Expand Down Expand Up @@ -155,6 +164,38 @@ export class CheckoutDeliveryAddressComponent implements OnInit {
);

this.setAddress(address);
if (this.featureConfigService?.isEnabled('a11yFocusOnCardAfterSelecting')) {
this.focusCardAfterSelecting();
}
}

/**
* Restores the focus to the Card component after it has been selected and the checkout has finished updating.
* The focus is lost due to DOM changes making it otherwise impossible to target elements that have been removed.
*/
focusCardAfterSelecting(): void {
const cardNodes = Array.from(
this.windowRef?.document.querySelectorAll('cx-card')
);
const triggeredCard =
this.windowRef?.document.activeElement?.closest('cx-card');

if (triggeredCard) {
const selectedCardIndex = cardNodes.indexOf(triggeredCard);
this.isUpdating$
.pipe(
filter((isUpdating) => !isUpdating),
take(1)
)
.subscribe(() => {
requestAnimationFrame(() => {
const selectedCard = this.windowRef?.document.querySelectorAll(
'cx-card'
)[selectedCardIndex] as HTMLElement;
this.focusService.findFirstFocusable(selectedCard)?.focus();
});
});
}
}

addAddress(address: Address | undefined): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Component, Input, Type } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import {
ComponentFixture,
fakeAsync,
TestBed,
tick,
waitForAsync,
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import { ActiveCartFacade } from '@spartacus/cart/base/root';
Expand All @@ -19,7 +25,7 @@ import {
} from '@spartacus/core';
import { CardComponent, ICON_TYPE } from '@spartacus/storefront';
import { MockFeatureDirective } from 'projects/storefrontlib/shared/test/mock-feature-directive';
import { BehaviorSubject, EMPTY, Observable, Subject, of } from 'rxjs';
import { BehaviorSubject, EMPTY, Observable, of, Subject } from 'rxjs';
import { CheckoutStepService } from '../services/checkout-step.service';
import { CheckoutPaymentMethodComponent } from './checkout-payment-method.component';
import createSpy = jasmine.createSpy;
Expand Down Expand Up @@ -667,5 +673,26 @@ describe('CheckoutPaymentMethodComponent', () => {
).toEqual('button');
});
});

describe('focusCardAfterSelecting', () => {
it('should refocus the selected card after updating', fakeAsync(() => {
const card = document.createElement('cx-card');
const selectButton = document.createElement('button');
card.appendChild(selectButton);
card.tabIndex = 0;
document.body.appendChild(card);
selectButton.focus();
component['isUpdating$'] = of(false);
spyOn(card, 'focus');
spyOn(component['focusService'], 'findFirstFocusable').and.returnValue(
card
);

component.focusCardAfterSelecting();
tick(16); // Wait for requestAnimationFrame

expect(card.focus).toHaveBeenCalled();
}));
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import {
ChangeDetectionStrategy,
Component,
inject,
OnDestroy,
OnInit,
Optional,
inject,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActiveCartFacade } from '@spartacus/cart/base/root';
Expand All @@ -27,8 +27,9 @@ import {
PaymentDetails,
TranslationService,
UserPaymentService,
WindowRef,
} from '@spartacus/core';
import { Card, ICON_TYPE } from '@spartacus/storefront';
import { Card, ICON_TYPE, SelectFocusUtility } from '@spartacus/storefront';
import {
BehaviorSubject,
combineLatest,
Expand Down Expand Up @@ -58,6 +59,8 @@ export class CheckoutPaymentMethodComponent implements OnInit, OnDestroy {
@Optional() protected featureConfigService = inject(FeatureConfigService, {
optional: true,
});
@Optional() protected focusService = inject(SelectFocusUtility);
@Optional() protected windowRef = inject(WindowRef);

cards$: Observable<{ content: Card; paymentMethod: PaymentDetails }[]>;
iconTypes = ICON_TYPE;
Expand Down Expand Up @@ -218,6 +221,38 @@ export class CheckoutPaymentMethodComponent implements OnInit, OnDestroy {
);

this.savePaymentMethod(paymentDetails);
if (this.featureConfigService?.isEnabled('a11yFocusOnCardAfterSelecting')) {
this.focusCardAfterSelecting();
}
}

/**
* Restores the focus to the Card component after it has been selected and the checkout has finished updating.
* The focus is lost due to DOM changes making it otherwise impossible to target elements that have been removed.
*/
focusCardAfterSelecting(): void {
const cardNodes = Array.from(
this.windowRef?.document.querySelectorAll('cx-card')
);
const triggeredCard =
this.windowRef?.document.activeElement?.closest('cx-card');

if (triggeredCard) {
const selectedCardIndex = cardNodes.indexOf(triggeredCard);
this.isUpdating$
.pipe(
filter((isUpdating) => !isUpdating),
take(1)
)
.subscribe(() => {
requestAnimationFrame(() => {
const selectedCard = this.windowRef?.document.querySelectorAll(
'cx-card'
)[selectedCardIndex] as HTMLElement;
this.focusService.findFirstFocusable(selectedCard)?.focus();
});
});
}
}

showNewPaymentForm(): void {
Expand Down
Loading

0 comments on commit b3189bc

Please sign in to comment.