Skip to content

Commit

Permalink
fix(dialog): not showing if opened before connected
Browse files Browse the repository at this point in the history
Fixes #4728

PiperOrigin-RevId: 559157443
  • Loading branch information
asyncLiz authored and copybara-github committed Aug 22, 2023
1 parent 87af9aa commit d25c5e9
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 4 deletions.
88 changes: 88 additions & 0 deletions dialog/dialog_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('<md-dialog>', () => {
throw new Error('Failed to query rendered <md-dialog>');
}

disableDialogAnimations(dialog);
const harness = new DialogHarness(dialog);
const dialogElement = dialog.shadowRoot?.querySelector('dialog');
if (!dialogElement) {
Expand Down Expand Up @@ -127,6 +128,7 @@ describe('<md-dialog>', () => {
contentElement.click();
expect(isClosing).not.toHaveBeenCalled();
dialogElement.click();
await env.waitForStability();
expect(isClosing).toHaveBeenCalled();
});

Expand Down Expand Up @@ -175,4 +177,90 @@ describe('<md-dialog>', () => {
.withContext('dialog.returnValue after close event canceled')
.toBe(prevReturnValue);
});

it('should open on connected if opened before connected to DOM', async () => {
const openListener = jasmine.createSpy('openListener');
const dialog = document.createElement('md-dialog');
disableDialogAnimations(dialog);
dialog.addEventListener('open', openListener);
dialog.open = true;
expect(openListener)
.withContext('should not trigger open before connected')
.not.toHaveBeenCalled();

const root = env.render(html``);
root.appendChild(dialog);
await env.waitForStability();
expect(openListener)
.withContext('opens after connecting')
.toHaveBeenCalled();
});

it('should not open on connected if opened, but closed before connected to DOM',
async () => {
const openListener = jasmine.createSpy('openListener');
const dialog = document.createElement('md-dialog');
disableDialogAnimations(dialog);
dialog.addEventListener('open', openListener);
dialog.open = true;
await env.waitForStability();
dialog.open = false;
const root = env.render(html``);
root.appendChild(dialog);
await env.waitForStability();
expect(openListener)
.withContext('should not open on connected since close was called')
.not.toHaveBeenCalled();
});

it('should not open on connected if opened before connection but closed after',
async () => {
const openListener = jasmine.createSpy('openListener');
const dialog = document.createElement('md-dialog');
disableDialogAnimations(dialog);
dialog.addEventListener('open', openListener);
dialog.open = true;
const root = env.render(html``);
root.appendChild(dialog);
dialog.open = false;
await env.waitForStability();
expect(openListener)
.withContext(
'should not open on connected since close was called before open could complete')
.not.toHaveBeenCalled();
});

it('should not dispatch close if closed while disconnected', async () => {
const {harness, root} = await setupTest();
await harness.element.show();

const closeListener = jasmine.createSpy('closeListener');
harness.element.addEventListener('close', closeListener);
harness.element.remove();
await env.waitForStability();

expect(closeListener)
.withContext('should not trigger close when disconnected')
.not.toHaveBeenCalled();

await harness.element.close();
expect(closeListener)
.withContext('should not trigger close when disconnected')
.not.toHaveBeenCalled();

root.appendChild(harness.element);
await env.waitForStability();
expect(closeListener)
.withContext('should not trigger close when disconnected')
.not.toHaveBeenCalled();
});
});

function disableDialogAnimations(dialog: MdDialog) {
dialog.getOpenAnimation = () => {
return {};
};
dialog.getCloseAnimation = () => {
return {};
};
}
46 changes: 42 additions & 4 deletions dialog/internal/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export class Dialog extends LitElement {
getCloseAnimation = () => DIALOG_DEFAULT_CLOSE_ANIMATION;

private isOpen = false;
private isOpening = false;
// getIsConnectedPromise() immediately sets the resolve property.
private isConnectedPromiseResolve!: () => void;
private isConnectedPromise = this.getIsConnectedPromise();
@query('dialog') private readonly dialog!: HTMLDialogElement|null;
@query('.scrim') private readonly scrim!: HTMLDialogElement|null;
@query('.container') private readonly container!: HTMLDialogElement|null;
Expand Down Expand Up @@ -122,8 +126,15 @@ export class Dialog extends LitElement {
* `opened` event was fired.
*/
async show() {
const {dialog, container} = this;
if (!dialog || !container || dialog.open) {
this.isOpening = true;
// Dialogs can be opened before being attached to the DOM, so we need to
// wait until we're connected before calling `showModal()`.
await this.isConnectedPromise;
await this.updateComplete;
const dialog = this.dialog!;
// Check if already opened or if `dialog.close()` was called while awaiting.
if (dialog.open || !this.isOpening) {
this.isOpening = false;
return;
}

Expand All @@ -148,6 +159,7 @@ export class Dialog extends LitElement {

await this.animateDialog(this.getOpenAnimation());
this.dispatchEvent(new Event('opened'));
this.isOpening = false;
}

/**
Expand All @@ -161,8 +173,18 @@ export class Dialog extends LitElement {
* `closed` event was fired.
*/
async close(returnValue = this.returnValue) {
const {dialog, container} = this;
if (!dialog || !container || !dialog.open) {
this.isOpening = false;
if (!this.isConnected) {
// Disconnected dialogs do not fire close events or animate.
this.open = false;
return;
}

await this.updateComplete;
const dialog = this.dialog!;
// Check if already closed or if `dialog.show()` was called while awaiting.
if (!dialog.open || this.isOpening) {
this.open = false;
return;
}

Expand All @@ -181,6 +203,16 @@ export class Dialog extends LitElement {
this.dispatchEvent(new Event('closed'));
}

override connectedCallback() {
super.connectedCallback();
this.isConnectedPromiseResolve();
}

override disconnectedCallback() {
super.disconnectedCallback();
this.isConnectedPromise = this.getIsConnectedPromise();
}

protected override render() {
const scrollable =
this.open && !(this.isAtScrollTop && this.isAtScrollBottom);
Expand Down Expand Up @@ -354,4 +386,10 @@ export class Dialog extends LitElement {
this.isAtScrollBottom = isIntersecting;
}
}

private getIsConnectedPromise() {
return new Promise<void>(resolve => {
this.isConnectedPromiseResolve = resolve;
});
}
}

0 comments on commit d25c5e9

Please sign in to comment.