Polyfill for the HTMLDialogElement closedBy attribute (fork with light-dismiss race condition fix)
npm install @fractaledmind/dialog-closedby-polyfillA polyfill for the HTMLDialogElement closedby attribute, providing control over how dialogs can be dismissed.
> Note: The HTML attribute is closedby (lowercase), while the JavaScript property is closedBy (camelCase).
- 🎯 Implements the closedby attribute for both modal and non-modal elements
- 🔒 Three closing modes: any, closerequest, and none
- 🚀 Zero dependencies
- 📦 TypeScript support included
- 🌐 Works in all modern browsers with support
- ✨ Automatically detects native support
``bash`
npm install @fractaledmind/dialog-closedby-polyfill
The polyfill is automatically applied when imported:
`javascript
// ES Modules (auto-applies if needed)
import "@fractaledmind/dialog-closedby-polyfill";
// CommonJS (auto-applies if needed)
require("@fractaledmind/dialog-closedby-polyfill");
`
If you need more control over when the polyfill is applied:
`javascript
import { apply, isSupported } from "@fractaledmind/dialog-closedby-polyfill";
if (!isSupported()) {
apply();
}
`
Or include it via CDN:
`html
type="module"
src="https://unpkg.com/@fractaledmind/dialog-closedby-polyfill"
>
`
https://tak-dcxi.github.io/github-pages-demo/closedby.html
The closedby attribute controls how a dialog (modal or non-modal) can be dismissed:
| closedby value | ESC key | Backdrop click | close() method |"any"
| ---------------- | ------- | -------------- | ---------------- |
| | ✅ | ✅ | ✅ |"closerequest"
| | ✅ | ❌ | ✅ |"none"
| | ❌ | ❌ | ✅ |
The dialog can be closed by:
- Pressing the ESC key
- Clicking the backdrop
- Calling the close() method
` This dialog can be closed in any wayhtml`
id="dialog-any"
closedby="any"
aria-labelledby="dialog-any-title"
autofocus
>
any
The dialog can be closed by:
- Pressing the ESC key
- Calling the close() method
- ❌ Clicking the backdrop (disabled)
` This dialog cannot be closed by clicking outsidehtml`
id="dialog-closerequest"
closedby="closerequest"
aria-labelledby="dialog-closerequest-title"
autofocus
>
closerequest
The dialog can only be closed by:
- Calling the close() method
- ❌ Pressing the ESC key (disabled)
- ❌ Clicking the backdrop (disabled)
` This dialog can only be closed programmaticallyhtml`
id="dialog-none"
closedby="none"
aria-labelledby="dialog-none-title"
autofocus
>
none
You can also set the attribute via JavaScript:
`javascript
const dialog = document.querySelector("dialog");
// Using setAttribute
dialog.setAttribute("closedby", "none");
// Using the property (when polyfill is loaded)
dialog.closedBy = "closerequest";
`
The closedby attribute can be changed while the dialog is open:
`javascript
const dialog = document.querySelector("dialog");
dialog.showModal();
// Change behavior while dialog is open
setTimeout(() => {
dialog.closedBy = "none"; // Now only closeable via close() method
}, 3000);
`
This polyfill works in all browsers that support the native
Native closedby support:
- Chrome 134+
- Safari: Not implemented yet
- Firefox: Not implemented yet
- Edge: 134+
Dialog element support (required for polyfill):
- Chrome 37+
- Firefox 98+
- Safari 15.4+
- Edge 79+
> Note: For browsers without native closedby support, this polyfill provides the functionality. For older browsers without
#### isSupported(): boolean
Check if the browser natively supports the closedby attribute.
`javascript
import { isSupported } from "@fractaledmind/dialog-closedby-polyfill";
if (isSupported()) {
console.log("Native closedby support available!");
}
`
#### isPolyfilled(): boolean
Check if the polyfill has already been applied.
`javascript
import { isPolyfilled } from "@fractaledmind/dialog-closedby-polyfill";
if (isPolyfilled()) {
console.log("Polyfill has been applied");
}
`
#### apply(): void
Manually apply the polyfill. This is called automatically when importing the main module.
`javascript
import { apply } from "@fractaledmind/dialog-closedby-polyfill";
apply(); // Apply the polyfill
`
TypeScript definitions are included. The polyfill extends the HTMLDialogElement interface:
`typescript`
interface HTMLDialogElement {
closedBy: "any" | "closerequest" | "none";
}
The polyfill works by:
1. Extending HTMLDialogElement: Adds the closedby property to dialog elementsshowModal()
1. Intercepting and show(): Sets up event listeners when a dialog is opened (modal or non-modal)keydown
1. Handling Events:
- event for ESC key detectionclick
- event on the dialog for backdrop clickscancel
- event dispatch and prevention based on closedby value
1. Observing Changes: Uses MutationObserver to watch for attribute changes
1. Cleanup: Removes event listeners when dialog is closed
This polyfill aims to match the native implementation as closely as possible. However, there might be minor differences in edge cases. Please report any discrepancies you find.
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the repository
2. Create your feature branch (git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature'
3. Commit your changes ()git push origin feature/amazing-feature
4. Push to the branch ()
5. Open a Pull Request
MIT License - see the LICENSE file for details
Declarative command/commandfor attributes to provide dialog button operations with markup only.
- GitHub: keithamus/invokers-polyfill
- Use case: Simplifies dialog controls without requiring JavaScript event handlers
` Contenthtml``
id="my-dialog"
closedby="closerequest"
aria-labelledby="my-dialog-heading"
autofocus
>
Heading
- MDN Documentation for closedBy
- Chrome Platform Status
- HTML Specification
This polyfill is inspired by the native implementation and the work of the web standards community.