├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── cjs ├── index.js └── package.json ├── esm └── index.js ├── index.js ├── min.js ├── package.json └── test ├── es5 ├── index.html └── my-button.js ├── index.html ├── index.js ├── my-button.js ├── nightmare.js ├── package.json ├── shadow.html └── table.html /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | node_modules/ 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | node_modules/ 3 | rollup/ 4 | test/ 5 | package-lock.json 6 | .travis.yml 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | git: 5 | depth: 1 6 | branches: 7 | only: 8 | - master 9 | - /^greenkeeper/.*$/ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2018, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Custom Elements with Builtin Extends 2 | 3 | [![Build Status](https://travis-ci.com/ungap/custom-elements-builtin.svg?branch=master)](https://travis-ci.com/ungap/custom-elements-builtin) [![Greenkeeper badge](https://badges.greenkeeper.io/ungap/custom-elements-builtin.svg)](https://greenkeeper.io/) ![WebReflection status](https://offline.report/status/webreflection.svg) 4 | 5 | ## Deprecated 6 | 7 | The current polyfill is [@ungap/custom-elements](https://github.com/ungap/custom-elements#readme) which includes all features detection in it. 8 | -------------------------------------------------------------------------------- /cjs/index.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi - ISC */ 2 | (function (document, customElements, Object) {'use strict'; 3 | // trick of the year 🎉 https://twitter.com/WebReflection/status/1232269200317652992 4 | if (document.importNode.length != 1 || customElements.get('ungap-li')) 5 | return; 6 | var EXTENDS = 'extends'; 7 | try { 8 | // class LI extends HTMLLIElement {} 9 | var desc = {}; 10 | desc[EXTENDS] = 'li'; 11 | var HtmlLI = HTMLLIElement; 12 | var LI = function () { 13 | return Reflect.construct(HtmlLI, [], LI); 14 | }; 15 | LI.prototype = Object.create(HtmlLI.prototype); 16 | customElements.define('ungap-li', LI, desc); 17 | if (!/is="ungap-li"/.test((new LI).outerHTML)) 18 | throw desc; 19 | } catch (o_O) { 20 | (function() { 21 | var ATTRIBUTE_CHANGED_CALLBACK = 'attributeChangedCallback'; 22 | var CONNECTED_CALLBACK = 'connectedCallback'; 23 | var DISCONNECTED_CALLBACK = 'disconnectedCallback'; 24 | var ElemProto = Element.prototype; 25 | var assign = Object.assign; 26 | var create = Object.create; 27 | var defineProperties = Object.defineProperties; 28 | var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; 29 | var setPrototypeOf = Object.setPrototypeOf; 30 | var define = customElements.define; 31 | var get = customElements.get; 32 | var upgrade = customElements.upgrade; 33 | var whenDefined = customElements.whenDefined; 34 | var registry = create(null); 35 | var lifeCycle = new WeakMap; 36 | var changesOptions = {childList: true, subtree: true}; 37 | 38 | // Safari TP decided at some point that it was OK to break the Web 39 | // https://github.com/ungap/custom-elements-builtin/issues/25 40 | Reflect 41 | .ownKeys(self) 42 | .filter(function (k) { 43 | return typeof k == 'string' && /^HTML(?!Element)/.test(k); 44 | }) 45 | .forEach(function (k) { 46 | function HTMLBuiltIn() {} 47 | var tmp = self[k]; 48 | setPrototypeOf(HTMLBuiltIn, tmp); 49 | (HTMLBuiltIn.prototype = tmp.prototype).constructor = HTMLBuiltIn; 50 | tmp = {}; 51 | tmp[k] = {value: HTMLBuiltIn}; 52 | defineProperties(self, tmp); 53 | }); 54 | 55 | new MutationObserver(DOMChanges).observe(document, changesOptions); 56 | wrapOriginal(Document.prototype, 'importNode'); 57 | wrapOriginal(Node.prototype, 'cloneNode'); 58 | defineProperties( 59 | customElements, 60 | { 61 | define: { 62 | value: function (name, Class, options) { 63 | name = name.toLowerCase(); 64 | if (options && EXTENDS in options) { 65 | // currently options is not used but preserved for the future 66 | registry[name] = assign({}, options, {Class: Class}); 67 | var query = options[EXTENDS] + '[is="' + name + '"]'; 68 | var changes = document.querySelectorAll(query); 69 | for (var i = 0, length = changes.length; i < length; i++) 70 | setupIfNeeded(changes[i]); 71 | } 72 | else 73 | define.apply(customElements, arguments); 74 | } 75 | }, 76 | get: { 77 | value: function (name) { 78 | return name in registry ? 79 | registry[name].Class : 80 | get.call(customElements, name); 81 | } 82 | }, 83 | upgrade: { 84 | value: function (node) { 85 | var info = getInfo(node); 86 | if (info && !(node instanceof info.Class)) 87 | setup(node, info); 88 | else 89 | upgrade.call(customElements, node); 90 | } 91 | }, 92 | whenDefined: { 93 | value: function (name) { 94 | return name in registry ? 95 | Promise.resolve() : 96 | whenDefined.call(customElements, name); 97 | } 98 | } 99 | } 100 | ); 101 | var createElement = document.createElement; 102 | defineProperties( 103 | document, 104 | { 105 | createElement: { 106 | value: function (name, options) { 107 | var node = createElement.call(document, name); 108 | if (options && 'is' in options) { 109 | node.setAttribute('is', options.is); 110 | customElements.upgrade(node); 111 | } 112 | return node; 113 | } 114 | } 115 | } 116 | ); 117 | var attach = getOwnPropertyDescriptor(ElemProto, 'attachShadow').value; 118 | var innerHTML = getOwnPropertyDescriptor(ElemProto, 'innerHTML'); 119 | defineProperties( 120 | ElemProto, 121 | { 122 | attachShadow: { 123 | value: function () { 124 | var root = attach.apply(this, arguments); 125 | new MutationObserver(DOMChanges).observe(root, changesOptions); 126 | return root; 127 | } 128 | }, 129 | innerHTML: { 130 | get: innerHTML.get, 131 | set: function (HTML) { 132 | innerHTML.set.call(this, HTML); 133 | if (/\bis=("|')?[a-z0-9_-]+\1/i.test(HTML)) 134 | setupSubNodes(this, setupIfNeeded); 135 | } 136 | } 137 | } 138 | ); 139 | function DOMChanges(changes) { 140 | for (var i = 0, length = changes.length; i < length; i++) { 141 | var change = changes[i]; 142 | var addedNodes = change.addedNodes; 143 | var removedNodes = change.removedNodes; 144 | for (var j = 0, len = addedNodes.length; j < len; j++) 145 | setupIfNeeded(addedNodes[j]); 146 | for (var j = 0, len = removedNodes.length; j < len; j++) 147 | disconnectIfNeeded(removedNodes[j]); 148 | } 149 | } 150 | function attributeChanged(changes) { 151 | for (var i = 0, length = changes.length; i < length; i++) { 152 | var change = changes[i]; 153 | var attributeName = change.attributeName; 154 | var oldValue = change.oldValue; 155 | var target = change.target; 156 | var newValue = target.getAttribute(attributeName); 157 | if ( 158 | ATTRIBUTE_CHANGED_CALLBACK in target && 159 | !(oldValue == newValue && newValue == null) 160 | ) 161 | target[ATTRIBUTE_CHANGED_CALLBACK]( 162 | attributeName, 163 | oldValue, 164 | target.getAttribute(attributeName), 165 | // TODO: add getAttributeNS if the node is XML 166 | null 167 | ); 168 | } 169 | } 170 | function disconnectIfNeeded(node) { 171 | if (node.nodeType !== 1) 172 | return; 173 | var info = getInfo(node); 174 | if ( 175 | info && 176 | node instanceof info.Class && 177 | DISCONNECTED_CALLBACK in node && 178 | lifeCycle.get(node) !== DISCONNECTED_CALLBACK 179 | ) { 180 | lifeCycle.set(node, DISCONNECTED_CALLBACK); 181 | Promise.resolve(node).then(invokeDisconnectedCallback); 182 | } 183 | setupSubNodes(node, disconnectIfNeeded); 184 | } 185 | function getInfo(node) { 186 | var is = node.getAttribute('is'); 187 | if (is) { 188 | is = is.toLowerCase(); 189 | if (is in registry) 190 | return registry[is]; 191 | } 192 | return null; 193 | } 194 | function invokeConnectedCallback(node) { 195 | node[CONNECTED_CALLBACK](); 196 | } 197 | function invokeDisconnectedCallback(node) { 198 | node[DISCONNECTED_CALLBACK](); 199 | } 200 | function setup(node, info) { 201 | var Class = info.Class; 202 | var oa = Class.observedAttributes || []; 203 | setPrototypeOf(node, Class.prototype); 204 | if (oa.length) { 205 | new MutationObserver(attributeChanged).observe( 206 | node, 207 | { 208 | attributes: true, 209 | attributeFilter: oa, 210 | attributeOldValue: true 211 | } 212 | ); 213 | var changes = []; 214 | for (var i = 0, length = oa.length; i < length; i++) 215 | changes.push({attributeName: oa[i], oldValue: null, target: node}); 216 | attributeChanged(changes); 217 | } 218 | } 219 | function setupIfNeeded(node) { 220 | if (node.nodeType !== 1) 221 | return; 222 | var info = getInfo(node); 223 | if (info) { 224 | if (!(node instanceof info.Class)) 225 | setup(node, info); 226 | if ( 227 | CONNECTED_CALLBACK in node && 228 | node.isConnected && 229 | lifeCycle.get(node) !== CONNECTED_CALLBACK 230 | ) { 231 | lifeCycle.set(node, CONNECTED_CALLBACK); 232 | Promise.resolve(node).then(invokeConnectedCallback); 233 | } 234 | } 235 | setupSubNodes(node, setupIfNeeded); 236 | } 237 | function setupSubNodes(node, setup) { 238 | for (var 239 | t = node.content, 240 | nodes = (t && t.nodeType == 11 ? t : node).querySelectorAll('[is]'), 241 | i = 0, length = nodes.length; i < length; i++ 242 | ) 243 | setup(nodes[i]); 244 | } 245 | function wrapOriginal(prototype, name) { 246 | var method = prototype[name]; 247 | var desc = {}; 248 | desc[name] = { 249 | value: function () { 250 | var result = method.apply(this, arguments); 251 | switch (result.nodeType) { 252 | case 1: 253 | case 11: 254 | setupSubNodes(result, setupIfNeeded); 255 | } 256 | return result; 257 | } 258 | }; 259 | defineProperties(prototype, desc); 260 | } 261 | }()); 262 | } 263 | }(document, customElements, Object)); 264 | -------------------------------------------------------------------------------- /cjs/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} 2 | -------------------------------------------------------------------------------- /esm/index.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi - ISC */ 2 | (function (document, customElements, Object) {'use strict'; 3 | // trick of the year 🎉 https://twitter.com/WebReflection/status/1232269200317652992 4 | if (document.importNode.length != 1 || customElements.get('ungap-li')) 5 | return; 6 | var EXTENDS = 'extends'; 7 | try { 8 | // class LI extends HTMLLIElement {} 9 | var desc = {}; 10 | desc[EXTENDS] = 'li'; 11 | var HtmlLI = HTMLLIElement; 12 | var LI = function () { 13 | return Reflect.construct(HtmlLI, [], LI); 14 | }; 15 | LI.prototype = Object.create(HtmlLI.prototype); 16 | customElements.define('ungap-li', LI, desc); 17 | if (!/is="ungap-li"/.test((new LI).outerHTML)) 18 | throw desc; 19 | } catch (o_O) { 20 | (function() { 21 | var ATTRIBUTE_CHANGED_CALLBACK = 'attributeChangedCallback'; 22 | var CONNECTED_CALLBACK = 'connectedCallback'; 23 | var DISCONNECTED_CALLBACK = 'disconnectedCallback'; 24 | var ElemProto = Element.prototype; 25 | var assign = Object.assign; 26 | var create = Object.create; 27 | var defineProperties = Object.defineProperties; 28 | var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; 29 | var setPrototypeOf = Object.setPrototypeOf; 30 | var define = customElements.define; 31 | var get = customElements.get; 32 | var upgrade = customElements.upgrade; 33 | var whenDefined = customElements.whenDefined; 34 | var registry = create(null); 35 | var lifeCycle = new WeakMap; 36 | var changesOptions = {childList: true, subtree: true}; 37 | 38 | // Safari TP decided at some point that it was OK to break the Web 39 | // https://github.com/ungap/custom-elements-builtin/issues/25 40 | Reflect 41 | .ownKeys(self) 42 | .filter(function (k) { 43 | return typeof k == 'string' && /^HTML(?!Element)/.test(k); 44 | }) 45 | .forEach(function (k) { 46 | function HTMLBuiltIn() {} 47 | var tmp = self[k]; 48 | setPrototypeOf(HTMLBuiltIn, tmp); 49 | (HTMLBuiltIn.prototype = tmp.prototype).constructor = HTMLBuiltIn; 50 | tmp = {}; 51 | tmp[k] = {value: HTMLBuiltIn}; 52 | defineProperties(self, tmp); 53 | }); 54 | 55 | new MutationObserver(DOMChanges).observe(document, changesOptions); 56 | wrapOriginal(Document.prototype, 'importNode'); 57 | wrapOriginal(Node.prototype, 'cloneNode'); 58 | defineProperties( 59 | customElements, 60 | { 61 | define: { 62 | value: function (name, Class, options) { 63 | name = name.toLowerCase(); 64 | if (options && EXTENDS in options) { 65 | // currently options is not used but preserved for the future 66 | registry[name] = assign({}, options, {Class: Class}); 67 | var query = options[EXTENDS] + '[is="' + name + '"]'; 68 | var changes = document.querySelectorAll(query); 69 | for (var i = 0, length = changes.length; i < length; i++) 70 | setupIfNeeded(changes[i]); 71 | } 72 | else 73 | define.apply(customElements, arguments); 74 | } 75 | }, 76 | get: { 77 | value: function (name) { 78 | return name in registry ? 79 | registry[name].Class : 80 | get.call(customElements, name); 81 | } 82 | }, 83 | upgrade: { 84 | value: function (node) { 85 | var info = getInfo(node); 86 | if (info && !(node instanceof info.Class)) 87 | setup(node, info); 88 | else 89 | upgrade.call(customElements, node); 90 | } 91 | }, 92 | whenDefined: { 93 | value: function (name) { 94 | return name in registry ? 95 | Promise.resolve() : 96 | whenDefined.call(customElements, name); 97 | } 98 | } 99 | } 100 | ); 101 | var createElement = document.createElement; 102 | defineProperties( 103 | document, 104 | { 105 | createElement: { 106 | value: function (name, options) { 107 | var node = createElement.call(document, name); 108 | if (options && 'is' in options) { 109 | node.setAttribute('is', options.is); 110 | customElements.upgrade(node); 111 | } 112 | return node; 113 | } 114 | } 115 | } 116 | ); 117 | var attach = getOwnPropertyDescriptor(ElemProto, 'attachShadow').value; 118 | var innerHTML = getOwnPropertyDescriptor(ElemProto, 'innerHTML'); 119 | defineProperties( 120 | ElemProto, 121 | { 122 | attachShadow: { 123 | value: function () { 124 | var root = attach.apply(this, arguments); 125 | new MutationObserver(DOMChanges).observe(root, changesOptions); 126 | return root; 127 | } 128 | }, 129 | innerHTML: { 130 | get: innerHTML.get, 131 | set: function (HTML) { 132 | innerHTML.set.call(this, HTML); 133 | if (/\bis=("|')?[a-z0-9_-]+\1/i.test(HTML)) 134 | setupSubNodes(this, setupIfNeeded); 135 | } 136 | } 137 | } 138 | ); 139 | function DOMChanges(changes) { 140 | for (var i = 0, length = changes.length; i < length; i++) { 141 | var change = changes[i]; 142 | var addedNodes = change.addedNodes; 143 | var removedNodes = change.removedNodes; 144 | for (var j = 0, len = addedNodes.length; j < len; j++) 145 | setupIfNeeded(addedNodes[j]); 146 | for (var j = 0, len = removedNodes.length; j < len; j++) 147 | disconnectIfNeeded(removedNodes[j]); 148 | } 149 | } 150 | function attributeChanged(changes) { 151 | for (var i = 0, length = changes.length; i < length; i++) { 152 | var change = changes[i]; 153 | var attributeName = change.attributeName; 154 | var oldValue = change.oldValue; 155 | var target = change.target; 156 | var newValue = target.getAttribute(attributeName); 157 | if ( 158 | ATTRIBUTE_CHANGED_CALLBACK in target && 159 | !(oldValue == newValue && newValue == null) 160 | ) 161 | target[ATTRIBUTE_CHANGED_CALLBACK]( 162 | attributeName, 163 | oldValue, 164 | target.getAttribute(attributeName), 165 | // TODO: add getAttributeNS if the node is XML 166 | null 167 | ); 168 | } 169 | } 170 | function disconnectIfNeeded(node) { 171 | if (node.nodeType !== 1) 172 | return; 173 | var info = getInfo(node); 174 | if ( 175 | info && 176 | node instanceof info.Class && 177 | DISCONNECTED_CALLBACK in node && 178 | lifeCycle.get(node) !== DISCONNECTED_CALLBACK 179 | ) { 180 | lifeCycle.set(node, DISCONNECTED_CALLBACK); 181 | Promise.resolve(node).then(invokeDisconnectedCallback); 182 | } 183 | setupSubNodes(node, disconnectIfNeeded); 184 | } 185 | function getInfo(node) { 186 | var is = node.getAttribute('is'); 187 | if (is) { 188 | is = is.toLowerCase(); 189 | if (is in registry) 190 | return registry[is]; 191 | } 192 | return null; 193 | } 194 | function invokeConnectedCallback(node) { 195 | node[CONNECTED_CALLBACK](); 196 | } 197 | function invokeDisconnectedCallback(node) { 198 | node[DISCONNECTED_CALLBACK](); 199 | } 200 | function setup(node, info) { 201 | var Class = info.Class; 202 | var oa = Class.observedAttributes || []; 203 | setPrototypeOf(node, Class.prototype); 204 | if (oa.length) { 205 | new MutationObserver(attributeChanged).observe( 206 | node, 207 | { 208 | attributes: true, 209 | attributeFilter: oa, 210 | attributeOldValue: true 211 | } 212 | ); 213 | var changes = []; 214 | for (var i = 0, length = oa.length; i < length; i++) 215 | changes.push({attributeName: oa[i], oldValue: null, target: node}); 216 | attributeChanged(changes); 217 | } 218 | } 219 | function setupIfNeeded(node) { 220 | if (node.nodeType !== 1) 221 | return; 222 | var info = getInfo(node); 223 | if (info) { 224 | if (!(node instanceof info.Class)) 225 | setup(node, info); 226 | if ( 227 | CONNECTED_CALLBACK in node && 228 | node.isConnected && 229 | lifeCycle.get(node) !== CONNECTED_CALLBACK 230 | ) { 231 | lifeCycle.set(node, CONNECTED_CALLBACK); 232 | Promise.resolve(node).then(invokeConnectedCallback); 233 | } 234 | } 235 | setupSubNodes(node, setupIfNeeded); 236 | } 237 | function setupSubNodes(node, setup) { 238 | for (var 239 | t = node.content, 240 | nodes = (t && t.nodeType == 11 ? t : node).querySelectorAll('[is]'), 241 | i = 0, length = nodes.length; i < length; i++ 242 | ) 243 | setup(nodes[i]); 244 | } 245 | function wrapOriginal(prototype, name) { 246 | var method = prototype[name]; 247 | var desc = {}; 248 | desc[name] = { 249 | value: function () { 250 | var result = method.apply(this, arguments); 251 | switch (result.nodeType) { 252 | case 1: 253 | case 11: 254 | setupSubNodes(result, setupIfNeeded); 255 | } 256 | return result; 257 | } 258 | }; 259 | defineProperties(prototype, desc); 260 | } 261 | }()); 262 | } 263 | }(document, customElements, Object)); 264 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi - ISC */ 2 | (function (document, customElements, Object) {'use strict'; 3 | // trick of the year 🎉 https://twitter.com/WebReflection/status/1232269200317652992 4 | if (document.importNode.length != 1 || customElements.get('ungap-li')) 5 | return; 6 | var EXTENDS = 'extends'; 7 | try { 8 | // class LI extends HTMLLIElement {} 9 | var desc = {}; 10 | desc[EXTENDS] = 'li'; 11 | var HtmlLI = HTMLLIElement; 12 | var LI = function () { 13 | return Reflect.construct(HtmlLI, [], LI); 14 | }; 15 | LI.prototype = Object.create(HtmlLI.prototype); 16 | customElements.define('ungap-li', LI, desc); 17 | if (!/is="ungap-li"/.test((new LI).outerHTML)) 18 | throw desc; 19 | } catch (o_O) { 20 | (function() { 21 | var ATTRIBUTE_CHANGED_CALLBACK = 'attributeChangedCallback'; 22 | var CONNECTED_CALLBACK = 'connectedCallback'; 23 | var DISCONNECTED_CALLBACK = 'disconnectedCallback'; 24 | var ElemProto = Element.prototype; 25 | var assign = Object.assign; 26 | var create = Object.create; 27 | var defineProperties = Object.defineProperties; 28 | var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; 29 | var setPrototypeOf = Object.setPrototypeOf; 30 | var define = customElements.define; 31 | var get = customElements.get; 32 | var upgrade = customElements.upgrade; 33 | var whenDefined = customElements.whenDefined; 34 | var registry = create(null); 35 | var lifeCycle = new WeakMap; 36 | var changesOptions = {childList: true, subtree: true}; 37 | 38 | // Safari TP decided at some point that it was OK to break the Web 39 | // https://github.com/ungap/custom-elements-builtin/issues/25 40 | Reflect 41 | .ownKeys(self) 42 | .filter(function (k) { 43 | return typeof k == 'string' && /^HTML(?!Element)/.test(k); 44 | }) 45 | .forEach(function (k) { 46 | function HTMLBuiltIn() {} 47 | var tmp = self[k]; 48 | setPrototypeOf(HTMLBuiltIn, tmp); 49 | (HTMLBuiltIn.prototype = tmp.prototype).constructor = HTMLBuiltIn; 50 | tmp = {}; 51 | tmp[k] = {value: HTMLBuiltIn}; 52 | defineProperties(self, tmp); 53 | }); 54 | 55 | new MutationObserver(DOMChanges).observe(document, changesOptions); 56 | wrapOriginal(Document.prototype, 'importNode'); 57 | wrapOriginal(Node.prototype, 'cloneNode'); 58 | defineProperties( 59 | customElements, 60 | { 61 | define: { 62 | value: function (name, Class, options) { 63 | name = name.toLowerCase(); 64 | if (options && EXTENDS in options) { 65 | // currently options is not used but preserved for the future 66 | registry[name] = assign({}, options, {Class: Class}); 67 | var query = options[EXTENDS] + '[is="' + name + '"]'; 68 | var changes = document.querySelectorAll(query); 69 | for (var i = 0, length = changes.length; i < length; i++) 70 | setupIfNeeded(changes[i]); 71 | } 72 | else 73 | define.apply(customElements, arguments); 74 | } 75 | }, 76 | get: { 77 | value: function (name) { 78 | return name in registry ? 79 | registry[name].Class : 80 | get.call(customElements, name); 81 | } 82 | }, 83 | upgrade: { 84 | value: function (node) { 85 | var info = getInfo(node); 86 | if (info && !(node instanceof info.Class)) 87 | setup(node, info); 88 | else 89 | upgrade.call(customElements, node); 90 | } 91 | }, 92 | whenDefined: { 93 | value: function (name) { 94 | return name in registry ? 95 | Promise.resolve() : 96 | whenDefined.call(customElements, name); 97 | } 98 | } 99 | } 100 | ); 101 | var createElement = document.createElement; 102 | defineProperties( 103 | document, 104 | { 105 | createElement: { 106 | value: function (name, options) { 107 | var node = createElement.call(document, name); 108 | if (options && 'is' in options) { 109 | node.setAttribute('is', options.is); 110 | customElements.upgrade(node); 111 | } 112 | return node; 113 | } 114 | } 115 | } 116 | ); 117 | var attach = getOwnPropertyDescriptor(ElemProto, 'attachShadow').value; 118 | var innerHTML = getOwnPropertyDescriptor(ElemProto, 'innerHTML'); 119 | defineProperties( 120 | ElemProto, 121 | { 122 | attachShadow: { 123 | value: function () { 124 | var root = attach.apply(this, arguments); 125 | new MutationObserver(DOMChanges).observe(root, changesOptions); 126 | return root; 127 | } 128 | }, 129 | innerHTML: { 130 | get: innerHTML.get, 131 | set: function (HTML) { 132 | innerHTML.set.call(this, HTML); 133 | if (/\bis=("|')?[a-z0-9_-]+\1/i.test(HTML)) 134 | setupSubNodes(this, setupIfNeeded); 135 | } 136 | } 137 | } 138 | ); 139 | function DOMChanges(changes) { 140 | for (var i = 0, length = changes.length; i < length; i++) { 141 | var change = changes[i]; 142 | var addedNodes = change.addedNodes; 143 | var removedNodes = change.removedNodes; 144 | for (var j = 0, len = addedNodes.length; j < len; j++) 145 | setupIfNeeded(addedNodes[j]); 146 | for (var j = 0, len = removedNodes.length; j < len; j++) 147 | disconnectIfNeeded(removedNodes[j]); 148 | } 149 | } 150 | function attributeChanged(changes) { 151 | for (var i = 0, length = changes.length; i < length; i++) { 152 | var change = changes[i]; 153 | var attributeName = change.attributeName; 154 | var oldValue = change.oldValue; 155 | var target = change.target; 156 | var newValue = target.getAttribute(attributeName); 157 | if ( 158 | ATTRIBUTE_CHANGED_CALLBACK in target && 159 | !(oldValue == newValue && newValue == null) 160 | ) 161 | target[ATTRIBUTE_CHANGED_CALLBACK]( 162 | attributeName, 163 | oldValue, 164 | target.getAttribute(attributeName), 165 | // TODO: add getAttributeNS if the node is XML 166 | null 167 | ); 168 | } 169 | } 170 | function disconnectIfNeeded(node) { 171 | if (node.nodeType !== 1) 172 | return; 173 | var info = getInfo(node); 174 | if ( 175 | info && 176 | node instanceof info.Class && 177 | DISCONNECTED_CALLBACK in node && 178 | lifeCycle.get(node) !== DISCONNECTED_CALLBACK 179 | ) { 180 | lifeCycle.set(node, DISCONNECTED_CALLBACK); 181 | Promise.resolve(node).then(invokeDisconnectedCallback); 182 | } 183 | setupSubNodes(node, disconnectIfNeeded); 184 | } 185 | function getInfo(node) { 186 | var is = node.getAttribute('is'); 187 | if (is) { 188 | is = is.toLowerCase(); 189 | if (is in registry) 190 | return registry[is]; 191 | } 192 | return null; 193 | } 194 | function invokeConnectedCallback(node) { 195 | node[CONNECTED_CALLBACK](); 196 | } 197 | function invokeDisconnectedCallback(node) { 198 | node[DISCONNECTED_CALLBACK](); 199 | } 200 | function setup(node, info) { 201 | var Class = info.Class; 202 | var oa = Class.observedAttributes || []; 203 | setPrototypeOf(node, Class.prototype); 204 | if (oa.length) { 205 | new MutationObserver(attributeChanged).observe( 206 | node, 207 | { 208 | attributes: true, 209 | attributeFilter: oa, 210 | attributeOldValue: true 211 | } 212 | ); 213 | var changes = []; 214 | for (var i = 0, length = oa.length; i < length; i++) 215 | changes.push({attributeName: oa[i], oldValue: null, target: node}); 216 | attributeChanged(changes); 217 | } 218 | } 219 | function setupIfNeeded(node) { 220 | if (node.nodeType !== 1) 221 | return; 222 | var info = getInfo(node); 223 | if (info) { 224 | if (!(node instanceof info.Class)) 225 | setup(node, info); 226 | if ( 227 | CONNECTED_CALLBACK in node && 228 | node.isConnected && 229 | lifeCycle.get(node) !== CONNECTED_CALLBACK 230 | ) { 231 | lifeCycle.set(node, CONNECTED_CALLBACK); 232 | Promise.resolve(node).then(invokeConnectedCallback); 233 | } 234 | } 235 | setupSubNodes(node, setupIfNeeded); 236 | } 237 | function setupSubNodes(node, setup) { 238 | for (var 239 | t = node.content, 240 | nodes = (t && t.nodeType == 11 ? t : node).querySelectorAll('[is]'), 241 | i = 0, length = nodes.length; i < length; i++ 242 | ) 243 | setup(nodes[i]); 244 | } 245 | function wrapOriginal(prototype, name) { 246 | var method = prototype[name]; 247 | var desc = {}; 248 | desc[name] = { 249 | value: function () { 250 | var result = method.apply(this, arguments); 251 | switch (result.nodeType) { 252 | case 1: 253 | case 11: 254 | setupSubNodes(result, setupIfNeeded); 255 | } 256 | return result; 257 | } 258 | }; 259 | defineProperties(prototype, desc); 260 | } 261 | }()); 262 | } 263 | }(document, customElements, Object)); 264 | -------------------------------------------------------------------------------- /min.js: -------------------------------------------------------------------------------- 1 | !function(P,H,k){"use strict";if(1==P.importNode.length&&!H.get("ungap-li")){var D="extends";try{var e={extends:"li"},t=HTMLLIElement,n=function(){return Reflect.construct(t,[],n)};if(n.prototype=k.create(t.prototype),H.define("ungap-li",n,e),!/is="ungap-li"/.test((new n).outerHTML))throw e}catch(e){!function(){var l="attributeChangedCallback",n="connectedCallback",r="disconnectedCallback",e=Element.prototype,i=k.assign,t=k.create,o=k.defineProperties,a=k.getOwnPropertyDescriptor,s=k.setPrototypeOf,u=H.define,c=H.get,f=H.upgrade,p=H.whenDefined,v=t(null),d=new WeakMap,g={childList:!0,subtree:!0};Reflect.ownKeys(self).filter(function(e){return"string"==typeof e&&/^HTML(?!Element)/.test(e)}).forEach(function(e){function t(){}var n=self[e];s(t,n),(t.prototype=n.prototype).constructor=t,(n={})[e]={value:t},o(self,n)}),new MutationObserver(m).observe(P,g),O(Document.prototype,"importNode"),O(Node.prototype,"cloneNode"),o(H,{define:{value:function(e,t,n){if(e=e.toLowerCase(),n&&D in n){v[e]=i({},n,{Class:t});for(var e=n[D]+'[is="'+e+'"]',r=P.querySelectorAll(e),o=0,a=r.length;o test/es5/my-button.js", 10 | "build": "npm run cjs && npm run esm && npm run min && npm run test && npm run size", 11 | "cjs": "cp index.js cjs/", 12 | "esm": "cp index.js esm/", 13 | "min": "uglifyjs index.js -c -m -o min.js", 14 | "size": "cat index.js | wc -c && cat min.js | wc -c && gzip -c9 min.js | wc -c && cat min.js | brotli | wc -c", 15 | "test": "npm run server & (sleep 1 && npm run nightmare && npm run kill)", 16 | "nightmare": "node test/nightmare.js || (npm run kill && exit 1)", 17 | "server": "node -e 'require(`fs`).writeFileSync(`pid`,require(`child_process`).spawn(`http-server`,[`.`,`-s`]).pid.toString());'", 18 | "kill": "kill -9 $(cat pid) && rm -f pid" 19 | }, 20 | "keywords": [ 21 | "customElements", 22 | "builtin", 23 | "polyfill", 24 | "ungap" 25 | ], 26 | "author": "Andrea Giammarchi", 27 | "license": "ISC", 28 | "devDependencies": { 29 | "@babel/cli": "^7.13.16", 30 | "@babel/core": "^7.14.0", 31 | "@babel/preset-env": "^7.14.1", 32 | "http-server": "^0.12.3", 33 | "nightmare": "^3.0.2", 34 | "uglify-js": "^3.13.5" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "git+https://github.com/ungap/custom-elements-builtin.git" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/ungap/custom-elements-builtin/issues" 42 | }, 43 | "homepage": "https://github.com/ungap/custom-elements-builtin#readme", 44 | "type": "module", 45 | "exports": { 46 | "import": "./esm/index.js", 47 | "default": "./cjs/index.js" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/es5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Custom Elements Built-in Extends 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /test/es5/my-button.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 4 | 5 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 6 | 7 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 8 | 9 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 10 | 11 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 12 | 13 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 14 | 15 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 16 | 17 | function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null || !_isNativeFunction(Class)) return Class; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); } 18 | 19 | function isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } 20 | 21 | function _construct(Parent, args, Class) { if (isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } 22 | 23 | function _isNativeFunction(fn) { return Function.toString.call(fn).indexOf("[native code]") !== -1; } 24 | 25 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 26 | 27 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 28 | 29 | customElements.define('my-button', 30 | /*#__PURE__*/ 31 | function (_HTMLButtonElement) { 32 | _inherits(MyButton, _HTMLButtonElement); 33 | 34 | function MyButton() { 35 | _classCallCheck(this, MyButton); 36 | 37 | return _possibleConstructorReturn(this, _getPrototypeOf(MyButton).apply(this, arguments)); 38 | } 39 | 40 | _createClass(MyButton, [{ 41 | key: "attributeChangedCallback", 42 | value: function attributeChangedCallback(name, oldValue, newValue, nsValue) { 43 | this.style.color = newValue; 44 | } 45 | }, { 46 | key: "connectedCallback", 47 | value: function connectedCallback() { 48 | this.addEventListener('click', this); 49 | } 50 | }, { 51 | key: "disconnectedCallback", 52 | value: function disconnectedCallback() { 53 | this.removeEventListener('click', this); 54 | } 55 | }, { 56 | key: "handleEvent", 57 | value: function handleEvent(event) { 58 | var next = this.nextElementSibling || this.parentNode.appendChild(document.createElement('div')); 59 | next.textContent = "".concat(event.type, " @ ").concat(new Date()); 60 | } 61 | }], [{ 62 | key: "observedAttributes", 63 | get: function get() { 64 | return ['color']; 65 | } 66 | }]); 67 | 68 | return MyButton; 69 | }(_wrapNativeSuper(HTMLButtonElement)), { 70 | 'extends': 'button' 71 | }); 72 | 73 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Custom Elements Built-in Extends 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ungap/custom-elements-builtin/b4a0467f282f56dc8fe7ec76de42cfcd12837aa0/test/index.js -------------------------------------------------------------------------------- /test/my-button.js: -------------------------------------------------------------------------------- 1 | customElements.define( 2 | 'my-button', 3 | class MyButton extends HTMLButtonElement { 4 | static get observedAttributes() { return ['color']; } 5 | attributeChangedCallback(name, oldValue, newValue, nsValue) { 6 | this.style.color = newValue; 7 | } 8 | connectedCallback() { 9 | this.addEventListener('click', this); 10 | } 11 | disconnectedCallback() { 12 | this.removeEventListener('click', this); 13 | } 14 | handleEvent(event) { 15 | const next = this.nextElementSibling || 16 | this.parentNode.appendChild( 17 | document.createElement('div') 18 | ); 19 | next.textContent = `${event.type} @ ${new Date}`; 20 | } 21 | }, 22 | {'extends': 'button'} 23 | ); 24 | -------------------------------------------------------------------------------- /test/nightmare.js: -------------------------------------------------------------------------------- 1 | const Nightmare = require('nightmare'); 2 | const nightmare = Nightmare({show: false}); 3 | 4 | nightmare 5 | .goto(`http://localhost:8080/test/`) 6 | .evaluate(() => { 7 | window.assert = (ok, message) => 8 | console.warn('nightmare', !!ok, message || 'unknown'); 9 | }) 10 | .on('console', (type, ...args) => { 11 | if (type === 'warn' && args[0] === 'nightmare') { 12 | type = 'assert'; 13 | args.shift(); 14 | } 15 | switch (type) { 16 | case 'assert': 17 | const [ok, message] = args; 18 | if (!ok) exit(new Error(message)); 19 | else console.log(` \x1B[0;32m✔\x1B[0;0m ${message}`); 20 | break; 21 | case 'error': 22 | exit(new Error(args[0])); 23 | default: 24 | console[type](...args); 25 | } 26 | }) 27 | .evaluate(() => { 28 | const constructor = customElements.get('my-button'); 29 | assert(typeof constructor === 'function', 'MyButton is registered'); 30 | const mybtn = document.querySelector('button[is="my-button"]'); 31 | assert(mybtn instanceof constructor, 'DOM node was upgraded'); 32 | assert(mybtn.style.color === 'blue', 'attribute change applied'); 33 | }) 34 | .end() 35 | .catch(exit); 36 | 37 | function exit(error) { 38 | console.error(error); 39 | process.exit(1); 40 | } 41 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} 2 | -------------------------------------------------------------------------------- /test/shadow.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /test/table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Custom Elements Built-in Extends 8 | 9 | 10 | 51 | 52 | 53 | 54 | 55 | 56 |
1
2
57 | 58 | --------------------------------------------------------------------------------