Skip to content

Commit

Permalink
Feature: ability to prevent modal close (#953)
Browse files Browse the repository at this point in the history
* set to flex grow

* ability to prevent close + docs + tests

* rename to forceClose

* correct comment

---------

Co-authored-by: Mads Rasmussen <[email protected]>
  • Loading branch information
nielslyngsoe and madsrasmussen authored Nov 18, 2024
1 parent 5a1620f commit ed30b6d
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 20 deletions.
13 changes: 3 additions & 10 deletions packages/uui-modal/lib/uui-modal-sidebar.element.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { css, html, PropertyValueMap } from 'lit';
import { property } from 'lit/decorators.js';
import { UUIModalCloseEvent, UUIModalElement } from './uui-modal.element';
import { UUIModalElement } from './uui-modal.element';
import { defineElement } from '@umbraco-ui/uui-base/lib/registration';

export type UUIModalSidebarSize = 'small' | 'medium' | 'large' | 'full';
Expand All @@ -13,11 +13,6 @@ export class UUIModalSidebarElement extends UUIModalElement {
@property({ reflect: true })
size: UUIModalSidebarSize = 'full';

constructor() {
super();
this.addEventListener(UUIModalCloseEvent, this.#onClose.bind(this));
}

protected firstUpdated(
_changedProperties: Map<string | number | symbol, unknown>,
): void {
Expand All @@ -44,16 +39,14 @@ export class UUIModalSidebarElement extends UUIModalElement {
return this._dialogElement?.getBoundingClientRect().width ?? 0;
}

#onClose(event: Event) {
event.preventDefault();

forceClose() {
if (this.isClosing) return;

this.isClosing = true;
this.style.setProperty('--uui-modal-offset', -this.#getWidth + 'px');

setTimeout(() => {
this._closeModal();
super.forceClose();
}, this.transitionDuration);
}

Expand Down
18 changes: 14 additions & 4 deletions packages/uui-modal/lib/uui-modal.element.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { LitElement, css } from 'lit';
import { property, query } from 'lit/decorators.js';

export const UUIModalOpenEvent = 'uui:modal-open';
export const UUIModalCloseEvent = 'uui:modal-close';
export const UUIModalCloseEndEvent = 'uui:modal-close-end';

export class UUIModalElement extends LitElement {
@query('dialog')
protected _dialogElement?: HTMLDialogElement;
Expand Down Expand Up @@ -46,12 +49,17 @@ export class UUIModalElement extends LitElement {
event?.preventDefault();
event?.stopImmediatePropagation();

const openEvent = new CustomEvent('open', {
const openEvent = new CustomEvent(UUIModalOpenEvent, {
cancelable: true,
});
// TODO: get rid of legacy 'open'-event sometime in the future. [NL]
const legacyOpenEvent = new CustomEvent('open', {
cancelable: true,
});

this.dispatchEvent(openEvent);
if (openEvent.defaultPrevented) return;
this.dispatchEvent(legacyOpenEvent);
if (openEvent.defaultPrevented || legacyOpenEvent.defaultPrevented) return;

this._openModal();
};
Expand All @@ -67,7 +75,7 @@ export class UUIModalElement extends LitElement {

if (closeEvent.defaultPrevented) return;

this._closeModal();
this.forceClose();
};

protected _openModal() {
Expand All @@ -76,12 +84,14 @@ export class UUIModalElement extends LitElement {
this._dialogElement?.addEventListener('cancel', this.close);
}

protected _closeModal() {
public forceClose() {
this.isClosing = true;
this.isOpen = false;
this._dialogElement?.close();

// TODO: get rid of legacy 'close-end'-event sometime in the future. [NL]
this.dispatchEvent(new CustomEvent('close-end'));
this.dispatchEvent(new CustomEvent(UUIModalCloseEndEvent));

this.remove();
}
Expand Down
10 changes: 5 additions & 5 deletions packages/uui-modal/lib/uui-modal.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,18 @@ Example from the sidebar:

All events from the modal base can be cancelled, thus preventing them from executing code, which enables you to customize the behavior.

#### `open`
#### `uui:modal-open`

Dispatched on first render. This will set open to true and show the modal. Can be cancelled if you want to prevent the modal from opening. But then you'll have to manually call `_openModal()` when you want to open the modal.

#### `close`
#### `uui:modal-close`

Dispatched when the modal is closed. Can be cancelled if you want to prevent the modal from closing. But then you'll have to manually call `_closeModal()` when you want to close the modal.
Dispatched when the modal is closed. Can be cancelled if you want to prevent the modal from closing. But then you'll have to manually call `forceClose()` when you want to close the modal.
This is used in the `uui-modal-sidebar` to wait for the animation to finish before removing the modal from the DOM.

#### `close-end`
#### `uui:modal-close-end`

This event is triggered before removing the component from the DOM, either after animations or delays or when `_closeModal()` is manually invoked.
This event is triggered before removing the component from the DOM, either after animations or delays or when `forceClose()` is manually invoked.

### CSS Variables

Expand Down
55 changes: 54 additions & 1 deletion packages/uui-modal/lib/uui-modal.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { html, fixture, expect } from '@open-wc/testing';
import { html, fixture, expect, oneEvent } from '@open-wc/testing';
import {
UUIModalDialogElement,
UUIModalSidebarElement,
UUIModalContainerElement,
UUIModalCloseEvent,
UUIModalCloseEndEvent,
} from '.';

describe('UUIModalContainerElement', () => {
Expand Down Expand Up @@ -53,4 +55,55 @@ describe('UUIModalSidebarElement', () => {
it('passes the a11y audit', async () => {
await expect(element).shadowDom.to.be.accessible();
});

describe('properties', () => {
it('has a size property', () => {
expect(element).to.have.property('size');
});

it('has a index property', () => {
expect(element).to.have.property('index');
});

it('has a uniqueIndex property', () => {
expect(element).to.have.property('uniqueIndex');
});

it('has a transitionDuration property', () => {
expect(element).to.have.property('transitionDuration');
});
});

it('can close', async () => {
expect(element.isOpen).to.be.true;

const listener = oneEvent(element, UUIModalCloseEvent);

element.close();

const event = await listener;
expect(event).to.exist;

const endListener = oneEvent(element, UUIModalCloseEndEvent);

const endEvent = await endListener;
expect(endEvent).to.exist;

expect(element.isOpen).to.be.false;
});

it('can have a prevented close', async () => {
expect(element.isOpen).to.be.true;

expect(element.isClosing).to.be.false;
element.addEventListener(UUIModalCloseEvent, e => e.preventDefault());
const closeListener = oneEvent(element, UUIModalCloseEvent);
element.close();

const closeEvent = await closeListener;
expect(closeEvent).to.exist;

expect(element.isClosing).to.be.false;
expect(element.isOpen).to.be.true;
});
});

0 comments on commit ed30b6d

Please sign in to comment.