Skip to content
Open
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
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The enhancements are:
2. Wrap your `<dialog>` with `<dialog-utils>`.
3. Add a trigger and close `<button>` with your desired markup (e.g. nested icon, attributes, translated text, etc.). The buttons need to be made identifiable with `commandfor` and `command` as per the [specification](https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element), if you want to benefit from the ease-of-use polyfills. The Web Component leaves positioning and aesthetics of the buttons to the outside context.

### Example
#### Basic example

```
<button commandfor="my-dialog" command="show-modal">Open my dialog</button>
Expand All @@ -39,3 +39,31 @@ The enhancements are:
</dialog>
</dialog-utils>
```

### Events

<dl>
<dt><code>show</code></dt>
<dd>The component monkey patches the <code>show()</code> and <code>showModal()</code> methods to emit a <code>show</code> event that includes information about whether the dialog is displayed as a modal via <code>(bool) event.detail.isModal</code>.</dd>
</dl>

### Extending the component

The component is exported to allow subclassing and extending its methods.

#### Example

```
import {DialogUtils} from '@webfactoryde/dialog-utils';

class MyCustomDialogUtils extends DialogUtils {
onShow(event) {
// call the parent method
super.onShow?.(event);

// do your custom thing
}
}

customElements.define('my-custom-dialog-utils', MyCustomDialogUtils);
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@webfactoryde/dialog-utils",
"version": "0.1.0",
"version": "0.2.0",
"description": "Web Component with progressive enhancements for the HTML <dialog> element",
"main": "src/dialog-utils.js",
"repository": {
Expand Down
61 changes: 60 additions & 1 deletion src/dialog-utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class DialogUtils extends HTMLElement {
export class DialogUtils extends HTMLElement {
static get observedAttributes() {
return ["autofocus-target", "autoopen"];
}
Expand Down Expand Up @@ -28,8 +28,12 @@ class DialogUtils extends HTMLElement {
}

this.dialog = this.querySelector('dialog');
Copy link
Preview

Copilot AI Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You assume a <dialog> is always present; add a null check or guard so that missing dialogs don’t cause runtime errors.

Suggested change
this.dialog = this.querySelector('dialog');
this.dialog = this.querySelector('dialog');
if (!this.dialog) {
console.warn('DialogUtils: No <dialog> element found. Initialization aborted.');
return;
}

Copilot uses AI. Check for mistakes.


this.dialog.addEventListener('show', this.onShow.bind(this));
Copy link
Preview

Copilot AI Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event listeners are added in connectedCallback but never removed in disconnectedCallback, leading to potential memory leaks; consider cleaning them up.

Copilot uses AI. Check for mistakes.

this.dialog.addEventListener('close', this.onClose.bind(this));

this.monkeyPatchShowMethod();
this.monkeyPatchShowModalMethod();
this.polyfillClosedByAny();
this.polyfillInvokerCommands();
this.handleAutofocus();
Expand All @@ -39,10 +43,52 @@ class DialogUtils extends HTMLElement {
if (this._observer) this._observer.disconnect();
}

onShow(e) {
if (e.detail.isModal) {
this.disablePageScroll();
}
}

onClose() {
this.resetIframes();
this.enablePageScroll();
}

/**
* Patch showModal() to emit a 'show' event, indicate that the dialog is modal
*/
monkeyPatchShowModalMethod() {
const origShowModal = this.dialog.showModal.bind(this.dialog);
this.dialog.showModal = (...args) => {
const result = origShowModal(...args);
this.dialog.dispatchEvent(new CustomEvent('show', {
detail: {
isModal: true,
},
bubbles: true,
}));
return result;
};
}

/**
* Patch show() to emit a 'show' event, indicate that the dialog is non-modal
*/
monkeyPatchShowMethod() {
const origShow = this.dialog.show.bind(this.dialog);
this.dialog.show = (...args) => {
const result = origShow(...args);
this.dialog.dispatchEvent(new CustomEvent('show', {
detail: {
isModal: false,
},
bubbles: true,
}));
return result;
};
}


/**
* Polyfill for light dismissal via closedby="any"
*
Expand Down Expand Up @@ -122,6 +168,19 @@ class DialogUtils extends HTMLElement {
}
}

enablePageScroll() {
if (this.origBodyOverflow) {
document.body.style.overflow = this.origBodyOverflow;
this.origBodyOverflow = null;
}
}

disablePageScroll() {
this.origBodyOverflow = document.body.style.overflow;

document.body.style.overflow = 'hidden';
}

/**
* Ensure that any interactive iframe content (i.e. embedded video) stops playing when the dialog is closed
*/
Expand Down