@primer/view-components
Version:
ViewComponents for the Primer Design System
133 lines (132 loc) • 7.25 kB
JavaScript
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _DialogHelperElement_instances, _DialogHelperElement_abortController, _DialogHelperElement_handleDialogOpenAttribute;
function dialogInvokerButtonHandler(event) {
const target = event.target;
const button = target?.closest('button');
if (!button || button.hasAttribute('disabled') || button.getAttribute('aria-disabled') === 'true')
return;
// If the user is clicking a valid dialog trigger
let dialogId = button?.getAttribute('data-show-dialog-id');
if (dialogId) {
const dialog = document.getElementById(dialogId);
if (dialog instanceof HTMLDialogElement) {
dialog.showModal();
// A buttons default behaviour in some browsers it to send a pointer event
// If the behaviour is allowed through the dialog will be shown but then
// quickly hidden- as if it were never shown. This prevents that.
event.preventDefault();
// In some older browsers, such as Chrome 122, when a top layer element (such as a dialog)
// opens from within a popover, the "hide all popovers" internal algorithm runs, hiding
// any popover that is currently open, regardless of whether or not another top layer element,
// such as a <dialog> is nested inside.
// See https://github.com/whatwg/html/issues/9998.
// This is fixed by https://github.com/whatwg/html/pull/10116, but while we still support browsers
// that present this bug, we must undo the work they did to hide ancestral popovers of the dialog:
let node = button;
let fixed = false;
while (node) {
node = node.parentElement?.closest('[popover]:not(:popover-open)');
if (node && node.popover === 'auto') {
node.classList.add('dialog-inside-popover-fix');
node.popover = 'manual';
node.showPopover();
fixed = true;
}
}
if (fixed) {
// We need to re-open the dialog as modal, and also ensure no close event listeners
// are trying to act on the close
/* eslint-disable-next-line no-restricted-syntax */
dialog.addEventListener('close', e => e.stopImmediatePropagation(), { once: true });
dialog.close();
dialog.showModal();
dialog.addEventListener('close', () => {
for (const el of dialog.ownerDocument.querySelectorAll('.dialog-inside-popover-fix')) {
if (el.contains(dialog)) {
el.classList.remove('dialog-inside-popover-fix');
el.popover = 'auto';
el.showPopover();
}
}
}, { once: true });
}
}
}
dialogId = button.getAttribute('data-close-dialog-id') || button.getAttribute('data-submit-dialog-id');
if (dialogId) {
const dialog = document.getElementById(dialogId);
if (dialog instanceof HTMLDialogElement && dialog.open) {
dialog.close();
}
}
}
export class DialogHelperElement extends HTMLElement {
constructor() {
super(...arguments);
_DialogHelperElement_instances.add(this);
_DialogHelperElement_abortController.set(this, null);
}
get dialog() {
return this.querySelector('dialog');
}
connectedCallback() {
const { signal } = (__classPrivateFieldSet(this, _DialogHelperElement_abortController, new AbortController(), "f"));
document.addEventListener('click', dialogInvokerButtonHandler, true);
document.addEventListener('click', this, { signal });
this.ownerDocument.body.style.setProperty('--dialog-scrollgutter', `${window.innerWidth - this.ownerDocument.body.clientWidth}px`);
new MutationObserver(records => {
for (const record of records) {
if (record.target === this.dialog) {
__classPrivateFieldGet(this, _DialogHelperElement_instances, "m", _DialogHelperElement_handleDialogOpenAttribute).call(this);
}
}
}).observe(this, { subtree: true, attributeFilter: ['open'] });
__classPrivateFieldGet(this, _DialogHelperElement_instances, "m", _DialogHelperElement_handleDialogOpenAttribute).call(this);
}
disconnectedCallback() {
__classPrivateFieldGet(this, _DialogHelperElement_abortController, "f")?.abort();
}
handleEvent(event) {
const target = event.target;
const dialog = this.dialog;
// The click target _must_ be the dialog element itself, and not elements underneath or inside.
if (target !== dialog || !dialog?.open)
return;
// If the dialog contains a form, do not close the dialog when clicking outside of the dialog
if (dialog.querySelector('form'))
return;
const rect = dialog.getBoundingClientRect();
const clickWasInsideDialog = rect.top <= event.clientY &&
event.clientY <= rect.top + rect.height &&
rect.left <= event.clientX &&
event.clientX <= rect.left + rect.width;
if (!clickWasInsideDialog) {
dialog.close();
}
}
}
_DialogHelperElement_abortController = new WeakMap(), _DialogHelperElement_instances = new WeakSet(), _DialogHelperElement_handleDialogOpenAttribute = function _DialogHelperElement_handleDialogOpenAttribute() {
if (!this.dialog)
return;
// We don't want to show the Dialog component as non-modal
if (this.dialog.matches('[open]:not(:modal)')) {
// eslint-disable-next-line no-restricted-syntax
this.dialog.addEventListener('close', e => e.stopImmediatePropagation(), { once: true });
this.dialog.close();
this.dialog.showModal();
}
};
if (!window.customElements.get('dialog-helper')) {
window.DialogHelperElement = DialogHelperElement;
window.customElements.define('dialog-helper', DialogHelperElement);
}