UNPKG

ce-v0

Version:
766 lines (702 loc) 22.7 kB
(function(window){'use strict'; /** * Copyright (c) 2017, Andrea Giammarchi, @WebReflection * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. * */ var document = window.document, Object = window.Object, // V0 polyfill entry REGISTER_ELEMENT = 'registerElement', // IE < 11 only + old WebKit for attributes + feature detection EXPANDO_UID = '__' + REGISTER_ELEMENT + (window.Math.random() * 10e4 >> 0), // shortcuts and costants ADD_EVENT_LISTENER = 'addEventListener', ATTACHED = 'attached', CALLBACK = 'Callback', DETACHED = 'detached', ATTRIBUTE_CHANGED_CALLBACK = 'attributeChanged' + CALLBACK, ATTACHED_CALLBACK = ATTACHED + CALLBACK, CONNECTED_CALLBACK = 'connected' + CALLBACK, DISCONNECTED_CALLBACK = 'disconnected' + CALLBACK, CREATED_CALLBACK = 'created' + CALLBACK, DETACHED_CALLBACK = DETACHED + CALLBACK, ADDITION = 'ADDITION', MODIFICATION = 'MODIFICATION', REMOVAL = 'REMOVAL', DOM_ATTR_MODIFIED = 'DOMAttrModified', DOM_CONTENT_LOADED = 'DOMContentLoaded', DOM_SUBTREE_MODIFIED = 'DOMSubtreeModified', PREFIX_TAG = '<', PREFIX_IS = '=', // registered types and their prototypes types = [], protos = [], // to query subnodes query = '', // html shortcut used to feature detect documentElement = document.documentElement, // ES5 inline helpers || basic patches indexOf = types.indexOf || function (v) { for(var i = this.length; i-- && this[i] !== v;){} return i; }, // other helpers / shortcuts OP = Object.prototype, hOP = OP.hasOwnProperty, iPO = OP.isPrototypeOf, defineProperty = Object.defineProperty, gOPD = Object.getOwnPropertyDescriptor, gOPN = Object.getOwnPropertyNames, gPO = Object.getPrototypeOf, sPO = Object.setPrototypeOf, // jshint proto: true hasProto = !!Object.__proto__, // used to create unique instances create = Object.create || function Bridge(proto) { // silly broken polyfill probably ever used but short enough to work return proto ? ((Bridge.prototype = proto), new Bridge()) : this; }, // will set the prototype if possible // or copy over all properties setPrototype = sPO || ( hasProto ? function (o, p) { o.__proto__ = p; return o; } : ( (gOPN && gOPD) ? (function(){ function setProperties(o, p) { for (var key, names = gOPN(p), i = 0, length = names.length; i < length; i++ ) { key = names[i]; if (!hOP.call(o, key)) { defineProperty(o, key, gOPD(p, key)); } } } return function (o, p) { do { setProperties(o, p); } while ((p = gPO(p)) && !iPO.call(p, o)); return o; }; }()) : function (o, p) { for (var key in p) { o[key] = p[key]; } return o; } )), // DOM shortcuts and helpers, if any MutationObserver = window.MutationObserver || window.WebKitMutationObserver, HTMLElementPrototype = ( window.HTMLElement || window.Element || window.Node ).prototype, IE8 = !iPO.call(HTMLElementPrototype, documentElement), isValidNode = IE8 ? function (node) { return node.nodeType === 1; } : function (node) { return iPO.call(HTMLElementPrototype, node); }, targets = IE8 && [], attachShadow = HTMLElementPrototype.attachShadow, dispatchEvent = HTMLElementPrototype.dispatchEvent, getAttribute = HTMLElementPrototype.getAttribute, hasAttribute = HTMLElementPrototype.hasAttribute, removeAttribute = HTMLElementPrototype.removeAttribute, setAttribute = HTMLElementPrototype.setAttribute, // replaced later on createElement = document.createElement, // shared observer for all attributes attributesObserver = MutationObserver && { attributes: true, characterData: true, attributeOldValue: true }, // useful to detect only if there's no MutationObserver DOMAttrModified = MutationObserver || function() { doesNotSupportDOMAttrModified = false; documentElement.removeEventListener( DOM_ATTR_MODIFIED, DOMAttrModified ); }, // will both be used to make DOMNodeInserted asynchronous asapQueue, asapTimer = 0, setListener = true, justSetup = false, doesNotSupportDOMAttrModified = true, dropDomContentLoaded = true, // needed for the innerHTML helper notFromInnerHTMLHelper = true, // optionally defined later on onSubtreeModified, callDOMAttrModified, getAttributesMirror, observer, observe, // based on setting prototype capability // will check proto or the expando attribute // in order to setup the node once patchIfNotAlready, patch, // customElements ready browsers customElements = window.customElements, noop, construct, nativeDefine ; // only if needed if (REGISTER_ELEMENT in document) return; // otherwise if V1 is available, use it else if (customElements) { noop = function () {}; construct = Reflect.construct; nativeDefine = customElements.define.bind(customElements); document.registerElement = function registerElement(name, info) { var proto = info.prototype, Constructor = gPO(proto).constructor, created = proto[CREATED_CALLBACK], attributeChanged = proto[ATTRIBUTE_CHANGED_CALLBACK], attached = proto[ATTACHED_CALLBACK], detached = proto[DETACHED_CALLBACK], observe = attributeChanged ? function (node) { mo.observe(node, { attributes: true, attributeOldValue: true }); return node; } : Object, define = function (name, value) { defineProperty(CustomElementV0.prototype, name, { configurable: true, writable: true, value: value }); }, mo = attributeChanged && new MutationObserver(function (mutations) { for (var i = 0, length = mutations.length; i < length; i++) { notifyAttributeChanged(mutations[i]); } }) ; function CustomElementV0() { return construct( Constructor, arguments, CustomElementV0 )[CREATED_CALLBACK](); } CustomElementV0.prototype = create(proto); define(CREATED_CALLBACK, created ? function () { return created.call(observe(this)), this; } : function () { return observe(this); } ); if (attributeChanged) define(ATTRIBUTE_CHANGED_CALLBACK, attributeChanged); if (attached) define(CONNECTED_CALLBACK, attached); if (detached) define(DISCONNECTED_CALLBACK, detached); nativeDefine(name, CustomElementV0); return CustomElementV0; }; } // the rest is from the old polyfill for every old mobile/desktop browser else { if (sPO || hasProto) { patchIfNotAlready = function (node, proto) { if (!iPO.call(proto, node)) { setupNode(node, proto); } }; patch = setupNode; } else { patchIfNotAlready = function (node, proto) { if (!node[EXPANDO_UID]) { node[EXPANDO_UID] = Object(true); setupNode(node, proto); } }; patch = patchIfNotAlready; } if (IE8) { doesNotSupportDOMAttrModified = false; (function (){ var descriptor = gOPD(HTMLElementPrototype, ADD_EVENT_LISTENER), addEventListener = descriptor.value, patchedRemoveAttribute = function (name) { var e = new CustomEvent(DOM_ATTR_MODIFIED, {bubbles: true}); e.attrName = name; e.prevValue = getAttribute.call(this, name); e.newValue = null; e[REMOVAL] = e.attrChange = 2; removeAttribute.call(this, name); dispatchEvent.call(this, e); }, patchedSetAttribute = function (name, value) { var had = hasAttribute.call(this, name), old = had && getAttribute.call(this, name), e = new CustomEvent(DOM_ATTR_MODIFIED, {bubbles: true}) ; setAttribute.call(this, name, value); e.attrName = name; e.prevValue = had ? old : null; e.newValue = value; if (had) { e[MODIFICATION] = e.attrChange = 1; } else { e[ADDITION] = e.attrChange = 0; } dispatchEvent.call(this, e); }, onPropertyChange = function (e) { // jshint eqnull:true var node = e.currentTarget, superSecret = node[EXPANDO_UID], propertyName = e.propertyName, event ; if (superSecret.hasOwnProperty(propertyName)) { superSecret = superSecret[propertyName]; event = new CustomEvent(DOM_ATTR_MODIFIED, {bubbles: true}); event.attrName = superSecret.name; event.prevValue = superSecret.value || null; event.newValue = (superSecret.value = node[propertyName] || null); if (event.prevValue == null) { event[ADDITION] = event.attrChange = 0; } else { event[MODIFICATION] = event.attrChange = 1; } dispatchEvent.call(node, event); } } ; descriptor.value = function (type, handler, capture) { if ( type === DOM_ATTR_MODIFIED && this[ATTRIBUTE_CHANGED_CALLBACK] && this.setAttribute !== patchedSetAttribute ) { this[EXPANDO_UID] = { className: { name: 'class', value: this.className } }; this.setAttribute = patchedSetAttribute; this.removeAttribute = patchedRemoveAttribute; addEventListener.call(this, 'propertychange', onPropertyChange); } addEventListener.call(this, type, handler, capture); }; defineProperty(HTMLElementPrototype, ADD_EVENT_LISTENER, descriptor); }()); } else if (!MutationObserver) { documentElement[ADD_EVENT_LISTENER](DOM_ATTR_MODIFIED, DOMAttrModified); documentElement.setAttribute(EXPANDO_UID, 1); documentElement.removeAttribute(EXPANDO_UID); if (doesNotSupportDOMAttrModified) { onSubtreeModified = function (e) { var node = this, oldAttributes, newAttributes, key ; if (node === e.target) { oldAttributes = node[EXPANDO_UID]; node[EXPANDO_UID] = (newAttributes = getAttributesMirror(node)); for (key in newAttributes) { if (!(key in oldAttributes)) { // attribute was added return callDOMAttrModified( 0, node, key, oldAttributes[key], newAttributes[key], ADDITION ); } else if (newAttributes[key] !== oldAttributes[key]) { // attribute was changed return callDOMAttrModified( 1, node, key, oldAttributes[key], newAttributes[key], MODIFICATION ); } } // checking if it has been removed for (key in oldAttributes) { if (!(key in newAttributes)) { // attribute removed return callDOMAttrModified( 2, node, key, oldAttributes[key], newAttributes[key], REMOVAL ); } } } }; callDOMAttrModified = function ( attrChange, currentTarget, attrName, prevValue, newValue, action ) { var e = { attrChange: attrChange, currentTarget: currentTarget, attrName: attrName, prevValue: prevValue, newValue: newValue }; e[action] = attrChange; onDOMAttrModified(e); }; getAttributesMirror = function (node) { for (var attr, name, result = {}, attributes = node.attributes, i = 0, length = attributes.length; i < length; i++ ) { attr = attributes[i]; name = attr.name; if (name !== 'setAttribute') { result[name] = attr.value; } } return result; }; } } // set as enumerable, writable and configurable document[REGISTER_ELEMENT] = function registerElement(type, options) { upperType = type.toUpperCase(); if (setListener) { // only first time document.registerElement is used // we need to set this listener // setting it by default might slow down for no reason setListener = false; if (MutationObserver) { observer = (function(attached, detached){ function checkEmAll(list, callback) { for (var i = 0, length = list.length; i < length; callback(list[i++])){} } return new MutationObserver(function (records) { for (var current, i = 0, length = records.length; i < length; i++ ) { current = records[i]; if (current.type === 'childList') { checkEmAll(current.addedNodes, attached); checkEmAll(current.removedNodes, detached); } else { notifyAttributeChanged(current); } } }); }(executeAction(ATTACHED), executeAction(DETACHED))); observe = function (node) { observer.observe( node, { childList: true, subtree: true } ); return node; }; observe(document); if (attachShadow) { HTMLElementPrototype.attachShadow = function () { return observe(attachShadow.apply(this, arguments)); }; } } else { asapQueue = []; document[ADD_EVENT_LISTENER]('DOMNodeInserted', onDOMNode(ATTACHED)); document[ADD_EVENT_LISTENER]('DOMNodeRemoved', onDOMNode(DETACHED)); } document[ADD_EVENT_LISTENER](DOM_CONTENT_LOADED, onReadyStateChange); document[ADD_EVENT_LISTENER]('readystatechange', onReadyStateChange); patchCloneNode(HTMLElementPrototype); patchCloneNode(document.createDocumentFragment().constructor.prototype); } if (justSetup) return (justSetup = false); if (-2 < ( indexOf.call(types, PREFIX_IS + upperType) + indexOf.call(types, PREFIX_TAG + upperType) )) { throwTypeError(type); } var constructor = function () { return document.createElement(nodeName); }, opt = options || OP, nodeName = upperType, upperType, i ; i = types.push(PREFIX_TAG + upperType) - 1; query = query.concat( query.length ? ',' : '', nodeName ); constructor.prototype = ( protos[i] = hOP.call(opt, 'prototype') ? opt.prototype : create(HTMLElementPrototype) ); if (query.length) loopAndVerify( document.querySelectorAll(query), ATTACHED ); return constructor; }; document.createElement = function (localName) { var node = createElement.apply(document, arguments), name = '' + localName, i = indexOf.call( types, PREFIX_TAG + name.toUpperCase() ), setup = -1 < i ; notFromInnerHTMLHelper = !document.createElement.innerHTMLHelper; if (setup) patch(node, protos[i]); return node; }; } function notifyAttributeChanged(mutation) { var node = mutation.target, name = mutation.attributeName, oldValue = mutation.oldValue, newValue ; if (notFromInnerHTMLHelper && node[ATTRIBUTE_CHANGED_CALLBACK] && name !== 'style') { newValue = getAttribute.call(node, name); if (newValue !== oldValue) { node[ATTRIBUTE_CHANGED_CALLBACK]( name, oldValue, newValue ); } } } function ASAP() { var queue = asapQueue.splice(0, asapQueue.length); asapTimer = 0; while (queue.length) { queue.shift().call( null, queue.shift() ); } } function loopAndVerify(list, action) { for (var i = 0, length = list.length; i < length; i++) { verifyAndSetupAndAction(list[i], action); } } function loopAndSetup(list) { for (var i = 0, length = list.length, node; i < length; i++) { node = list[i]; patch(node, protos[getTypeIndex(node)]); } } function executeAction(action) { return function (node) { if (isValidNode(node)) { verifyAndSetupAndAction(node, action); if (query.length) loopAndVerify( node.querySelectorAll(query), action ); } }; } function getTypeIndex(target) { return indexOf.call( types, PREFIX_TAG + target.nodeName.toUpperCase() ); } function onDOMAttrModified(e) { var node = e.currentTarget, attrChange = e.attrChange, attrName = e.attrName, target = e.target, addition = e[ADDITION] || 2, removal = e[REMOVAL] || 3 ; if (notFromInnerHTMLHelper && (!target || target === node) && node[ATTRIBUTE_CHANGED_CALLBACK] && attrName !== 'style' && ( e.prevValue !== e.newValue || // IE9, IE10, and Opera 12 gotcha e.newValue === '' && ( attrChange === addition || attrChange === removal ) )) { node[ATTRIBUTE_CHANGED_CALLBACK]( attrName, attrChange === addition ? null : e.prevValue, attrChange === removal ? null : e.newValue ); } } function onDOMNode(action) { var executor = executeAction(action); return function (e) { asapQueue.push(executor, e.target); if (asapTimer) clearTimeout(asapTimer); asapTimer = setTimeout(ASAP, 1); }; } function onReadyStateChange(e) { if (dropDomContentLoaded) { dropDomContentLoaded = false; e.currentTarget.removeEventListener(DOM_CONTENT_LOADED, onReadyStateChange); } if (query.length) loopAndVerify( (e.target || document).querySelectorAll(query), e.detail === DETACHED ? DETACHED : ATTACHED ); if (IE8) purge(); } function patchCloneNode(proto) { var cloneNode = proto.cloneNode; proto.cloneNode = function (deep) { var node = cloneNode.call(this, !!deep), i = getTypeIndex(node) ; if (-1 < i) patch(node, protos[i]); if (deep && query.length) loopAndSetup(node.querySelectorAll(query)); return node; }; } function patchedSetAttribute(name, value) { // jshint validthis:true var self = this; setAttribute.call(self, name, value); onSubtreeModified.call(self, {target: self}); } function setupNode(node, proto) { setPrototype(node, proto); if (observer) { observer.observe(node, attributesObserver); } else { if (doesNotSupportDOMAttrModified) { node.setAttribute = patchedSetAttribute; node[EXPANDO_UID] = getAttributesMirror(node); node[ADD_EVENT_LISTENER](DOM_SUBTREE_MODIFIED, onSubtreeModified); } node[ADD_EVENT_LISTENER](DOM_ATTR_MODIFIED, onDOMAttrModified); } if (node[CREATED_CALLBACK] && notFromInnerHTMLHelper) { node.created = true; node[CREATED_CALLBACK](); node.created = false; } } function purge() { for (var node, i = 0, length = targets.length; i < length; i++ ) { node = targets[i]; if (!documentElement.contains(node)) { length--; targets.splice(i--, 1); verifyAndSetupAndAction(node, DETACHED); } } } function throwTypeError(type) { throw new Error('A ' + type + ' type is already registered'); } function verifyAndSetupAndAction(node, action) { var fn, i = getTypeIndex(node), counterAction ; if (-1 < i) { patchIfNotAlready(node, protos[i]); i = 0; if (action === ATTACHED && !node[ATTACHED]) { node[DETACHED] = false; node[ATTACHED] = true; counterAction = 'connected'; i = 1; if (IE8 && indexOf.call(targets, node) < 0) { targets.push(node); } } else if (action === DETACHED && !node[DETACHED]) { node[ATTACHED] = false; node[DETACHED] = true; counterAction = 'disconnected'; i = 1; } if (i && (fn = ( node[action + CALLBACK] || node[counterAction + CALLBACK] ))) fn.call(node); } } }(window));