Skip to content

Commit

Permalink
fix: (CXSPA-7958) - Dialog trigger refocus (#19240)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pio-Bar authored Oct 4, 2024
1 parent 23b72bb commit cbff331
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
</ng-container>

<button
#addToCartDialogTriggerEl
*ngIf="hasStock"
[ngClass]="
options?.displayAddToCart
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import {
ChangeDetectorRef,
Component,
ComponentRef,
ElementRef,
HostListener,
Input,
OnDestroy,
OnInit,
Optional,
ViewChild,
inject,
} from '@angular/core';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
Expand Down Expand Up @@ -55,6 +57,11 @@ export class AddToCartComponent implements OnInit, OnDestroy {
*/
@Input() product: Product;

/**
* Element responsible for opening the modal. The reference is used to refocus the modal after it closes.
*/
@ViewChild('addToCartDialogTriggerEl') addToCartDialogTriggerEl: ElementRef;

maxQuantity: number;

hasStock: boolean = false;
Expand All @@ -77,9 +84,7 @@ export class AddToCartComponent implements OnInit, OnDestroy {

iconTypes = ICON_TYPE;

@Optional() featureConfigService = inject(FeatureConfigService, {
optional: true,
});
private featureConfigService = inject(FeatureConfigService);

/**
* We disable the dialog launch on quantity input,
Expand Down Expand Up @@ -228,6 +233,9 @@ export class AddToCartComponent implements OnInit, OnDestroy {
newEvent.quantity = quantity;
newEvent.numberOfEntriesBeforeAdd = numberOfEntriesBeforeAdd;
newEvent.pickupStoreName = storeName;
if (this.featureConfigService.isEnabled('a11yDialogTriggerRefocus')) {
newEvent.triggerElementRef = this.addToCartDialogTriggerEl;
}
return newEvent;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class AddedToCartDialogEventListener implements OnDestroy {

const dialog = this.launchDialogService.openDialog(
LAUNCH_CALLER.ADDED_TO_CART,
undefined,
event?.triggerElementRef,
undefined,
addToCartData
);
Expand Down
6 changes: 6 additions & 0 deletions feature-libs/cart/base/root/events/cart.events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { ElementRef } from '@angular/core';
import { CxEvent } from '@spartacus/core';
import { OrderEntry } from '../models/cart.model';

Expand Down Expand Up @@ -127,6 +128,11 @@ export class CartUiEventAddToCart extends CxEvent {
quantity: number;
numberOfEntriesBeforeAdd: number;
pickupStoreName?: string;
/**
* Since the event can be used to open a dialog, we need to know which element triggered it.
* This way we can refocus on it after the dialog is closed.
*/
triggerElementRef?: ElementRef;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
[selectedOption]="pickupOption$ | async"
[displayPickupLocation]="displayPickupLocation$ | async"
(pickupOptionChange)="onPickupOptionChange($event)"
(pickupLocationChange)="openDialog()"
(pickupLocationChange)="openDialog($event)"
></cx-pickup-options>
</ng-container>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { CommonModule } from '@angular/common';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { I18nTestingModule, Product } from '@spartacus/core';
import {
FeatureConfigService,
I18nTestingModule,
Product,
} from '@spartacus/core';

import {
AugmentedPointOfService,
Expand All @@ -13,8 +17,8 @@ import {
} from '@spartacus/pickup-in-store/root';
import {
CurrentProductService,
LaunchDialogService,
LAUNCH_CALLER,
LaunchDialogService,
} from '@spartacus/storefront';
import { Observable, of, Subscription } from 'rxjs';
import { PdpPickupOptionsContainerComponent } from './pdp-pickup-options-container.component';
Expand Down Expand Up @@ -80,6 +84,12 @@ class MockCurrentLocationService {
}
}

class MockFeatureConfigService {
isEnabled() {
return true;
}
}

describe('PdpPickupOptionsComponent', () => {
let component: PdpPickupOptionsContainerComponent;
let fixture: ComponentFixture<PdpPickupOptionsContainerComponent>;
Expand Down Expand Up @@ -118,6 +128,10 @@ describe('PdpPickupOptionsComponent', () => {
provide: CurrentLocationService,
useClass: MockCurrentLocationService,
},
{
provide: FeatureConfigService,
useClass: MockFeatureConfigService,
},
],
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
import {
Component,
ElementRef,
inject,
OnDestroy,
OnInit,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Product } from '@spartacus/core';
import { FeatureConfigService, Product } from '@spartacus/core';

import {
AugmentedPointOfService,
Expand All @@ -26,8 +27,8 @@ import {
} from '@spartacus/pickup-in-store/root';
import {
CurrentProductService,
LaunchDialogService,
LAUNCH_CALLER,
LaunchDialogService,
} from '@spartacus/storefront';
import { combineLatest, iif, Observable, of, Subscription } from 'rxjs';
import {
Expand Down Expand Up @@ -55,6 +56,12 @@ function isProductWithCode(
templateUrl: 'pdp-pickup-options-container.component.html',
})
export class PdpPickupOptionsContainerComponent implements OnInit, OnDestroy {
// TODO: Remove element reference once 'a11yDialogTriggerRefocus' feature flag is removed.
/**
* @deprecated since 2211.28.0
* This reference does not point to any element and will be removed at earliest convinience.
* The 'triggerElement' is passed through 'PickupOptionChange' event instead.
*/
@ViewChild('open') element: ElementRef;
subscription = new Subscription();

Expand All @@ -65,6 +72,7 @@ export class PdpPickupOptionsContainerComponent implements OnInit, OnDestroy {
private productCode: string;
private displayNameIsSet = false;

private featureConfigService = inject(FeatureConfigService);
constructor(
protected currentProductService: CurrentProductService,
protected intendedPickupLocationService: IntendedPickupLocationFacade,
Expand Down Expand Up @@ -160,10 +168,17 @@ export class PdpPickupOptionsContainerComponent implements OnInit, OnDestroy {
this.subscription.unsubscribe();
}

openDialog(): void {
// TODO: Make argument required once 'a11yDialogTriggerRefocus' feature flag is removed.
/**
* @deprecated since 2211.28.0 - The use of TriggerElement param will become mandatory.
* @param triggerElement - The reference of element that triggered the dialog. Used to refocus on it after the dialog is closed.
*/
openDialog(triggerElement?: ElementRef): void {
const dialog = this.launchDialogService.openDialog(
LAUNCH_CALLER.PICKUP_IN_STORE,
this.element,
this.featureConfigService.isEnabled('a11yDialogTriggerRefocus')
? triggerElement
: this.element,
this.vcr,
{ productCode: this.productCode }
);
Expand All @@ -173,16 +188,46 @@ export class PdpPickupOptionsContainerComponent implements OnInit, OnDestroy {
}
}

onPickupOptionChange(option: PickupOption) {
this.intendedPickupLocationService.setPickupOption(
this.productCode,
option
);
if (option === 'delivery') {
return;
}
if (!this.displayNameIsSet) {
this.openDialog();
// TODO: Remove 'PickupOption' argument type once 'a11yDialogTriggerRefocus' feature flag is removed.
/**
* @deprecated since 2211.28.0 - Use event param instead of option.
* @param event - Object containing the selected option and the element that triggered the change.
*/
onPickupOptionChange(option: PickupOption): void;
// eslint-disable-next-line @typescript-eslint/unified-signatures
onPickupOptionChange(event: {
option: PickupOption;
triggerElement: ElementRef;
}): void;
onPickupOptionChange(
event: { option: PickupOption; triggerElement: ElementRef } | PickupOption
): void {
if (
this.featureConfigService.isEnabled('a11yDialogTriggerRefocus') &&
typeof event === 'object'
) {
const { option, triggerElement = undefined } = event;
this.intendedPickupLocationService.setPickupOption(
this.productCode,
option
);
if (option === 'delivery') {
return;
}
if (!this.displayNameIsSet) {
this.openDialog(triggerElement);
}
} else if (typeof event === 'string') {
this.intendedPickupLocationService.setPickupOption(
this.productCode,
event
);
if (event === 'delivery') {
return;
}
if (!this.displayNameIsSet) {
this.openDialog();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
|
<button
*cxFeature="'a11yUseButtonsForBtnLinks'"
#dialogTriggerEl
[attr.data-store-location-link]="
displayPickupLocation ? 'change' : 'select'
"
Expand All @@ -59,6 +60,7 @@
<a
*cxFeature="'!a11yUseButtonsForBtnLinks'"
role="button"
#dialogTriggerEl
[attr.data-store-location-link]="
displayPickupLocation ? 'change' : 'select'
"
Expand Down Expand Up @@ -119,6 +121,7 @@
|
<button
*cxFeature="'a11yUseButtonsForBtnLinks'"
#dialogTriggerEl
[attr.data-store-location-link]="
displayPickupLocation ? 'change' : 'select'
"
Expand All @@ -134,6 +137,7 @@
</button>
<a
*cxFeature="'!a11yUseButtonsForBtnLinks'"
#dialogTriggerEl
role="button"
[attr.data-store-location-link]="
displayPickupLocation ? 'change' : 'select'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { I18nTestingModule } from '@spartacus/core';
import { FeatureConfigService, I18nTestingModule } from '@spartacus/core';
import { PickupOption } from '@spartacus/pickup-in-store/root';
import { MockFeatureDirective } from 'projects/storefrontlib/shared/test/mock-feature-directive';
import { Observable } from 'rxjs';
import { PickupOptionsComponent } from './pickup-options.component';

class MockFeatureConfigService {
isEnabled() {
return true;
}
}

describe('PickupOptionsComponent', () => {
let component: PickupOptionsComponent;
let fixture: ComponentFixture<PickupOptionsComponent>;
Expand All @@ -16,6 +22,9 @@ describe('PickupOptionsComponent', () => {
TestBed.configureTestingModule({
declarations: [PickupOptionsComponent, MockFeatureDirective],
imports: [CommonModule, I18nTestingModule, ReactiveFormsModule],
providers: [
{ provide: FeatureConfigService, useClass: MockFeatureConfigService },
],
});
fixture = TestBed.createComponent(PickupOptionsComponent);
component = fixture.componentInstance;
Expand Down Expand Up @@ -43,7 +52,10 @@ describe('PickupOptionsComponent', () => {
spyOn(component.pickupOptionChange, 'emit');
component.onPickupOptionChange('delivery');

expect(component.pickupOptionChange.emit).toHaveBeenCalledWith('delivery');
expect(component.pickupOptionChange.emit).toHaveBeenCalledWith({
option: 'delivery',
triggerElement: component.triggerElement,
});
});

it('should emit on onPickupLocationChange', () => {
Expand Down
Loading

0 comments on commit cbff331

Please sign in to comment.