From 4d6e41dea6492f18029f0dd70b118217c02f27d8 Mon Sep 17 00:00:00 2001 From: Johann-S Date: Sat, 9 Jun 2018 18:24:06 +0200 Subject: [PATCH] refactor(polyfill): a file for polyfills --- js/src/dom/eventHandler.js | 82 +----------------- js/src/dom/polyfill.js | 164 +++++++++++++++++++++++++---------- js/src/dom/selectorEngine.js | 104 +++------------------- js/tests/karma.conf.js | 6 +- 4 files changed, 137 insertions(+), 219 deletions(-) diff --git a/js/src/dom/eventHandler.js b/js/src/dom/eventHandler.js index c73bcd2ab8..dda1eb216f 100644 --- a/js/src/dom/eventHandler.js +++ b/js/src/dom/eventHandler.js @@ -1,88 +1,14 @@ +import Polyfill from './polyfill' import Util from '../util' /** * -------------------------------------------------------------------------- - * Bootstrap (v4.0.0-beta): dom/eventHandler.js + * Bootstrap (v4.1.1): dom/eventHandler.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ const EventHandler = (() => { - /** - * ------------------------------------------------------------------------ - * Polyfills - * ------------------------------------------------------------------------ - */ - - // defaultPrevented is broken in IE. - // https://connect.microsoft.com/IE/feedback/details/790389/event-defaultprevented-returns-false-after-preventdefault-was-called - const workingDefaultPrevented = (() => { - const e = document.createEvent('CustomEvent') - e.initEvent('Bootstrap', true, true) - e.preventDefault() - return e.defaultPrevented - })() - - let defaultPreventedPreservedOnDispatch = true - - // CustomEvent polyfill for IE (see: https://mzl.la/2v76Zvn) - if (typeof window.CustomEvent !== 'function') { - window.CustomEvent = (event, params) => { - params = params || { - bubbles: false, - cancelable: false, - detail: null - } - const evt = document.createEvent('CustomEvent') - evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail) - if (!workingDefaultPrevented) { - const origPreventDefault = Event.prototype.preventDefault - evt.preventDefault = () => { - if (!evt.cancelable) { - return - } - - origPreventDefault.call(evt) - Object.defineProperty(evt, 'defaultPrevented', { - get() { - return true - }, - configurable: true - }) - } - } - return evt - } - - window.CustomEvent.prototype = window.Event.prototype - } else { - // MSEdge resets defaultPrevented flag upon dispatchEvent call if at least one listener is attached - defaultPreventedPreservedOnDispatch = (() => { - const e = new CustomEvent('Bootstrap', { - cancelable: true - }) - - const element = document.createElement('div') - element.addEventListener('Bootstrap', () => null) - - e.preventDefault() - element.dispatchEvent(e) - return e.defaultPrevented - })() - } - - // Event constructor shim - if (!window.Event || typeof window.Event !== 'function') { - const origEvent = window.Event - window.Event = (inType, params) => { - params = params || {} - const e = document.createEvent('Event') - e.initEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable)) - return e - } - window.Event.prototype = origEvent.prototype - } - /** * ------------------------------------------------------------------------ * Constants @@ -361,7 +287,7 @@ const EventHandler = (() => { if (defaultPrevented) { evt.preventDefault() - if (!defaultPreventedPreservedOnDispatch) { + if (!Polyfill.defaultPreventedPreservedOnDispatch) { Object.defineProperty(evt, 'defaultPrevented', { get: () => true }) @@ -382,7 +308,7 @@ const EventHandler = (() => { })() // focusin and focusout polyfill -if (typeof window.onfocusin === 'undefined') { +if (Polyfill.focusIn) { (() => { function listenerFocus(event) { EventHandler.trigger(event.target, 'focusin') diff --git a/js/src/dom/polyfill.js b/js/src/dom/polyfill.js index 644e6025b1..2c811c25af 100644 --- a/js/src/dom/polyfill.js +++ b/js/src/dom/polyfill.js @@ -1,8 +1,14 @@ -import EventHandler from './eventHandler' +import Util from '../util' + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.1.1): dom/polyfill.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ const Polyfill = (() => { - // defaultPrevented is broken in IE. - // https://connect.microsoft.com/IE/feedback/details/790389/event-defaultprevented-returns-false-after-preventdefault-was-called + // defaultPrevented is broken in IE const workingDefaultPrevented = (() => { const e = document.createEvent('CustomEvent') e.initEvent('Bootstrap', true, true) @@ -10,7 +16,22 @@ const Polyfill = (() => { return e.defaultPrevented })() - let defaultPreventedPreservedOnDispatch = true + if (!workingDefaultPrevented) { + const origPreventDefault = Event.prototype.preventDefault + Event.prototype.preventDefault = function () { + if (!this.cancelable) { + return + } + + origPreventDefault.call(this) + Object.defineProperty(this, 'defaultPrevented', { + get() { + return true + }, + configurable: true + }) + } + } // CustomEvent polyfill for IE (see: https://mzl.la/2v76Zvn) if (typeof window.CustomEvent !== 'function') { @@ -22,41 +43,25 @@ const Polyfill = (() => { } const evt = document.createEvent('CustomEvent') evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail) - if (!workingDefaultPrevented) { - const origPreventDefault = Event.prototype.preventDefault - evt.preventDefault = () => { - if (!evt.cancelable) { - return - } - - origPreventDefault.call(evt) - Object.defineProperty(evt, 'defaultPrevented', { - get() { - return true - }, - configurable: true - }) - } - } return evt } window.CustomEvent.prototype = window.Event.prototype - } else { - // MSEdge resets defaultPrevented flag upon dispatchEvent call if at least one listener is attached - defaultPreventedPreservedOnDispatch = (() => { - const e = new CustomEvent('Bootstrap', { - cancelable: true - }) + } - const element = document.createElement('div') - element.addEventListener('Bootstrap', () => null) + // MSEdge resets defaultPrevented flag upon dispatchEvent call if at least one listener is attached + const defaultPreventedPreservedOnDispatch = (() => { + const e = new CustomEvent('Bootstrap', { + cancelable: true + }) - e.preventDefault() - element.dispatchEvent(e) - return e.defaultPrevented - })() - } + const element = document.createElement('div') + element.addEventListener('Bootstrap', () => null) + + e.preventDefault() + element.dispatchEvent(e) + return e.defaultPrevented + })() // Event constructor shim if (!window.Event || typeof window.Event !== 'function') { @@ -70,24 +75,91 @@ const Polyfill = (() => { window.Event.prototype = origEvent.prototype } - // focusin and focusout polyfill - if (typeof window.onfocusin === 'undefined') { - (() => { - function listenerFocus(event) { - EventHandler.trigger(event.target, 'focusin') + // matches polyfill (see: https://mzl.la/2ikXneG) + if (!Element.prototype.matches) { + Element.prototype.matches = + Element.prototype.msMatchesSelector || + Element.prototype.webkitMatchesSelector + } + + // closest polyfill (see: https://mzl.la/2vXggaI) + let closest + if (!Element.prototype.closest) { + closest = (element, selector) => { + let ancestor = element + do { + if (ancestor.matches(selector)) { + return ancestor + } + + ancestor = ancestor.parentElement + } while (ancestor !== null && ancestor.nodeType === Node.ELEMENT_NODE) + + return null + } + } else { + closest = (element, selector) => element.closest(selector) + } + + const supportScopeQuery = (() => { + const element = document.createElement('div') + try { + element.querySelectorAll(':scope *') + } catch (e) { + return false + } + + return true + })() + + const scopeSelectorRegex = /:scope\b/ + let find = Element.prototype.querySelectorAll + let findOne = Element.prototype.querySelector + + if (!supportScopeQuery) { + find = function (selector) { + if (!scopeSelectorRegex.test(selector)) { + return this.querySelectorAll(selector) } - function listenerBlur(event) { - EventHandler.trigger(event.target, 'focusout') + + const hasId = Boolean(this.id) + if (!hasId) { + this.id = Util.getUID('scope') } - EventHandler.on(document, 'focus', 'input', listenerFocus) - EventHandler.on(document, 'blur', 'input', listenerBlur) - })() + + let nodeList = null + try { + selector = selector.replace(scopeSelectorRegex, `#${this.id}`) + nodeList = this.querySelectorAll(selector) + } finally { + if (!hasId) { + this.removeAttribute('id') + } + } + + return nodeList + } + + findOne = function (selector) { + if (!scopeSelectorRegex.test(selector)) { + return this.querySelector(selector) + } + + const matches = find.call(this, selector) + if (typeof matches[0] !== 'undefined') { + return matches[0] + } + + return null + } } return { - get defaultPreventedPreservedOnDispatch() { - return defaultPreventedPreservedOnDispatch - } + defaultPreventedPreservedOnDispatch, + focusIn: typeof window.onfocusin === 'undefined', + closest, + find, + findOne } })() diff --git a/js/src/dom/selectorEngine.js b/js/src/dom/selectorEngine.js index e515164458..b6b45bacb4 100644 --- a/js/src/dom/selectorEngine.js +++ b/js/src/dom/selectorEngine.js @@ -1,3 +1,4 @@ +import Polyfill from './polyfill' import Util from '../util' /** @@ -10,100 +11,17 @@ import Util from '../util' const SelectorEngine = (() => { /** * ------------------------------------------------------------------------ - * Polyfills + * Constants * ------------------------------------------------------------------------ */ - // matches polyfill (see: https://mzl.la/2ikXneG) - let fnMatches = null - if (!Element.prototype.matches) { - fnMatches = - Element.prototype.msMatchesSelector || - Element.prototype.webkitMatchesSelector - } else { - fnMatches = Element.prototype.matches - } - - // closest polyfill (see: https://mzl.la/2vXggaI) - let fnClosest = null - if (!Element.prototype.closest) { - fnClosest = (element, selector) => { - let ancestor = element - do { - if (fnMatches.call(ancestor, selector)) { - return ancestor - } - - ancestor = ancestor.parentElement - } while (ancestor !== null && ancestor.nodeType === Node.ELEMENT_NODE) - - return null - } - } else { - // eslint-disable-next-line arrow-body-style - fnClosest = (element, selector) => { - return element.closest(selector) - } - } - - const scopeSelectorRegex = /:scope\b/ - const supportScopeQuery = (() => { - const element = document.createElement('div') - try { - element.querySelectorAll(':scope *') - } catch (e) { - return false - } - - return true - })() - - let findFn = null - let findOneFn = null - if (supportScopeQuery) { - findFn = Element.prototype.querySelectorAll - findOneFn = Element.prototype.querySelector - } else { - findFn = function (selector) { - if (!scopeSelectorRegex.test(selector)) { - return this.querySelectorAll(selector) - } - - const hasId = Boolean(this.id) - if (!hasId) { - this.id = Util.getUID('scope') - } - - let nodeList = null - try { - selector = selector.replace(scopeSelectorRegex, `#${this.id}`) - nodeList = this.querySelectorAll(selector) - } finally { - if (!hasId) { - this.removeAttribute('id') - } - } - - return nodeList - } - - findOneFn = function (selector) { - if (!scopeSelectorRegex.test(selector)) { - return this.querySelector(selector) - } - - const matches = findFn.call(this, selector) - if (typeof matches[0] !== 'undefined') { - return matches[0] - } - - return null - } - } + const closest = Polyfill.closest + const find = Polyfill.find + const findOne = Polyfill.findOne return { matches(element, selector) { - return fnMatches.call(element, selector) + return element.matches(selector) }, find(selector, element = document.documentElement) { @@ -111,7 +29,7 @@ const SelectorEngine = (() => { return null } - return findFn.call(element, selector) + return find.call(element, selector) }, findOne(selector, element = document.documentElement) { @@ -119,7 +37,7 @@ const SelectorEngine = (() => { return null } - return findOneFn.call(element, selector) + return findOne.call(element, selector) }, children(element, selector) { @@ -140,7 +58,7 @@ const SelectorEngine = (() => { let ancestor = element.parentNode while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE) { - if (fnMatches.call(ancestor, selector)) { + if (ancestor.matches(selector)) { parents.push(ancestor) } @@ -151,7 +69,7 @@ const SelectorEngine = (() => { }, closest(element, selector) { - return fnClosest(element, selector) + return closest(element, selector) }, prev(element, selector) { @@ -163,7 +81,7 @@ const SelectorEngine = (() => { let previous = element.previousSibling while (previous) { - if (fnMatches.call(previous, selector)) { + if (previous.matches(selector)) { siblings.push(previous) } diff --git a/js/tests/karma.conf.js b/js/tests/karma.conf.js index f6570cdcb4..066165a14d 100644 --- a/js/tests/karma.conf.js +++ b/js/tests/karma.conf.js @@ -98,7 +98,8 @@ if (bundle) { 'js/coverage/dist/dom/data.js', 'js/coverage/dist/dom/manipulator.js', 'js/coverage/dist/util.js', - 'js/coverage/dist/dom/*.js', + 'js/coverage/dist/dom/polyfill.js', + 'js/coverage/dist/dom/!(polyfill).js', 'js/coverage/dist/tooltip.js', 'js/coverage/dist/!(util|index|tooltip).js', // include all of our js/dist files except util.js, index.js and tooltip.js 'js/tests/unit/*.js', @@ -119,7 +120,8 @@ if (bundle) { 'js/coverage/dist/dom/data.js', 'js/coverage/dist/dom/manipulator.js', 'js/coverage/dist/util.js', - 'js/coverage/dist/dom/*.js', + 'js/coverage/dist/dom/polyfill.js', + 'js/coverage/dist/dom/!(polyfill).js', 'js/coverage/dist/tooltip.js', 'js/coverage/dist/!(util|index|tooltip).js', // include all of our js/dist files except util.js, index.js and tooltip.js 'js/tests/unit/*.js', -- 2.47.2