vendor: 4,915 bytes, lines 1-148
1(function (global, factory) { 2 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 typeof define === 'function' && define.amd ? define(factory) : 4 (global = global || self, global.dialogPolyfill = factory()); 5}(this, function () { 'use strict'; 6 7 // nb. This is for IE10 and lower _only_. 8 var supportCustomEvent = window.CustomEvent; 9 if (!supportCustomEvent || typeof supportCustomEvent === 'object') { 10 supportCustomEvent = function CustomEvent(event, x) { 11 x = x || {}; 12 var ev = document.createEvent('CustomEvent'); 13 ev.initCustomEvent(event, !!x.bubbles, !!x.cancelable, x.detail || null); 14 return ev; 15 }; 16 supportCustomEvent.prototype = window.Event.prototype; 17 } 18 19 /** 20 * @param {Element} el to check for stacking context 21 * @return {boolean} whether this el or its parents creates a stacking context 22 */ 23 function createsStackingContext(el) { 24 while (el && el !== document.body) { 25 var s = window.getComputedStyle(el); 26 var invalid = function(k, ok) { 27 return !(s[k] === undefined || s[k] === ok); 28 }; 29 30 if (s.opacity < 1 || 31 invalid('zIndex', 'auto') || 32 invalid('transform', 'none') || 33 invalid('mixBlendMode', 'normal') || 34 invalid('filter', 'none') || 35 invalid('perspective', 'none') || 36 s['isolation'] === 'isolate' || 37 s.position === 'fixed' || 38 s.webkitOverflowScrolling === 'touch') { 39 return true; 40 } 41 el = el.parentElement; 42 } 43 return false; 44 } 45 46 /** 47 * Finds the nearest <dialog> from the passed element. 48 * 49 * @param {Element} el to search from 50 * @return {HTMLDialogElement} dialog found 51 */ 52 function findNearestDialog(el) { 53 while (el) { 54 if (el.localName === 'dialog') { 55 return /** @type {HTMLDialogElement} */ (el); 56 } 57 el = el.parentElement; 58 } 59 return null; 60 } 61 62 /** 63 * Blur the specified element, as long as it's not the HTML body element. 64 * This works around an IE9/10 bug - blurring the body causes Windows to 65 * blur the whole application. 66 * 67 * @param {Element} el to blur 68 */ 69 function safeBlur(el) { 70 if (el && el.blur && el !== document.body) { 71 el.blur(); 72 } 73 } 74 75 /** 76 * @param {!NodeList} nodeList to search 77 * @param {Node} node to find 78 * @return {boolean} whether node is inside nodeList 79 */ 80 function inNodeList(nodeList, node) { 81 for (var i = 0; i < nodeList.length; ++i) { 82 if (nodeList[i] === node) { 83 return true; 84 } 85 } 86 return false; 87 } 88 89 /** 90 * @param {HTMLFormElement} el to check 91 * @return {boolean} whether this form has method="dialog" 92 */ 93 function isFormMethodDialog(el) { 94 if (!el || !el.hasAttribute('method')) { 95 return false; 96 } 97 return el.getAttribute('method').toLowerCase() === 'dialog'; 98 } 99 100 /** 101 * @param {!HTMLDialogElement} dialog to upgrade 102 * @constructor 103 */ 104 function dialogPolyfillInfo(dialog) { 105 this.dialog_ = dialog; 106 this.replacedStyleTop_ = false; 107 this.openAsModal_ = false; 108 109 // Set a11y role. Browsers that support dialog implicitly know this already. 110 if (!dialog.hasAttribute('role')) { 111 dialog.setAttribute('role', 'dialog'); 112 } 113 114 dialog.show = this.show.bind(this); 115 dialog.showModal = this.showModal.bind(this); 116 dialog.close = this.close.bind(this); 117 118 if (!('returnValue' in dialog)) { 119 dialog.returnValue = ''; 120 } 121 122 if ('MutationObserver' in window) { 123 var mo = new MutationObserver(this.maybeHideModal.bind(this)); 124 mo.observe(dialog, {attributes: true, attributeFilter: ['open']}); 125 } else { 126 // IE10 and below support. Note that DOMNodeRemoved etc fire _before_ removal. They also 127 // seem to fire even if the element was removed as part of a parent removal. Use the removed 128 // events to force downgrade (useful if removed/immediately added). 129 var removed = false; 130 var cb = function() { 131 removed ? this.downgradeModal() : this.maybeHideModal(); 132 removed = false; 133 }.bind(this); 134 var timeout; 135 var delayModel = function(ev) { 136 if (ev.target !== dialog) { return; } // not for a child element 137 var cand = 'DOMNodeRemoved'; 138 removed |= (ev.type.substr(0, cand.length) === cand); 139 window.clearTimeout(timeout); 140 timeout = window.setTimeout(cb, 0); 141 }; 142 ['DOMAttrModified', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument'].forEach(function(name) { 143 dialog.addEventListener(name, delayModel); 144 }); 145 } 146 // Note that the DOM is observed inside DialogManager while any dialog 147 // is being displayed as a modal, to catch modal removal from the DOM. 148
vendor: 4,149 bytes, lines 149-253
149 Object.defineProperty(dialog, 'open', { 150 set: this.setOpen.bind(this), 151 get: dialog.hasAttribute.bind(dialog, 'open') 152 }); 153 154 this.backdrop_ = document.createElement('div'); 155 this.backdrop_.className = 'backdrop'; 156 this.backdrop_.addEventListener('click', this.backdropClick_.bind(this)); 157 } 158 159 dialogPolyfillInfo.prototype = { 160 161 get dialog() { 162 return this.dialog_; 163 }, 164 165 /** 166 * Maybe remove this dialog from the modal top layer. This is called when 167 * a modal dialog may no longer be tenable, e.g., when the dialog is no 168 * longer open or is no longer part of the DOM. 169 */ 170 maybeHideModal: function() { 171 if (this.dialog_.hasAttribute('open') && document.body.contains(this.dialog_)) { return; } 172 this.downgradeModal(); 173 }, 174 175 /** 176 * Remove this dialog from the modal top layer, leaving it as a non-modal. 177 */ 178 downgradeModal: function() { 179 if (!this.openAsModal_) { return; } 180 this.openAsModal_ = false; 181 this.dialog_.style.zIndex = ''; 182 183 // This won't match the native <dialog> exactly because if the user set top on a centered 184 // polyfill dialog, that top gets thrown away when the dialog is closed. Not sure it's 185 // possible to polyfill this perfectly. 186 if (this.replacedStyleTop_) { 187 this.dialog_.style.top = ''; 188 this.replacedStyleTop_ = false; 189 } 190 191 // Clear the backdrop and remove from the manager. 192 this.backdrop_.parentNode && this.backdrop_.parentNode.removeChild(this.backdrop_); 193 dialogPolyfill.dm.removeDialog(this); 194 }, 195 196 /** 197 * @param {boolean} value whether to open or close this dialog 198 */ 199 setOpen: function(value) { 200 if (value) { 201 this.dialog_.hasAttribute('open') || this.dialog_.setAttribute('open', ''); 202 } else { 203 this.dialog_.removeAttribute('open'); 204 this.maybeHideModal(); // nb. redundant with MutationObserver 205 } 206 }, 207 208 /** 209 * Handles clicks on the fake .backdrop element, redirecting them as if 210 * they were on the dialog itself. 211 * 212 * @param {!Event} e to redirect 213 */ 214 backdropClick_: function(e) { 215 if (!this.dialog_.hasAttribute('tabindex')) { 216 // Clicking on the backdrop should move the implicit cursor, even if dialog cannot be 217 // focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this 218 // would not be needed - clicks would move the implicit cursor there. 219 var fake = document.createElement('div'); 220 this.dialog_.insertBefore(fake, this.dialog_.firstChild); 221 fake.tabIndex = -1; 222 fake.focus(); 223 this.dialog_.removeChild(fake); 224 } else { 225 this.dialog_.focus(); 226 } 227 228 var redirectedEvent = document.createEvent('MouseEvents'); 229 redirectedEvent.initMouseEvent(e.type, e.bubbles, e.cancelable, window, 230 e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, 231 e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget); 232 this.dialog_.dispatchEvent(redirectedEvent); 233 e.stopPropagation(); 234 }, 235 236 /** 237 * Focuses on the first focusable element within the dialog. This will always blur the current 238 * focus, even if nothing within the dialog is found. 239 */ 240 focus_: function() { 241 // Find element with `autofocus` attribute, or fall back to the first form/tabindex control. 242 var target = this.dialog_.querySelector('[autofocus]:not([disabled])'); 243 if (!target && this.dialog_.tabIndex >= 0) { 244 target = this.dialog_; 245 } 246 if (!target) { 247 // Note that this is 'any focusable area'. This list is probably not exhaustive, but the 248 // alternative involves stepping through and trying to focus everything. 249 var opts = ['button', 'input', 'keygen', 'select', 'textarea']; 250 var query = opts.map(function(el) { 251 return el + ':not([disabled])'; 252 }); 253 // TODO(samthor): tabindex values that are not numeric are not focusable.
vendor: 15,216 bytes, lines 254-680
254 query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled 255 target = this.dialog_.querySelector(query.join(', ')); 256 } 257 safeBlur(document.activeElement); 258 target && target.focus(); 259 }, 260 261 /** 262 * Sets the zIndex for the backdrop and dialog. 263 * 264 * @param {number} dialogZ 265 * @param {number} backdropZ 266 */ 267 updateZIndex: function(dialogZ, backdropZ) { 268 if (dialogZ < backdropZ) { 269 throw new Error('dialogZ should never be < backdropZ'); 270 } 271 this.dialog_.style.zIndex = dialogZ; 272 this.backdrop_.style.zIndex = backdropZ; 273 }, 274 275 /** 276 * Shows the dialog. If the dialog is already open, this does nothing. 277 */ 278 show: function() { 279 if (!this.dialog_.open) { 280 this.setOpen(true); 281 this.focus_(); 282 } 283 }, 284 285 /** 286 * Show this dialog modally. 287 */ 288 showModal: function() { 289 if (this.dialog_.hasAttribute('open')) { 290 throw new Error('Failed to execute \'showModal\' on dialog: The element is already open, and therefore cannot be opened modally.'); 291 } 292 if (!document.body.contains(this.dialog_)) { 293 throw new Error('Failed to execute \'showModal\' on dialog: The element is not in a Document.'); 294 } 295 if (!dialogPolyfill.dm.pushDialog(this)) { 296 throw new Error('Failed to execute \'showModal\' on dialog: There are too many open modal dialogs.'); 297 } 298 299 if (createsStackingContext(this.dialog_.parentElement)) { 300 console.warn('A dialog is being shown inside a stacking context. ' + 301 'This may cause it to be unusable. For more information, see this link: ' + 302 'https://github.com/GoogleChrome/dialog-polyfill/#stacking-context'); 303 } 304 305 this.setOpen(true); 306 this.openAsModal_ = true; 307 308 // Optionally center vertically, relative to the current viewport. 309 if (dialogPolyfill.needsCentering(this.dialog_)) { 310 dialogPolyfill.reposition(this.dialog_); 311 this.replacedStyleTop_ = true; 312 } else { 313 this.replacedStyleTop_ = false; 314 } 315 316 // Insert backdrop. 317 this.dialog_.parentNode.insertBefore(this.backdrop_, this.dialog_.nextSibling); 318 319 // Focus on whatever inside the dialog. 320 this.focus_(); 321 }, 322 323 /** 324 * Closes this HTMLDialogElement. This is optional vs clearing the open 325 * attribute, however this fires a 'close' event. 326 * 327 * @param {string=} opt_returnValue to use as the returnValue 328 */ 329 close: function(opt_returnValue) { 330 if (!this.dialog_.hasAttribute('open')) { 331 throw new Error('Failed to execute \'close\' on dialog: The element does not have an \'open\' attribute, and therefore cannot be closed.'); 332 } 333 this.setOpen(false); 334 335 // Leave returnValue untouched in case it was set directly on the element 336 if (opt_returnValue !== undefined) { 337 this.dialog_.returnValue = opt_returnValue; 338 } 339 340 // Triggering "close" event for any attached listeners on the <dialog>. 341 var closeEvent = new supportCustomEvent('close', { 342 bubbles: false, 343 cancelable: false 344 }); 345 this.dialog_.dispatchEvent(closeEvent); 346 } 347 348 }; 349 350 var dialogPolyfill = {}; 351 352 dialogPolyfill.reposition = function(element) { 353 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; 354 var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / 2; 355 element.style.top = Math.max(scrollTop, topValue) + 'px'; 356 }; 357 358 dialogPolyfill.isInlinePositionSetByStylesheet = function(element) { 359 for (var i = 0; i < document.styleSheets.length; ++i) { 360 var styleSheet = document.styleSheets[i]; 361 var cssRules = null; 362 // Some browsers throw on cssRules. 363 try { 364 cssRules = styleSheet.cssRules; 365 } catch (e) {} 366 if (!cssRules) { continue; } 367 for (var j = 0; j < cssRules.length; ++j) { 368 var rule = cssRules[j]; 369 var selectedNodes = null; 370 // Ignore errors on invalid selector texts. 371 try { 372 selectedNodes = document.querySelectorAll(rule.selectorText); 373 } catch(e) {} 374 if (!selectedNodes || !inNodeList(selectedNodes, element)) { 375 continue; 376 } 377 var cssTop = rule.style.getPropertyValue('top'); 378 var cssBottom = rule.style.getPropertyValue('bottom'); 379 if ((cssTop && cssTop !== 'auto') || (cssBottom && cssBottom !== 'auto')) { 380 return true; 381 } 382 } 383 } 384 return false; 385 }; 386 387 dialogPolyfill.needsCentering = function(dialog) { 388 var computedStyle = window.getComputedStyle(dialog); 389 if (computedStyle.position !== 'absolute') { 390 return false; 391 } 392 393 // We must determine whether the top/bottom specified value is non-auto. In 394 // WebKit/Blink, checking computedStyle.top == 'auto' is sufficient, but 395 // Firefox returns the used value. So we do this crazy thing instead: check 396 // the inline style and then go through CSS rules. 397 if ((dialog.style.top !== 'auto' && dialog.style.top !== '') || 398 (dialog.style.bottom !== 'auto' && dialog.style.bottom !== '')) { 399 return false; 400 } 401 return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog); 402 }; 403 404 /** 405 * @param {!Element} element to force upgrade 406 */ 407 dialogPolyfill.forceRegisterDialog = function(element) { 408 if (window.HTMLDialogElement || element.showModal) { 409 console.warn('This browser already supports <dialog>, the polyfill ' + 410 'may not work correctly', element); 411 } 412 if (element.localName !== 'dialog') { 413 throw new Error('Failed to register dialog: The element is not a dialog.'); 414 } 415 new dialogPolyfillInfo(/** @type {!HTMLDialogElement} */ (element)); 416 }; 417 418 /** 419 * @param {!Element} element to upgrade, if necessary 420 */ 421 dialogPolyfill.registerDialog = function(element) { 422 if (!element.showModal) { 423 dialogPolyfill.forceRegisterDialog(element); 424 } 425 }; 426 427 /** 428 * @constructor 429 */ 430 dialogPolyfill.DialogManager = function() { 431 /** @type {!Array<!dialogPolyfillInfo>} */ 432 this.pendingDialogStack = []; 433 434 var checkDOM = this.checkDOM_.bind(this); 435 436 // The overlay is used to simulate how a modal dialog blocks the document. 437 // The blocking dialog is positioned on top of the overlay, and the rest of 438 // the dialogs on the pending dialog stack are positioned below it. In the 439 // actual implementation, the modal dialog stacking is controlled by the 440 // top layer, where z-index has no effect. 441 this.overlay = document.createElement('div'); 442 this.overlay.className = '_dialog_overlay'; 443 this.overlay.addEventListener('click', function(e) { 444 this.forwardTab_ = undefined; 445 e.stopPropagation(); 446 checkDOM([]); // sanity-check DOM 447 }.bind(this)); 448 449 this.handleKey_ = this.handleKey_.bind(this); 450 this.handleFocus_ = this.handleFocus_.bind(this); 451 452 this.zIndexLow_ = 100000; 453 this.zIndexHigh_ = 100000 + 150; 454 455 this.forwardTab_ = undefined; 456 457 if ('MutationObserver' in window) { 458 this.mo_ = new MutationObserver(function(records) { 459 var removed = []; 460 records.forEach(function(rec) { 461 for (var i = 0, c; c = rec.removedNodes[i]; ++i) { 462 if (!(c instanceof Element)) { 463 continue; 464 } else if (c.localName === 'dialog') { 465 removed.push(c); 466 } 467 removed = removed.concat(c.querySelectorAll('dialog')); 468 } 469 }); 470 removed.length && checkDOM(removed); 471 }); 472 } 473 }; 474 475 /** 476 * Called on the first modal dialog being shown. Adds the overlay and related 477 * handlers. 478 */ 479 dialogPolyfill.DialogManager.prototype.blockDocument = function() { 480 document.documentElement.addEventListener('focus', this.handleFocus_, true); 481 document.addEventListener('keydown', this.handleKey_); 482 this.mo_ && this.mo_.observe(document, {childList: true, subtree: true}); 483 }; 484 485 /** 486 * Called on the first modal dialog being removed, i.e., when no more modal 487 * dialogs are visible. 488 */ 489 dialogPolyfill.DialogManager.prototype.unblockDocument = function() { 490 document.documentElement.removeEventListener('focus', this.handleFocus_, true); 491 document.removeEventListener('keydown', this.handleKey_); 492 this.mo_ && this.mo_.disconnect(); 493 }; 494 495 /** 496 * Updates the stacking of all known dialogs. 497 */ 498 dialogPolyfill.DialogManager.prototype.updateStacking = function() { 499 var zIndex = this.zIndexHigh_; 500 501 for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) { 502 dpi.updateZIndex(--zIndex, --zIndex); 503 if (i === 0) { 504 this.overlay.style.zIndex = --zIndex; 505 } 506 } 507 508 // Make the overlay a sibling of the dialog itself. 509 var last = this.pendingDialogStack[0]; 510 if (last) { 511 var p = last.dialog.parentNode || document.body; 512 p.appendChild(this.overlay); 513 } else if (this.overlay.parentNode) { 514 this.overlay.parentNode.removeChild(this.overlay); 515 } 516 }; 517 518 /** 519 * @param {Element} candidate to check if contained or is the top-most modal dialog 520 * @return {boolean} whether candidate is contained in top dialog 521 */ 522 dialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function(candidate) { 523 while (candidate = findNearestDialog(candidate)) { 524 for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) { 525 if (dpi.dialog === candidate) { 526 return i === 0; // only valid if top-most 527 } 528 } 529 candidate = candidate.parentElement; 530 } 531 return false; 532 }; 533 534 dialogPolyfill.DialogManager.prototype.handleFocus_ = function(event) { 535 if (this.containedByTopDialog_(event.target)) { return; } 536 537 if (document.activeElement === document.documentElement) { return; } 538 539 event.preventDefault(); 540 event.stopPropagation(); 541 safeBlur(/** @type {Element} */ (event.target)); 542 543 if (this.forwardTab_ === undefined) { return; } // move focus only from a tab key 544 545 var dpi = this.pendingDialogStack[0]; 546 var dialog = dpi.dialog; 547 var position = dialog.compareDocumentPosition(event.target); 548 if (position & Node.DOCUMENT_POSITION_PRECEDING) { 549 if (this.forwardTab_) { 550 // forward 551 dpi.focus_(); 552 } else if (event.target !== document.documentElement) { 553 // backwards if we're not already focused on <html> 554 document.documentElement.focus(); 555 } 556 } 557 558 return false; 559 }; 560 561 dialogPolyfill.DialogManager.prototype.handleKey_ = function(event) { 562 this.forwardTab_ = undefined; 563 if (event.keyCode === 27) { 564 event.preventDefault(); 565 event.stopPropagation(); 566 var cancelEvent = new supportCustomEvent('cancel', { 567 bubbles: false, 568 cancelable: true 569 }); 570 var dpi = this.pendingDialogStack[0]; 571 if (dpi && dpi.dialog.dispatchEvent(cancelEvent)) { 572 dpi.dialog.close(); 573 } 574 } else if (event.keyCode === 9) { 575 this.forwardTab_ = !event.shiftKey; 576 } 577 }; 578 579 /** 580 * Finds and downgrades any known modal dialogs that are no longer displayed. Dialogs that are 581 * removed and immediately readded don't stay modal, they become normal. 582 * 583 * @param {!Array<!HTMLDialogElement>} removed that have definitely been removed 584 */ 585 dialogPolyfill.DialogManager.prototype.checkDOM_ = function(removed) { 586 // This operates on a clone because it may cause it to change. Each change also calls 587 // updateStacking, which only actually needs to happen once. But who removes many modal dialogs 588 // at a time?! 589 var clone = this.pendingDialogStack.slice(); 590 clone.forEach(function(dpi) { 591 if (removed.indexOf(dpi.dialog) !== -1) { 592 dpi.downgradeModal(); 593 } else { 594 dpi.maybeHideModal(); 595 } 596 }); 597 }; 598 599 /** 600 * @param {!dialogPolyfillInfo} dpi 601 * @return {boolean} whether the dialog was allowed 602 */ 603 dialogPolyfill.DialogManager.prototype.pushDialog = function(dpi) { 604 var allowed = (this.zIndexHigh_ - this.zIndexLow_) / 2 - 1; 605 if (this.pendingDialogStack.length >= allowed) { 606 return false; 607 } 608 if (this.pendingDialogStack.unshift(dpi) === 1) { 609 this.blockDocument(); 610 } 611 this.updateStacking(); 612 return true; 613 }; 614 615 /** 616 * @param {!dialogPolyfillInfo} dpi 617 */ 618 dialogPolyfill.DialogManager.prototype.removeDialog = function(dpi) { 619 var index = this.pendingDialogStack.indexOf(dpi); 620 if (index === -1) { return; } 621 622 this.pendingDialogStack.splice(index, 1); 623 if (this.pendingDialogStack.length === 0) { 624 this.unblockDocument(); 625 } 626 this.updateStacking(); 627 }; 628 629 dialogPolyfill.dm = new dialogPolyfill.DialogManager(); 630 dialogPolyfill.formSubmitter = null; 631 dialogPolyfill.useValue = null; 632 633 /** 634 * Installs global handlers, such as click listers and native method overrides. These are needed 635 * even if a no dialog is registered, as they deal with <form method="dialog">. 636 */ 637 if (window.HTMLDialogElement === undefined) { 638 639 /** 640 * If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with 641 * one that returns the correct value. 642 */ 643 var testForm = document.createElement('form'); 644 testForm.setAttribute('method', 'dialog'); 645 if (testForm.method !== 'dialog') { 646 var methodDescriptor = Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, 'method'); 647 if (methodDescriptor) { 648 // nb. Some older iOS and older PhantomJS fail to return the descriptor. Don't do anything 649 // and don't bother to update the element. 650 var realGet = methodDescriptor.get; 651 methodDescriptor.get = function() { 652 if (isFormMethodDialog(this)) { 653 return 'dialog'; 654 } 655 return realGet.call(this); 656 }; 657 var realSet = methodDescriptor.set; 658 methodDescriptor.set = function(v) { 659 if (typeof v === 'string' && v.toLowerCase() === 'dialog') { 660 return this.setAttribute('method', v); 661 } 662 return realSet.call(this, v); 663 }; 664 Object.defineProperty(HTMLFormElement.prototype, 'method', methodDescriptor); 665 } 666 } 667 668 /** 669 * Global 'click' handler, to capture the <input type="submit"> or <button> element which has 670 * submitted a <form method="dialog">. Needed as Safari and others don't report this inside 671 * document.activeElement. 672 */ 673 document.addEventListener('click', function(ev) { 674 dialogPolyfill.formSubmitter = null; 675 dialogPolyfill.useValue = null; 676 if (ev.defaultPrevented) { return; } // e.g. a submit which prevents default submission 677 678 var target = /** @type {Element} */ (ev.target); 679 if (!target || !isFormMethodDialog(target.form)) { return; } 680
681 var valid = (target.type === 'submit' && ['button', 'input'].indexOf(target.localName) > -1); 682 if (!valid) { 683 if (!(target.localName === 'input' && target.type === 'image')) { return; } 684 // this is a <input type="image">, which can submit forms 685 dialogPolyfill.useValue = ev.offsetX + ',' + ev.offsetY; 686 } 687 688 var dialog = findNearestDialog(target); 689 if (!dialog) { return; } 690 691 dialogPolyfill.formSubmitter = target; 692 693 }, false); 694 695 /** 696 * Replace the native HTMLFormElement.submit() method, as it won't fire the 697 * submit event and give us a chance to respond. 698 */ 699 var nativeFormSubmit = HTMLFormElement.prototype.submit; 700 var replacementFormSubmit = function () { 701 if (!isFormMethodDialog(this)) { 702 return nativeFormSubmit.call(this); 703 } 704 var dialog = findNearestDialog(this); 705 dialog && dialog.close(); 706 }; 707 HTMLFormElement.prototype.submit = replacementFormSubmit; 708 709 /** 710 * Global form 'dialog' method handler. Closes a dialog correctly on submit 711 * and possibly sets its return value. 712 */ 713 document.addEventListener('submit', function(ev) { 714 var form = /** @type {HTMLFormElement} */ (ev.target); 715 if (!isFormMethodDialog(form)) { return; } 716 ev.preventDefault(); 717 718 var dialog = findNearestDialog(form); 719 if (!dialog) { return; } 720 721 // Forms can only be submitted via .submit() or a click (?), but anyway: sanity-check that 722 // the submitter is correct before using its value as .returnValue. 723 var s = dialogPolyfill.formSubmitter; 724 if (s && s.form === form) { 725 dialog.close(dialogPolyfill.useValue || s.value); 726 } else { 727 dialog.close(); 728 } 729 dialogPolyfill.formSubmitter = null; 730 731 }, true); 732 } 733 734 return dialogPolyfill; 735 736}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.