Skip to content

Commit

Permalink
feat(overlays): 'willClose' and 'willOpen' are now cancelable events
Browse files Browse the repository at this point in the history
  • Loading branch information
TomMenga committed Dec 6, 2023
1 parent 8babd50 commit 5738d33
Show file tree
Hide file tree
Showing 14 changed files with 324 additions and 111 deletions.
30 changes: 30 additions & 0 deletions src/components/autocomplete/autocomplete.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,34 @@ describe('sbb-autocomplete', () => {
await waitForLitRender(element);
expect(input).to.have.attribute('aria-expanded', 'false');
});

it('does not open if prevented', async () => {
const willOpenEventSpy = new EventSpy(SbbAutocomplete.events.willOpen);

element.addEventListener(SbbAutocomplete.events.willOpen, (ev) => ev.preventDefault());
element.open();

await waitForCondition(() => willOpenEventSpy.events.length === 1);
expect(willOpenEventSpy.count).to.be.equal(1);
await waitForLitRender(element);

expect(element).to.have.attribute('data-state', 'closed');
});

it('does not close if prevented', async () => {
const didOpenEventSpy = new EventSpy(SbbAutocomplete.events.didOpen);
const willCloseEventSpy = new EventSpy(SbbAutocomplete.events.willClose);

element.open();
await waitForCondition(() => didOpenEventSpy.events.length === 1);
await waitForLitRender(element);

element.addEventListener(SbbAutocomplete.events.willClose, (ev) => ev.preventDefault());
element.close();

await waitForCondition(() => willCloseEventSpy.events.length === 1);
await waitForLitRender(element);

expect(element).to.have.attribute('data-state', 'opened');
});
});
18 changes: 10 additions & 8 deletions src/components/autocomplete/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ let nextId = 0;
* Combined with a native input, it displays a panel with a list of available options.
*
* @slot - Use the unnamed slot to add `sbb-option` or `sbb-optgroup` elements to the `sbb-autocomplete`.
* @event {CustomEvent<void>} willOpen - Emits whenever the `sbb-autocomplete` starts the opening transition.
* @event {CustomEvent<void>} willOpen - Emits whenever the `sbb-autocomplete` starts the opening transition. Can be canceled.
* @event {CustomEvent<void>} didOpen - Emits whenever the `sbb-autocomplete` is opened.
* @event {CustomEvent<void>} willClose - Emits whenever the `sbb-autocomplete` begins the closing transition.
* @event {CustomEvent<void>} willClose - Emits whenever the `sbb-autocomplete` begins the closing transition. Can be canceled.
* @event {CustomEvent<void>} didClose - Emits whenever the `sbb-autocomplete` is closed.
*/
@customElement('sbb-autocomplete')
Expand Down Expand Up @@ -123,9 +123,10 @@ export class SbbAutocomplete extends LitElement {
return;
}

this._state = 'opening';
this._willOpen.emit();
this._setOverlayPosition();
if (this._willOpen.emit()) {
this._state = 'opening';
this._setOverlayPosition();
}
}

/** Closes the autocomplete. */
Expand All @@ -134,9 +135,10 @@ export class SbbAutocomplete extends LitElement {
return;
}

this._state = 'closing';
this._willClose.emit();
this._openPanelEventsController.abort();
if (this._willClose.emit()) {
this._state = 'closing';
this._openPanelEventsController.abort();
}
}

/** Removes trigger click listener on trigger change. */
Expand Down
36 changes: 36 additions & 0 deletions src/components/dialog/dialog.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ describe('sbb-dialog', () => {
await openDialog(element);
});

it('does not open the dialog if prevented', async () => {
const willOpen = new EventSpy(SbbDialog.events.willOpen);
const didOpen = new EventSpy(SbbDialog.events.didOpen);

element.addEventListener(SbbDialog.events.willOpen, (ev) => ev.preventDefault());

element.open();
await waitForLitRender(element);

await waitForCondition(() => willOpen.events.length === 1);
expect(willOpen.count).to.be.equal(1);
await waitForLitRender(element);

expect(didOpen.count).to.be.equal(0);
expect(element).to.have.attribute('data-state', 'closed');
});

it('closes the dialog', async () => {
const willClose = new EventSpy(SbbDialog.events.willClose);
const didClose = new EventSpy(SbbDialog.events.didClose);
Expand All @@ -73,6 +90,25 @@ describe('sbb-dialog', () => {
expect(ariaLiveRef.textContent).to.be.equal('');
});

it('does not close the dialog if prevented', async () => {
const willClose = new EventSpy(SbbDialog.events.willClose);
const didClose = new EventSpy(SbbDialog.events.didClose);

await openDialog(element);

element.addEventListener(SbbDialog.events.willClose, (ev) => ev.preventDefault());

element.close();
await waitForLitRender(element);

await waitForCondition(() => willClose.events.length === 1);
expect(willClose.count).to.be.equal(1);
await waitForLitRender(element);

expect(didClose.count).to.be.equal(0);
expect(element).to.have.attribute('data-state', 'opened');
});

it('closes the dialog on backdrop click', async () => {
const willClose = new EventSpy(SbbDialog.events.willClose);
const didClose = new EventSpy(SbbDialog.events.didClose);
Expand Down
34 changes: 22 additions & 12 deletions src/components/dialog/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ let nextId = 0;
* @slot - Use the unnamed slot to add content to the `sbb-dialog`.
* @slot title - Use this slot to provide a title.
* @slot action-group - Use this slot to display a `sbb-action-group` in the footer.
* @event {CustomEvent<void>} willOpen - Emits whenever the `sbb-dialog` starts the opening transition.
* @event {CustomEvent<void>} willOpen - Emits whenever the `sbb-dialog` starts the opening transition. Can be canceled.
* @event {CustomEvent<void>} didOpen - Emits whenever the `sbb-dialog` is opened.
* @event {CustomEvent<void>} willClose - Emits whenever the `sbb-dialog` begins the closing transition.
* @event {CustomEvent<void>} willClose - Emits whenever the `sbb-dialog` begins the closing transition. Can be canceled.
* @event {CustomEvent<void>} didClose - Emits whenever the `sbb-dialog` is closed.
* @event {CustomEvent<void>} requestBackAction - Emits whenever the back button is clicked.
*/
Expand Down Expand Up @@ -184,13 +184,17 @@ export class SbbDialog extends LitElement {
return;
}
this._lastFocusedElement = document.activeElement as HTMLElement;
this._willOpen.emit();
this._state = 'opening';
// Add this dialog to the global collection
dialogRefs.push(this as SbbDialog);
this._setOverflowAttribute();
// Disable scrolling for content below the dialog
this._scrollHandler.disableScroll();

if (this._willOpen.emit()) {
this._state = 'opening';

// Add this dialog to the global collection
dialogRefs.push(this as SbbDialog);
this._setOverflowAttribute();

// Disable scrolling for content below the dialog
this._scrollHandler.disableScroll();
}
}

/**
Expand All @@ -203,9 +207,15 @@ export class SbbDialog extends LitElement {

this._returnValue = result;
this._dialogCloseElement = target;
this._willClose.emit({ returnValue: this._returnValue, closeTarget: this._dialogCloseElement });
this._state = 'closing';
this._removeAriaLiveRefContent();
if (
this._willClose.emit({
returnValue: this._returnValue,
closeTarget: this._dialogCloseElement,
})
) {
this._state = 'closing';
this._removeAriaLiveRefContent();
}
}

// Closes the dialog on "Esc" key pressed.
Expand Down
30 changes: 30 additions & 0 deletions src/components/menu/menu/menu.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,34 @@ describe('sbb-menu', () => {
await waitForLitRender(element);
expect(document.activeElement.id).to.be.equal('menu-link');
});

it('does not open if prevented', async () => {
const willOpenEventSpy = new EventSpy(SbbMenu.events.willOpen);

element.addEventListener(SbbMenu.events.willOpen, (ev) => ev.preventDefault());
element.open();

await waitForCondition(() => willOpenEventSpy.events.length === 1);
expect(willOpenEventSpy.count).to.be.equal(1);
await waitForLitRender(element);

expect(element).to.have.attribute('data-state', 'closed');
});

it('does not close if prevented', async () => {
const didOpenEventSpy = new EventSpy(SbbMenu.events.didOpen);
const willCloseEventSpy = new EventSpy(SbbMenu.events.willClose);

element.open();
await waitForCondition(() => didOpenEventSpy.events.length === 1);
await waitForLitRender(element);

element.addEventListener(SbbMenu.events.willClose, (ev) => ev.preventDefault());
element.close();

await waitForCondition(() => willCloseEventSpy.events.length === 1);
await waitForLitRender(element);

expect(element).to.have.attribute('data-state', 'opened');
});
});
46 changes: 18 additions & 28 deletions src/components/menu/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ let nextId = 0;
* It displays a contextual menu with one or more action element.
*
* @slot - Use the unnamed slot to add `sbb-menu-action` or other elements to the menu.
* @event {CustomEvent<void>} willOpen - Emits whenever the `sbb-menu` starts the opening transition.
* @event {CustomEvent<void>} willOpen - Emits whenever the `sbb-menu` starts the opening transition. Can be canceled.
* @event {CustomEvent<void>} didOpen - Emits whenever the `sbb-menu` is opened.
* @event {CustomEvent<void>} willClose - Emits whenever the `sbb-menu` begins the closing transition.
* @event {CustomEvent<void>} willClose - Emits whenever the `sbb-menu` begins the closing transition. Can be canceled.
* @event {CustomEvent<void>} didClose - Emits whenever the `sbb-menu` is closed.
*/
@customElement('sbb-menu')
Expand Down Expand Up @@ -93,28 +93,16 @@ export class SbbMenu extends SlotChildObserver(LitElement) {
@state() private _actions: SbbMenuAction[];

/** Emits whenever the `sbb-menu` starts the opening transition. */
private _willOpen: EventEmitter<void> = new EventEmitter(this, SbbMenu.events.willOpen, {
bubbles: true,
composed: true,
});
private _willOpen: EventEmitter<void> = new EventEmitter(this, SbbMenu.events.willOpen);

/** Emits whenever the `sbb-menu` is opened. */
private _didOpen: EventEmitter<void> = new EventEmitter(this, SbbMenu.events.didOpen, {
bubbles: true,
composed: true,
});
private _didOpen: EventEmitter<void> = new EventEmitter(this, SbbMenu.events.didOpen);

/** Emits whenever the `sbb-menu` begins the closing transition. */
private _willClose: EventEmitter<void> = new EventEmitter(this, SbbMenu.events.willClose, {
bubbles: true,
composed: true,
});
private _willClose: EventEmitter<void> = new EventEmitter(this, SbbMenu.events.willClose);

/** Emits whenever the `sbb-menu` is closed. */
private _didClose: EventEmitter<void> = new EventEmitter(this, SbbMenu.events.didClose, {
bubbles: true,
composed: true,
});
private _didClose: EventEmitter<void> = new EventEmitter(this, SbbMenu.events.didClose);

private _menu: HTMLDivElement;
private _triggerElement: HTMLElement;
Expand All @@ -135,14 +123,15 @@ export class SbbMenu extends SlotChildObserver(LitElement) {
return;
}

this._willOpen.emit();
this._state = 'opening';
this._setMenuPosition();
this._triggerElement?.setAttribute('aria-expanded', 'true');
if (this._willOpen.emit()) {
this._state = 'opening';
this._setMenuPosition();
this._triggerElement?.setAttribute('aria-expanded', 'true');

// Starting from breakpoint medium, disable scroll
if (!isBreakpoint('medium')) {
this._scrollHandler.disableScroll();
// Starting from breakpoint medium, disable scroll
if (!isBreakpoint('medium')) {
this._scrollHandler.disableScroll();
}
}
}

Expand All @@ -154,9 +143,10 @@ export class SbbMenu extends SlotChildObserver(LitElement) {
return;
}

this._willClose.emit();
this._state = 'closing';
this._triggerElement?.setAttribute('aria-expanded', 'false');
if (this._willClose.emit()) {
this._state = 'closing';
this._triggerElement?.setAttribute('aria-expanded', 'false');
}
}

/**
Expand Down
30 changes: 30 additions & 0 deletions src/components/navigation/navigation/navigation.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,4 +323,34 @@ describe('sbb-navigation', () => {
expect(element).to.have.attribute('data-state', 'opened');
expect(section).to.have.attribute('data-state', 'closed');
});

it('does not open if prevented', async () => {
const willOpenEventSpy = new EventSpy(SbbNavigation.events.willOpen);

element.addEventListener(SbbNavigation.events.willOpen, (ev) => ev.preventDefault());
element.open();

await waitForCondition(() => willOpenEventSpy.events.length === 1);
expect(willOpenEventSpy.count).to.be.equal(1);
await waitForLitRender(element);

expect(element).to.have.attribute('data-state', 'closed');
});

it('does not close if prevented', async () => {
const didOpenEventSpy = new EventSpy(SbbNavigation.events.didOpen);
const willCloseEventSpy = new EventSpy(SbbNavigation.events.willClose);

element.open();
await waitForCondition(() => didOpenEventSpy.events.length === 1);
await waitForLitRender(element);

element.addEventListener(SbbNavigation.events.willClose, (ev) => ev.preventDefault());
element.close();

await waitForCondition(() => willCloseEventSpy.events.length === 1);
await waitForLitRender(element);

expect(element).to.have.attribute('data-state', 'opened');
});
});
26 changes: 14 additions & 12 deletions src/components/navigation/navigation/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ let nextId = 0;
* It displays a navigation menu, wrapping one or more `sbb-navigation-*` components.
*
* @slot - Use the unnamed slot to add `sbb-navigation-action` elements into the sbb-navigation menu.
* @event {CustomEvent<void>} willOpen - Emits whenever the `sbb-navigation` begins the opening transition.
* @event {CustomEvent<void>} willOpen - Emits whenever the `sbb-navigation` begins the opening transition. Can be canceled.
* @event {CustomEvent<void>} didOpen - Emits whenever the `sbb-navigation` is opened.
* @event {CustomEvent<void>} willClose - Emits whenever the `sbb-navigation` begins the closing transition.
* @event {CustomEvent<void>} willClose - Emits whenever the `sbb-navigation` begins the closing transition. Can be canceled.
* @event {CustomEvent<void>} didClose - Emits whenever the `sbb-navigation` is closed.
*/
@customElement('sbb-navigation')
Expand Down Expand Up @@ -137,13 +137,14 @@ export class SbbNavigation extends UpdateScheduler(LitElement) {
return;
}

this._willOpen.emit();
this._state = 'opening';
this.startUpdate();
if (this._willOpen.emit()) {
this._state = 'opening';
this.startUpdate();

// Disable scrolling for content below the navigation
this._scrollHandler.disableScroll();
this._triggerElement?.setAttribute('aria-expanded', 'true');
// Disable scrolling for content below the navigation
this._scrollHandler.disableScroll();
this._triggerElement?.setAttribute('aria-expanded', 'true');
}
}

/**
Expand All @@ -154,10 +155,11 @@ export class SbbNavigation extends UpdateScheduler(LitElement) {
return;
}

this._willClose.emit();
this._state = 'closing';
this.startUpdate();
this._triggerElement?.setAttribute('aria-expanded', 'false');
if (this._willClose.emit()) {
this._state = 'closing';
this.startUpdate();
this._triggerElement?.setAttribute('aria-expanded', 'false');
}
}

// Removes trigger click listener on trigger change.
Expand Down
Loading

0 comments on commit 5738d33

Please sign in to comment.