UNPKG

@navikt/ds-react

Version:

React components from the Norwegian Labour and Welfare Administration.

954 lines (861 loc) 28.5 kB
/* eslint-disable no-var, object-shorthand */ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck export const needPolyfill = typeof window !== "undefined" && (window.HTMLDialogElement === undefined || navigator.userAgent.includes("jsdom")); // Copyright (c) 2013 The Chromium Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** * Dispatches the passed event to both an "on<type>" handler as well as via the * normal dispatch operation. Does not bubble. * * @param {!EventTarget} target * @param {!Event} event * @return {boolean} */ function safeDispatchEvent(target, event) { var check = "on" + event.type.toLowerCase(); if (typeof target[check] === "function") { target[check](event); } return target.dispatchEvent(event); } /** * Finds the nearest <dialog> from the passed element. * * @param {Element} el to search from * @return {HTMLDialogElement} dialog found */ function findNearestDialog(el) { while (el) { if (el.localName === "dialog") { return /** @type {HTMLDialogElement} */ el; } if (el.parentElement) { el = el.parentElement; } else if (el.parentNode) { el = el.parentNode.host; } else { el = null; } } return null; } /** * Blur the specified element, as long as it's not the HTML body element. * This works around an IE9/10 bug - blurring the body causes Windows to * blur the whole application. * * @param {Element} el to blur */ function safeBlur(el) { // Find the actual focused element when the active element is inside a shadow root while (el && el.shadowRoot && el.shadowRoot.activeElement) { el = el.shadowRoot.activeElement; } if (el && el.blur && el !== document.body) { el.blur(); } } /** * @param {!NodeList} nodeList to search * @param {Node} node to find * @return {boolean} whether node is inside nodeList */ function inNodeList(nodeList, node) { for (var i = 0; i < nodeList.length; ++i) { if (nodeList[i] === node) { return true; } } return false; } /** * @param {HTMLFormElement} el to check * @return {boolean} whether this form has method="dialog" */ function isFormMethodDialog(el) { if (!el || !el.hasAttribute("method")) { return false; } return el.getAttribute("method").toLowerCase() === "dialog"; } /** * @param {!DocumentFragment|!Element} hostElement * @return {?Element} */ function findFocusableElementWithin(hostElement) { // Note that this is 'any focusable area'. This list is probably not exhaustive, but the // alternative involves stepping through and trying to focus everything. var opts = ["button", "input", "keygen", "select", "textarea"]; var query = opts.map(function (el) { return el + ":not([disabled])"; }); // TODO(samthor): tabindex values that are not numeric are not focusable. query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled var target = hostElement.querySelector(query.join(", ")); if (!target && "attachShadow" in Element.prototype) { // If we haven't found a focusable target, see if the host element contains an element // which has a shadowRoot. // Recursively search for the first focusable item in shadow roots. var elems = hostElement.querySelectorAll("*"); for (var i = 0; i < elems.length; i++) { if (elems[i].tagName && elems[i].shadowRoot) { target = findFocusableElementWithin(elems[i].shadowRoot); if (target) { break; } } } } return target; } /** * Determines if an element is attached to the DOM. * @param {Element} element to check * @return {boolean} whether the element is in DOM */ function isConnected(element) { return element.isConnected || document.body.contains(element); } /** * @param {!Event} event * @return {?Element} */ function findFormSubmitter(event) { if (event.submitter) { return event.submitter; } var form = event.target; if (!(form instanceof HTMLFormElement)) { return null; } var submitter = dialogPolyfill.formSubmitter; if (!submitter) { var target = event.target; var root = ("getRootNode" in target && target.getRootNode()) || document; submitter = root.activeElement; } if (!submitter || submitter.form !== form) { return null; } return submitter; } /** * @param {!Event} event */ function maybeHandleSubmit(event) { if (event.defaultPrevented) { return; } var form = /** @type {!HTMLFormElement} */ event.target; // We'd have a value if we clicked on an imagemap. var value = dialogPolyfill.imagemapUseValue; var submitter = findFormSubmitter(event); if (value === null && submitter) { value = submitter.value; } // There should always be a dialog as this handler is added specifically on them, but check just // in case. var dialog = findNearestDialog(form); if (!dialog) { return; } // Prefer formmethod on the button. var formmethod = (submitter && submitter.getAttribute("formmethod")) || form.getAttribute("method"); if (formmethod !== "dialog") { return; } event.preventDefault(); if (value != null) { // nb. we explicitly check against null/undefined dialog.close(value); } else { dialog.close(); } } /** * @param {!HTMLDialogElement} dialog to upgrade * @constructor */ function dialogPolyfillInfo(dialog) { this.dialog_ = dialog; this.replacedStyleTop_ = false; this.openAsModal_ = false; // Set a11y role. Browsers that support dialog implicitly know this already. if (!dialog.hasAttribute("role")) { dialog.setAttribute("role", "dialog"); } dialog.show = this.show.bind(this); dialog.showModal = this.showModal.bind(this); dialog.close = this.close.bind(this); dialog.addEventListener("submit", maybeHandleSubmit, false); if (!("returnValue" in dialog)) { dialog.returnValue = ""; } if ("MutationObserver" in window) { var mo = new MutationObserver(this.maybeHideModal.bind(this)); mo.observe(dialog, { attributes: true, attributeFilter: ["open"] }); } else { // IE10 and below support. Note that DOMNodeRemoved etc fire _before_ removal. They also // seem to fire even if the element was removed as part of a parent removal. Use the removed // events to force downgrade (useful if removed/immediately added). var removed = false; var cb = function () { removed ? this.downgradeModal() : this.maybeHideModal(); removed = false; }.bind(this); var timeout: ReturnType<typeof setTimeout>; var delayModel = function (ev) { if (ev.target !== dialog) { return; } // not for a child element var cand = "DOMNodeRemoved"; removed |= ev.type.substr(0, cand.length) === cand; window.clearTimeout(timeout); timeout = window.setTimeout(cb, 0); }; ["DOMAttrModified", "DOMNodeRemoved", "DOMNodeRemovedFromDocument"].forEach( function (name) { dialog.addEventListener(name, delayModel); }, ); } // Note that the DOM is observed inside DialogManager while any dialog // is being displayed as a modal, to catch modal removal from the DOM. Object.defineProperty(dialog, "open", { set: this.setOpen.bind(this), get: dialog.hasAttribute.bind(dialog, "open"), }); this.backdrop_ = document.createElement("div"); this.backdrop_.className = "backdrop"; this.backdrop_.addEventListener( "mouseup", this.backdropMouseEvent_.bind(this), ); this.backdrop_.addEventListener( "mousedown", this.backdropMouseEvent_.bind(this), ); this.backdrop_.addEventListener("click", this.backdropMouseEvent_.bind(this)); } dialogPolyfillInfo.prototype = /** @type {HTMLDialogElement.prototype} */ { get dialog() { return this.dialog_; }, /** * Maybe remove this dialog from the modal top layer. This is called when * a modal dialog may no longer be tenable, e.g., when the dialog is no * longer open or is no longer part of the DOM. */ maybeHideModal: function () { if (this.dialog_.hasAttribute("open") && isConnected(this.dialog_)) { return; } this.downgradeModal(); }, /** * Remove this dialog from the modal top layer, leaving it as a non-modal. */ downgradeModal: function () { if (!this.openAsModal_) { return; } this.openAsModal_ = false; this.dialog_.style.zIndex = ""; // This won't match the native <dialog> exactly because if the user set top on a centered // polyfill dialog, that top gets thrown away when the dialog is closed. Not sure it's // possible to polyfill this perfectly. if (this.replacedStyleTop_) { this.dialog_.style.top = ""; this.replacedStyleTop_ = false; } // Clear the backdrop and remove from the manager. this.backdrop_.parentNode && this.backdrop_.parentNode.removeChild(this.backdrop_); dialogPolyfill.dm.removeDialog(this); }, /** * @param {boolean} value whether to open or close this dialog */ setOpen: function (value) { if (value) { this.dialog_.hasAttribute("open") || this.dialog_.setAttribute("open", ""); } else { this.dialog_.removeAttribute("open"); this.maybeHideModal(); // nb. redundant with MutationObserver } }, /** * Handles mouse events ('mouseup', 'mousedown', 'click') on the fake .backdrop element, redirecting them as if * they were on the dialog itself. * * @param {!Event} e to redirect */ backdropMouseEvent_: function (e) { if (!this.dialog_.hasAttribute("tabindex")) { // Clicking on the backdrop should move the implicit cursor, even if dialog cannot be // focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this // would not be needed - clicks would move the implicit cursor there. var fake = document.createElement("div"); this.dialog_.insertBefore(fake, this.dialog_.firstChild); fake.tabIndex = -1; fake.focus(); this.dialog_.removeChild(fake); } else { this.dialog_.focus(); } var redirectedEvent = document.createEvent("MouseEvents"); redirectedEvent.initMouseEvent( e.type, e.bubbles, e.cancelable, window, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget, ); this.dialog_.dispatchEvent(redirectedEvent); e.stopPropagation(); }, /** * Focuses on the first focusable element within the dialog. This will always blur the current * focus, even if nothing within the dialog is found. */ focus_: function () { // Find element with `autofocus` attribute, or fall back to the first form/tabindex control. var target = this.dialog_.querySelector("[autofocus]:not([disabled])"); if (!target && this.dialog_.tabIndex >= 0) { target = this.dialog_; } if (!target) { target = findFocusableElementWithin(this.dialog_); } safeBlur(document.activeElement); target && target.focus(); }, /** * Sets the zIndex for the backdrop and dialog. * * @param {number} dialogZ * @param {number} backdropZ */ updateZIndex: function (dialogZ, backdropZ) { if (dialogZ < backdropZ) { throw new Error("dialogZ should never be < backdropZ"); } this.dialog_.style.zIndex = dialogZ; this.backdrop_.style.zIndex = backdropZ; }, /** * Shows the dialog. If the dialog is already open, this does nothing. */ show: function () { if (!this.dialog_.open) { this.setOpen(true); this.focus_(); } }, /** * Show this dialog modally. */ showModal: function () { if (this.dialog_.hasAttribute("open")) { throw new Error( "Failed to execute 'showModal' on dialog: The element is already open, and therefore cannot be opened modally.", ); } if (!isConnected(this.dialog_)) { throw new Error( "Failed to execute 'showModal' on dialog: The element is not in a Document.", ); } if (!dialogPolyfill.dm.pushDialog(this)) { throw new Error( "Failed to execute 'showModal' on dialog: There are too many open modal dialogs.", ); } this.setOpen(true); this.openAsModal_ = true; // Optionally center vertically, relative to the current viewport. if (dialogPolyfill.needsCentering(this.dialog_)) { dialogPolyfill.reposition(this.dialog_); this.replacedStyleTop_ = true; } else { this.replacedStyleTop_ = false; } // Insert backdrop. this.dialog_.parentNode.insertBefore( this.backdrop_, this.dialog_.nextSibling, ); // Focus on whatever inside the dialog. this.focus_(); }, /** * Closes this HTMLDialogElement. This is optional vs clearing the open * attribute, however this fires a 'close' event. * * @param {string=} opt_returnValue to use as the returnValue */ close: function (opt_returnValue) { if (!this.dialog_.hasAttribute("open")) { throw new Error( "Failed to execute 'close' on dialog: The element does not have an 'open' attribute, and therefore cannot be closed.", ); } this.setOpen(false); // Leave returnValue untouched in case it was set directly on the element if (opt_returnValue !== undefined) { this.dialog_.returnValue = opt_returnValue; } // Triggering "close" event for any attached listeners on the <dialog>. var closeEvent = new window.CustomEvent("close", { bubbles: false, cancelable: false, }); safeDispatchEvent(this.dialog_, closeEvent); }, }; var dialogPolyfill = {}; dialogPolyfill.reposition = function (element) { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / 2; element.style.top = Math.max(scrollTop, topValue) + "px"; }; dialogPolyfill.isInlinePositionSetByStylesheet = function (element) { for (var i = 0; i < document.styleSheets.length; ++i) { var styleSheet = document.styleSheets[i]; var cssRules = null; // Some browsers throw on cssRules. try { cssRules = styleSheet.cssRules; } catch { /* empty */ } if (!cssRules) { continue; } for (var j = 0; j < cssRules.length; ++j) { var rule = cssRules[j]; var selectedNodes = null; // Ignore errors on invalid selector texts. try { selectedNodes = document.querySelectorAll(rule.selectorText); } catch { /* empty */ } if (!selectedNodes || !inNodeList(selectedNodes, element)) { continue; } var cssTop = rule.style.getPropertyValue("top"); var cssBottom = rule.style.getPropertyValue("bottom"); if ( (cssTop && cssTop !== "auto") || (cssBottom && cssBottom !== "auto") ) { return true; } } } return false; }; dialogPolyfill.needsCentering = function (dialog) { var computedStyle = window.getComputedStyle(dialog); if (computedStyle.position !== "absolute") { return false; } // We must determine whether the top/bottom specified value is non-auto. In // WebKit/Blink, checking computedStyle.top == 'auto' is sufficient, but // Firefox returns the used value. So we do this crazy thing instead: check // the inline style and then go through CSS rules. if ( (dialog.style.top !== "auto" && dialog.style.top !== "") || (dialog.style.bottom !== "auto" && dialog.style.bottom !== "") ) { return false; } return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog); }; /** * @param {!Element} element to force upgrade */ dialogPolyfill.forceRegisterDialog = function (element) { if (element.showModal) { console.warn( "This browser already supports <dialog>, the polyfill " + "may not work correctly", element, ); } if (element.localName !== "dialog") { throw new Error("Failed to register dialog: The element is not a dialog."); } new dialogPolyfillInfo(/** @type {!HTMLDialogElement} */ element); }; /** * @param {!Element} element to upgrade, if necessary */ dialogPolyfill.registerDialog = function (element) { if (!element.showModal) { dialogPolyfill.forceRegisterDialog(element); } }; /** * @constructor */ dialogPolyfill.DialogManager = function () { /** @type {!dialogPolyfillInfo[]} */ this.pendingDialogStack = []; var checkDOM = this.checkDOM_.bind(this); // The overlay is used to simulate how a modal dialog blocks the document. // The blocking dialog is positioned on top of the overlay, and the rest of // the dialogs on the pending dialog stack are positioned below it. In the // actual implementation, the modal dialog stacking is controlled by the // top layer, where z-index has no effect. this.overlay = document.createElement("div"); this.overlay.className = "_dialog_overlay"; this.overlay.addEventListener( "click", function (e) { this.forwardTab_ = undefined; e.stopPropagation(); checkDOM([]); // sanity-check DOM }.bind(this), ); this.handleKey_ = this.handleKey_.bind(this); this.handleFocus_ = this.handleFocus_.bind(this); this.zIndexLow_ = 100000; this.zIndexHigh_ = 100000 + 150; this.forwardTab_ = undefined; if ("MutationObserver" in window) { this.mo_ = new MutationObserver(function (records) { var removed = []; records.forEach(function (rec) { for (var i = 0, c; (c = rec.removedNodes[i]); ++i) { if (!(c instanceof Element)) { continue; } if (c.localName === "dialog") { removed.push(c); } removed = removed.concat(c.querySelectorAll("dialog")); } }); removed.length && checkDOM(removed); }); } }; /** * Called on the first modal dialog being shown. Adds the overlay and related * handlers. */ dialogPolyfill.DialogManager.prototype.blockDocument = function () { document.documentElement.addEventListener("focus", this.handleFocus_, true); document.addEventListener("keydown", this.handleKey_); this.mo_ && this.mo_.observe(document, { childList: true, subtree: true }); }; /** * Called on the first modal dialog being removed, i.e., when no more modal * dialogs are visible. */ dialogPolyfill.DialogManager.prototype.unblockDocument = function () { document.documentElement.removeEventListener( "focus", this.handleFocus_, true, ); document.removeEventListener("keydown", this.handleKey_); this.mo_ && this.mo_.disconnect(); }; /** * Updates the stacking of all known dialogs. */ dialogPolyfill.DialogManager.prototype.updateStacking = function () { var zIndex = this.zIndexHigh_; for (var i = 0, dpi; (dpi = this.pendingDialogStack[i]); ++i) { dpi.updateZIndex(--zIndex, --zIndex); if (i === 0) { this.overlay.style.zIndex = --zIndex; } } // Make the overlay a sibling of the dialog itself. var last = this.pendingDialogStack[0]; if (last) { var p = last.dialog.parentNode || document.body; p.appendChild(this.overlay); } else if (this.overlay.parentNode) { this.overlay.parentNode.removeChild(this.overlay); } }; /** * @param {Element} candidate to check if contained or is the top-most modal dialog * @return {boolean} whether candidate is contained in top dialog */ dialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function ( candidate, ) { while ((candidate = findNearestDialog(candidate))) { for (var i = 0, dpi; (dpi = this.pendingDialogStack[i]); ++i) { if (dpi.dialog === candidate) { return i === 0; // only valid if top-most } } candidate = candidate.parentElement; } return false; }; dialogPolyfill.DialogManager.prototype.handleFocus_ = function (event) { var target = event.composedPath ? event.composedPath()[0] : event.target; if (this.containedByTopDialog_(target)) { return; } if (document.activeElement === document.documentElement) { return; } event.preventDefault(); event.stopPropagation(); safeBlur(/** @type {Element} */ target); if (this.forwardTab_ === undefined) { return; } // move focus only from a tab key var dpi = this.pendingDialogStack[0]; var dialog = dpi.dialog; var position = dialog.compareDocumentPosition(target); if (position & Node.DOCUMENT_POSITION_PRECEDING) { if (this.forwardTab_) { // forward dpi.focus_(); } else if (target !== document.documentElement) { // backwards if we're not already focused on <html> document.documentElement.focus(); } } else { // TODO: Focus after the dialog, is ignored. } return false; }; dialogPolyfill.DialogManager.prototype.handleKey_ = function (event) { this.forwardTab_ = undefined; if (event.keyCode === 27) { event.preventDefault(); event.stopPropagation(); var cancelEvent = new window.CustomEvent("cancel", { bubbles: false, cancelable: true, }); var dpi = this.pendingDialogStack[0]; if (dpi && safeDispatchEvent(dpi.dialog, cancelEvent)) { dpi.dialog.close(); } } else if (event.keyCode === 9) { this.forwardTab_ = !event.shiftKey; } }; /** * Finds and downgrades any known modal dialogs that are no longer displayed. Dialogs that are * removed and immediately readded don't stay modal, they become normal. * * @param {!HTMLDialogElement[]} removed that have definitely been removed */ dialogPolyfill.DialogManager.prototype.checkDOM_ = function (removed) { // This operates on a clone because it may cause it to change. Each change also calls // updateStacking, which only actually needs to happen once. But who removes many modal dialogs // at a time?! var clone = this.pendingDialogStack.slice(); clone.forEach(function (dpi) { if (removed.indexOf(dpi.dialog) !== -1) { dpi.downgradeModal(); } else { dpi.maybeHideModal(); } }); }; /** * @param {!dialogPolyfillInfo} dpi * @return {boolean} whether the dialog was allowed */ dialogPolyfill.DialogManager.prototype.pushDialog = function (dpi) { var allowed = (this.zIndexHigh_ - this.zIndexLow_) / 2 - 1; if (this.pendingDialogStack.length >= allowed) { return false; } if (this.pendingDialogStack.unshift(dpi) === 1) { this.blockDocument(); } this.updateStacking(); return true; }; /** * @param {!dialogPolyfillInfo} dpi */ dialogPolyfill.DialogManager.prototype.removeDialog = function (dpi) { var index = this.pendingDialogStack.indexOf(dpi); if (index === -1) { return; } this.pendingDialogStack.splice(index, 1); if (this.pendingDialogStack.length === 0) { this.unblockDocument(); } this.updateStacking(); }; if (needPolyfill) { dialogPolyfill.dm = new dialogPolyfill.DialogManager(); dialogPolyfill.formSubmitter = null; dialogPolyfill.imagemapUseValue = null; } /** * Installs global handlers, such as click listers and native method overrides. These are needed * even if a no dialog is registered, as they deal with <form method="dialog">. */ if (needPolyfill) { /** * If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with * one that returns the correct value. */ var testForm = document.createElement("form"); testForm.setAttribute("method", "dialog"); if (testForm.method !== "dialog") { var methodDescriptor = Object.getOwnPropertyDescriptor( HTMLFormElement.prototype, "method", ); if (methodDescriptor) { // nb. Some older iOS and older PhantomJS fail to return the descriptor. Don't do anything // and don't bother to update the element. var realGet = methodDescriptor.get; methodDescriptor.get = function () { if (isFormMethodDialog(this)) { return "dialog"; } return realGet.call(this); }; var realSet = methodDescriptor.set; /** @this {HTMLElement} */ methodDescriptor.set = function (v) { if (typeof v === "string" && v.toLowerCase() === "dialog") { return this.setAttribute("method", v); } return realSet.call(this, v); }; Object.defineProperty( HTMLFormElement.prototype, "method", methodDescriptor, ); } } /** * Global 'click' handler, to capture the <input type="submit"> or <button> element which has * submitted a <form method="dialog">. Needed as Safari and others don't report this inside * document.activeElement. */ document.addEventListener( "click", function (ev) { dialogPolyfill.formSubmitter = null; dialogPolyfill.imagemapUseValue = null; if (ev.defaultPrevented) { return; } // e.g. a submit which prevents default submission var target = /** @type {Element} */ ev.target; if ("composedPath" in ev) { var path = ev.composedPath(); target = path.shift() || target; } if (!target || !isFormMethodDialog(target.form)) { return; } var valid = target.type === "submit" && ["button", "input"].indexOf(target.localName) > -1; if (!valid) { if (!(target.localName === "input" && target.type === "image")) { return; } // this is a <input type="image">, which can submit forms dialogPolyfill.imagemapUseValue = ev.offsetX + "," + ev.offsetY; } var dialog = findNearestDialog(target); if (!dialog) { return; } dialogPolyfill.formSubmitter = target; }, false, ); /** * Global 'submit' handler. This handles submits of `method="dialog"` which are invalid, i.e., * outside a dialog. They get prevented. */ document.addEventListener("submit", function (ev) { var form = ev.target; var dialog = findNearestDialog(form); if (dialog) { return; // ignore, handle there } var submitter = findFormSubmitter(ev); var formmethod = (submitter && submitter.getAttribute("formmethod")) || form.getAttribute("method"); if (formmethod === "dialog") { ev.preventDefault(); } }); /** * Replace the native HTMLFormElement.submit() method, as it won't fire the * submit event and give us a chance to respond. */ var nativeFormSubmit = HTMLFormElement.prototype.submit; var replacementFormSubmit = function () { if (!isFormMethodDialog(this)) { return nativeFormSubmit.call(this); } var dialog = findNearestDialog(this); dialog && dialog.close(); }; HTMLFormElement.prototype.submit = replacementFormSubmit; } export default dialogPolyfill as { registerDialog(dialog: HTMLDialogElement): void; forceRegisterDialog(dialog: HTMLDialogElement): void; };