vendor custom element builtin
parent
7ce11937a5
commit
fa8c37b9e2
|
@ -0,0 +1,245 @@
|
|||
/*! (c) Andrea Giammarchi - ISC */
|
||||
(function (document, customElements, Object) {'use strict';
|
||||
// trick of the year 🎉 https://twitter.com/WebReflection/status/1232269200317652992
|
||||
if (document.importNode.length != 1 || customElements.get('ungap-li'))
|
||||
return;
|
||||
var EXTENDS = 'extends';
|
||||
try {
|
||||
// class LI extends HTMLLIElement {}
|
||||
var desc = {};
|
||||
desc[EXTENDS] = 'li';
|
||||
var HtmlLI = HTMLLIElement;
|
||||
var LI = function () {
|
||||
return Reflect.construct(HtmlLI, [], LI);
|
||||
};
|
||||
LI.prototype = Object.create(HtmlLI.prototype);
|
||||
customElements.define('ungap-li', LI, desc);
|
||||
if (!/is="ungap-li"/.test((new LI).outerHTML))
|
||||
throw desc;
|
||||
} catch (o_O) {
|
||||
(function() {
|
||||
var ATTRIBUTE_CHANGED_CALLBACK = 'attributeChangedCallback';
|
||||
var CONNECTED_CALLBACK = 'connectedCallback';
|
||||
var DISCONNECTED_CALLBACK = 'disconnectedCallback';
|
||||
var ElemProto = Element.prototype;
|
||||
var assign = Object.assign;
|
||||
var create = Object.create;
|
||||
var defineProperties = Object.defineProperties;
|
||||
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
||||
var setPrototypeOf = Object.setPrototypeOf;
|
||||
var define = customElements.define;
|
||||
var get = customElements.get;
|
||||
var upgrade = customElements.upgrade;
|
||||
var whenDefined = customElements.whenDefined;
|
||||
var registry = create(null);
|
||||
var lifeCycle = new WeakMap;
|
||||
var changesOptions = {childList: true, subtree: true};
|
||||
new MutationObserver(DOMChanges).observe(document, changesOptions);
|
||||
wrapOriginal(Document.prototype, 'importNode');
|
||||
wrapOriginal(Node.prototype, 'cloneNode');
|
||||
defineProperties(
|
||||
customElements,
|
||||
{
|
||||
define: {
|
||||
value: function (name, Class, options) {
|
||||
name = name.toLowerCase();
|
||||
if (options && EXTENDS in options) {
|
||||
// currently options is not used but preserved for the future
|
||||
registry[name] = assign({}, options, {Class: Class});
|
||||
var query = options[EXTENDS] + '[is="' + name + '"]';
|
||||
var changes = document.querySelectorAll(query);
|
||||
for (var i = 0, length = changes.length; i < length; i++)
|
||||
setupIfNeeded(changes[i]);
|
||||
}
|
||||
else
|
||||
define.apply(customElements, arguments);
|
||||
}
|
||||
},
|
||||
get: {
|
||||
value: function (name) {
|
||||
return name in registry ?
|
||||
registry[name].Class :
|
||||
get.call(customElements, name);
|
||||
}
|
||||
},
|
||||
upgrade: {
|
||||
value: function (node) {
|
||||
var info = getInfo(node);
|
||||
if (info && !(node instanceof info.Class))
|
||||
setup(node, info);
|
||||
else
|
||||
upgrade.call(customElements, node);
|
||||
}
|
||||
},
|
||||
whenDefined: {
|
||||
value: function (name) {
|
||||
return name in registry ?
|
||||
Promise.resolve() :
|
||||
whenDefined.call(customElements, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
var createElement = document.createElement;
|
||||
defineProperties(
|
||||
document,
|
||||
{
|
||||
createElement: {
|
||||
value: function (name, options) {
|
||||
var node = createElement.call(document, name);
|
||||
if (options && 'is' in options) {
|
||||
node.setAttribute('is', options.is);
|
||||
customElements.upgrade(node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
var attach = getOwnPropertyDescriptor(ElemProto, 'attachShadow').value;
|
||||
var innerHTML = getOwnPropertyDescriptor(ElemProto, 'innerHTML');
|
||||
defineProperties(
|
||||
ElemProto,
|
||||
{
|
||||
attachShadow: {
|
||||
value: function () {
|
||||
var root = attach.apply(this, arguments);
|
||||
new MutationObserver(DOMChanges).observe(root, changesOptions);
|
||||
return root;
|
||||
}
|
||||
},
|
||||
innerHTML: {
|
||||
get: innerHTML.get,
|
||||
set: function (HTML) {
|
||||
innerHTML.set.call(this, HTML);
|
||||
if (/\bis=("|')?[a-z0-9_-]+\1/i.test(HTML))
|
||||
setupSubNodes(this, setupIfNeeded);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
function DOMChanges(changes) {
|
||||
for (var i = 0, length = changes.length; i < length; i++) {
|
||||
var change = changes[i];
|
||||
var addedNodes = change.addedNodes;
|
||||
var removedNodes = change.removedNodes;
|
||||
for (var j = 0, len = addedNodes.length; j < len; j++)
|
||||
setupIfNeeded(addedNodes[j]);
|
||||
for (var j = 0, len = removedNodes.length; j < len; j++)
|
||||
disconnectIfNeeded(removedNodes[j]);
|
||||
}
|
||||
}
|
||||
function attributeChanged(changes) {
|
||||
for (var i = 0, length = changes.length; i < length; i++) {
|
||||
var change = changes[i];
|
||||
var attributeName = change.attributeName;
|
||||
var oldValue = change.oldValue;
|
||||
var target = change.target;
|
||||
var newValue = target.getAttribute(attributeName);
|
||||
if (
|
||||
ATTRIBUTE_CHANGED_CALLBACK in target &&
|
||||
!(oldValue == newValue && newValue == null)
|
||||
)
|
||||
target[ATTRIBUTE_CHANGED_CALLBACK](
|
||||
attributeName,
|
||||
oldValue,
|
||||
target.getAttribute(attributeName),
|
||||
// TODO: add getAttributeNS if the node is XML
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
function disconnectIfNeeded(node) {
|
||||
if (node.nodeType !== 1)
|
||||
return;
|
||||
var info = getInfo(node);
|
||||
if (
|
||||
info &&
|
||||
node instanceof info.Class &&
|
||||
DISCONNECTED_CALLBACK in node &&
|
||||
lifeCycle.get(node) !== DISCONNECTED_CALLBACK
|
||||
) {
|
||||
lifeCycle.set(node, DISCONNECTED_CALLBACK);
|
||||
Promise.resolve(node).then(invokeDisconnectedCallback);
|
||||
}
|
||||
setupSubNodes(node, disconnectIfNeeded);
|
||||
}
|
||||
function getInfo(node) {
|
||||
var is = node.getAttribute('is');
|
||||
if (is) {
|
||||
is = is.toLowerCase();
|
||||
if (is in registry)
|
||||
return registry[is];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function invokeConnectedCallback(node) {
|
||||
node[CONNECTED_CALLBACK]();
|
||||
}
|
||||
function invokeDisconnectedCallback(node) {
|
||||
node[DISCONNECTED_CALLBACK]();
|
||||
}
|
||||
function setup(node, info) {
|
||||
var Class = info.Class;
|
||||
var oa = Class.observedAttributes || [];
|
||||
setPrototypeOf(node, Class.prototype);
|
||||
if (oa.length) {
|
||||
new MutationObserver(attributeChanged).observe(
|
||||
node,
|
||||
{
|
||||
attributes: true,
|
||||
attributeFilter: oa,
|
||||
attributeOldValue: true
|
||||
}
|
||||
);
|
||||
var changes = [];
|
||||
for (var i = 0, length = oa.length; i < length; i++)
|
||||
changes.push({attributeName: oa[i], oldValue: null, target: node});
|
||||
attributeChanged(changes);
|
||||
}
|
||||
}
|
||||
function setupIfNeeded(node) {
|
||||
if (node.nodeType !== 1)
|
||||
return;
|
||||
var info = getInfo(node);
|
||||
if (info) {
|
||||
if (!(node instanceof info.Class))
|
||||
setup(node, info);
|
||||
if (
|
||||
CONNECTED_CALLBACK in node &&
|
||||
node.isConnected &&
|
||||
lifeCycle.get(node) !== CONNECTED_CALLBACK
|
||||
) {
|
||||
lifeCycle.set(node, CONNECTED_CALLBACK);
|
||||
Promise.resolve(node).then(invokeConnectedCallback);
|
||||
}
|
||||
}
|
||||
setupSubNodes(node, setupIfNeeded);
|
||||
}
|
||||
function setupSubNodes(node, setup) {
|
||||
for (var
|
||||
t = node.content,
|
||||
nodes = (t && t.nodeType == 11 ? t : node).querySelectorAll('[is]'),
|
||||
i = 0, length = nodes.length; i < length; i++
|
||||
)
|
||||
setup(nodes[i]);
|
||||
}
|
||||
function wrapOriginal(prototype, name) {
|
||||
var method = prototype[name];
|
||||
var desc = {};
|
||||
desc[name] = {
|
||||
value: function () {
|
||||
var result = method.apply(this, arguments);
|
||||
switch (result.nodeType) {
|
||||
case 1:
|
||||
case 11:
|
||||
setupSubNodes(result, setupIfNeeded);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
defineProperties(prototype, desc);
|
||||
}
|
||||
}());
|
||||
}
|
||||
}(document, customElements, Object));
|
Loading…
Reference in New Issue