Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ninety-maps-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashicorp/design-system-components": patch
---

`Modal` - Fixed destructor when the component is removed while open
1 change: 0 additions & 1 deletion packages/components/src/components/hds/modal/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
...attributes
aria-labelledby={{this.id}}
{{did-insert this.didInsert}}
{{will-destroy this.willDestroyNode}}
{{! @glint-expect-error - https://github.com/josemarluedke/ember-focus-trap/issues/86 }}
{{focus-trap isActive=this._isOpen focusTrapOptions=(hash onDeactivate=this.onDismiss)}}
>
Expand Down
24 changes: 12 additions & 12 deletions packages/components/src/components/hds/modal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ export default class HdsModal extends Component<HdsModalSignature> {
super(owner, args);

registerDestructor(this, (): void => {
// if the <dialog> is removed from the dom while open we emulate the close event
if (this._element && this._isOpen) {
this._element.dispatchEvent(new Event('close'));

this._element.removeEventListener(
'close',
// eslint-disable-next-line @typescript-eslint/unbound-method
this.registerOnCloseCallback,
true
);
}

document.removeEventListener('click', this._clickHandler, true);
});
}
Expand Down Expand Up @@ -201,18 +213,6 @@ export default class HdsModal extends Component<HdsModalSignature> {
});
}

@action
willDestroyNode(): void {
if (this._element) {
this._element.removeEventListener(
'close',
// eslint-disable-next-line @typescript-eslint/unbound-method
this.registerOnCloseCallback,
true
);
}
}

@action
open(): void {
// Make modal dialog visible using the native `showModal` method
Expand Down
2 changes: 2 additions & 0 deletions showcase/app/controllers/components/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { tracked } from '@glimmer/tracking';
export default class ModalController extends Controller {
@tracked basicModalActive = false;
@tracked longModalActive = false;
@tracked deactivateModalOnCloseActive = false;
@tracked deactivateModalOnDestroyActive = false;
@tracked formModalActive = false;
@tracked tabsModalActive = false;
@tracked dropdownModalActive = false;
Expand Down
62 changes: 62 additions & 0 deletions showcase/app/templates/components/modal.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,68 @@
</Hds::Modal>
{{/if}}

<button type="button" {{on "click" (fn this.activateModal "deactivateModalOnCloseActive")}}>Deactivated with `onClose`</button>

{{#if this.deactivateModalOnCloseActive}}
<Hds::Modal
id="deactivate-modal-on-close"
@onClose={{fn this.deactivateModal "deactivateModalOnCloseActive"}}
as |M|
>
<M.Header>
Modal title
</M.Header>
<M.Body>
<p class="hds-typography-body-300 hds-foreground-primary">Clicking the "confirm" button executes the
<code>F.close</code>
method.</p>
<p class="hds-typography-body-200 hds-foreground-primary" {{style margin-top="12px"}}>This is equivalent to a
manual dismiss (<code>Esc</code>
key, click outsite, click dismiss button) because they're all calling the same function, which invokes the
native
<code>close()</code>
method of the
<code>Dialog</code>
HTML element, who then will cause the
<code>willDestroyNode</code>
action to execute.</p>
</M.Body>
<M.Footer as |F|>
<Hds::Button type="button" @text="Confirm" {{on "click" F.close}} />
</M.Footer>
</Hds::Modal>
{{/if}}

<button type="button" {{on "click" (fn this.activateModal "deactivateModalOnDestroyActive")}}>Deactivated on destroy</button>

{{#if this.deactivateModalOnDestroyActive}}
<Hds::Modal
id="deactivate-modal-on-destruction"
@onClose={{fn this.deactivateModal "deactivateModalOnDestroyActive"}}
as |M|
>
<M.Header>
Modal title
</M.Header>
<M.Body>
<p class="hds-typography-body-300 hds-foreground-primary">Clicking the "confirm" button will remove the modal
from the DOM.</p>
<p class="hds-typography-body-200 hds-foreground-primary" {{style margin-top="12px"}}>This is not equivalent to
a manual dismiss (<code>Esc</code>
key, click outsite, click dismiss button) because it will trigger directly the
<code>willDestroyNode</code>
action.</p>
</M.Body>
<M.Footer>
<Hds::Button
type="button"
@text="Confirm"
{{on "click" (fn this.deactivateModal "deactivateModalOnDestroyActive")}}
/>
</M.Footer>
</Hds::Modal>
{{/if}}

<br />
<br />

Expand Down
20 changes: 20 additions & 0 deletions showcase/tests/integration/components/hds/modal/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,26 @@ module('Integration | Component | hds/modal/index', function (hooks) {
assert.ok(closed);
});

test('it should call `onClose` function if the component is destroyed while the modal is open', async function (assert) {
let closed = false;
this.set('onClose', () => (closed = true));

this.set('showModal', true);

await render(
hbs`
{{#if this.showModal}}
<Hds::Modal id="test-modal" @onClose={{this.onClose}} as |M|>
<M.Header>Title</M.Header>
</Hds::Modal>
{{/if}}
`,
);

this.set('showModal', false);
assert.ok(closed);
});

// ASSERTIONS

test('it should throw an assertion if an incorrect value for @size is provided', async function (assert) {
Expand Down
Loading