├── .gitignore
├── .npmignore
├── .npmrc
├── LICENSE
├── README.md
├── cjs
├── index.js
└── package.json
├── es.js
├── esm.js
├── esm
└── index.js
├── index.js
├── package.json
├── rollup
├── es.config.js
├── esm.config.js
└── index.config.js
└── test
├── async.html
├── index.html
├── index.js
├── nav.html
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | .travis.yml
4 | node_modules/
5 | rollup/
6 | test/
7 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2020, 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 Builtin
2 |
3 | **Social Media Photo by [Joanna Kosinska](https://unsplash.com/@joannakosinska) on [Unsplash](https://unsplash.com/)**
4 |
5 |
6 | A better custom-elements-builtin polyfill, targeting Safari, but working in every other browser that has native _customElements_.
7 |
8 |
9 | ## Update
10 |
11 | This module is included in [@ungap/custom-elements](https://github.com/ungap/custom-elements#readme) polyfill, use that to avoid dealing with `try` catches manually, it features detect everything for you.
12 |
13 | **Do not use this module directly** unless you are targeting Safari/WebKit browsers *only*.
14 |
15 | I am not maintaining how to feature detect in here, because it keeps changing, and the right polyfill that includes most updated feature detection is [this one](https://github.com/ungap/custom-elements#readme), not this module.
16 |
17 |
18 | ## To Keep In Mind
19 |
20 | If you'd like your builtin elements to be style-able, and you land these elements through their constructors or via `document.createElement('button', {is: 'custom-button'})`, remember to explicitly set their `is` attribute, because due to [this inconsistent bug](https://github.com/whatwg/html/issues/5782), Chrome and Firefox don't do that automatically, and your builtin extends might not get the desired style.
21 |
22 | ```js
23 | class BlueButton extends HTMLButtonElement {
24 | constructor() {
25 | super()
26 | // for dedicated styles, remember to do this!
27 | this.setAttribute('is', 'blue-button');
28 |
29 | // everything else is fine
30 | this.textContent = 'I am blue';
31 | }
32 | }
33 |
34 | customElements.define('blue-button', BlueButton, {extends: 'button'});
35 |
36 | document.body.appendChild(new BlueButton);
37 | ```
38 |
39 | This polyfill does that automatically because all builtin extends must be query-able via `querySelectorAll`, but other browsers won't do that automatically.
40 |
--------------------------------------------------------------------------------
/cjs/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const attributesObserver = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@webreflection/custom-elements-attributes'));
3 | const {expando} = require('@webreflection/custom-elements-upgrade');
4 | const qsaObserver = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('qsa-observer'));
5 |
6 | const {
7 | customElements, document,
8 | Element, MutationObserver, Object, Promise,
9 | Map, Set, WeakMap, Reflect
10 | } = self;
11 |
12 | const {createElement} = document;
13 | const {define, get, upgrade} = customElements;
14 | const {construct} = Reflect || {construct(HTMLElement) {
15 | return HTMLElement.call(this);
16 | }};
17 |
18 | const {defineProperty, getOwnPropertyNames, setPrototypeOf} = Object;
19 |
20 | const shadowRoots = new WeakMap;
21 | const shadows = new Set;
22 |
23 | const classes = new Map;
24 | const defined = new Map;
25 | const prototypes = new Map;
26 | const registry = new Map;
27 |
28 | const shadowed = [];
29 | const query = [];
30 |
31 | const getCE = is => registry.get(is) || get.call(customElements, is);
32 |
33 | const handle = (element, connected, selector) => {
34 | const proto = prototypes.get(selector);
35 | if (connected && !proto.isPrototypeOf(element)) {
36 | const redefine = expando(element);
37 | override = setPrototypeOf(element, proto);
38 | try { new proto.constructor; }
39 | finally {
40 | override = null;
41 | redefine();
42 | }
43 | }
44 | const method = `${connected ? '' : 'dis'}connectedCallback`;
45 | if (method in proto)
46 | element[method]();
47 | };
48 |
49 | const {parse} = qsaObserver({query, handle});
50 |
51 | const {parse: parseShadowed} = qsaObserver({
52 | query: shadowed,
53 | handle(element, connected) {
54 | if (shadowRoots.has(element)) {
55 | if (connected)
56 | shadows.add(element);
57 | else
58 | shadows.delete(element);
59 | if (query.length)
60 | parseShadow.call(query, element);
61 | }
62 | }
63 | });
64 |
65 | // qsaObserver also patches attachShadow
66 | // be sure this runs *after* that
67 | const {attachShadow} = Element.prototype;
68 | if (attachShadow)
69 | Element.prototype.attachShadow = function (init) {
70 | const root = attachShadow.call(this, init);
71 | shadowRoots.set(this, root);
72 | return root;
73 | };
74 |
75 | const whenDefined = name => {
76 | if (!defined.has(name)) {
77 | let _, $ = new Promise($ => { _ = $; });
78 | defined.set(name, {$, _});
79 | }
80 | return defined.get(name).$;
81 | };
82 |
83 | const augment = attributesObserver(whenDefined, MutationObserver);
84 |
85 | let override = null;
86 |
87 | getOwnPropertyNames(self)
88 | .filter(k => /^HTML.*Element$/.test(k))
89 | .forEach(k => {
90 | const HTMLElement = self[k];
91 | function HTMLBuiltIn() {
92 | const {constructor} = this;
93 | if (!classes.has(constructor))
94 | throw new TypeError('Illegal constructor');
95 | const {is, tag} = classes.get(constructor);
96 | if (is) {
97 | if (override)
98 | return augment(override, is);
99 | const element = createElement.call(document, tag);
100 | element.setAttribute('is', is);
101 | return augment(setPrototypeOf(element, constructor.prototype), is);
102 | }
103 | else
104 | return construct.call(this, HTMLElement, [], constructor);
105 | }
106 | setPrototypeOf(HTMLBuiltIn, HTMLElement);
107 | defineProperty(
108 | HTMLBuiltIn.prototype = HTMLElement.prototype,
109 | 'constructor',
110 | {value: HTMLBuiltIn}
111 | );
112 | defineProperty(self, k, {value: HTMLBuiltIn});
113 | });
114 |
115 | document.createElement = function (name, options) {
116 | const is = options && options.is;
117 | if (is) {
118 | const Class = registry.get(is);
119 | if (Class && classes.get(Class).tag === name)
120 | return new Class;
121 | }
122 | const element = createElement.call(document, name);
123 | if (is)
124 | element.setAttribute('is', is);
125 | return element;
126 | };
127 |
128 | customElements.get = getCE;
129 | customElements.whenDefined = whenDefined;
130 | customElements.upgrade = function (element) {
131 | const is = element.getAttribute('is');
132 | if (is) {
133 | const constructor = registry.get(is);
134 | if (constructor) {
135 | augment(setPrototypeOf(element, constructor.prototype), is);
136 | // apparently unnecessary because this is handled by qsa observer
137 | // if (element.isConnected && element.connectedCallback)
138 | // element.connectedCallback();
139 | return;
140 | }
141 | }
142 | upgrade.call(customElements, element);
143 | };
144 | customElements.define = function (is, Class, options) {
145 | if (getCE(is))
146 | throw new Error(`'${is}' has already been defined as a custom element`);
147 | let selector;
148 | const tag = options && options.extends;
149 | classes.set(Class, tag ? {is, tag} : {is: '', tag: is});
150 | if (tag) {
151 | selector = `${tag}[is="${is}"]`;
152 | prototypes.set(selector, Class.prototype);
153 | registry.set(is, Class);
154 | query.push(selector);
155 | }
156 | else {
157 | define.apply(customElements, arguments);
158 | shadowed.push(selector = is);
159 | }
160 | whenDefined(is).then(() => {
161 | if (tag) {
162 | parse(document.querySelectorAll(selector));
163 | shadows.forEach(parseShadow, [selector]);
164 | }
165 | else
166 | parseShadowed(document.querySelectorAll(selector));
167 | });
168 | defined.get(is)._(Class);
169 | };
170 |
171 | function parseShadow(element) {
172 | const root = shadowRoots.get(element);
173 | parse(root.querySelectorAll(this), element.isConnected);
174 | }
175 |
--------------------------------------------------------------------------------
/cjs/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/es.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";const{keys:e}=Object,t=!0,r=!1,n="querySelectorAll",o="querySelectorAll",{document:l,Element:s,MutationObserver:a,Set:c,WeakMap:u}=self,i=e=>o in e,{filter:d}=[];var h=e=>{const h=new u,f=(t,r)=>{let n;if(r)for(let o,l=(e=>e.matches||e.webkitMatchesSelector||e.msMatchesSelector)(t),s=0,{length:a}=p;s{e.handle(t,r,n)})))},g=(e,t=!0)=>{for(let r=0,{length:n}=e;r{const a=(r,o,l,s,c,u)=>{for(const i of r)(u||n in i)&&(c?l.has(i)||(l.add(i),s.delete(i),e(i,c)):s.has(i)||(s.add(i),l.delete(i),e(i,c)),u||a(i[n](o),o,l,s,c,t))},c=new l((e=>{if(s.length){const n=s.join(","),o=new Set,l=new Set;for(const{addedNodes:s,removedNodes:c}of e)a(c,n,o,l,r,r),a(s,n,o,l,t,r)}})),{observe:u}=c;return(c.observe=e=>u.call(c,e,{subtree:t,childList:t}))(o),c})(f,b,a,p),{attachShadow:w}=s.prototype;return w&&(s.prototype.attachShadow=function(e){const t=w.call(this,e);return y.observe(t),t}),p.length&&g(b[o](p)),{drop:e=>{for(let t=0,{length:r}=e;t{const e=y.takeRecords();for(let t=0,{length:r}=e;tT.get(e)||O.call(f,e),{parse:D}=h({query:_,handle:(t,r,n)=>{const o=R.get(n);if(r&&!o.isPrototypeOf(t)){const r=(t=>{const r=e(t),n=[],o=new Set,{length:l}=r;for(let e=0;e{for(let e=0;e{if(!L.has(e)){let t,r=new w((e=>{t=e}));L.set(e,{$:r,_:t})}return L.get(e).$},z=((e,t)=>{const r=e=>{for(let t=0,{length:r}=e;t{e.attributeChangedCallback(t,r,e.getAttribute(t))};return(o,l)=>{const{observedAttributes:s}=o.constructor;return s&&e(l).then((()=>{new t(r).observe(o,{attributes:!0,attributeOldValue:!0,attributeFilter:s});for(let e=0,{length:t}=s;e/^HTML.*Element$/.test(e))).forEach((e=>{const t=self[e];function r(){const{constructor:e}=this;if(!V.has(e))throw new TypeError("Illegal constructor");const{is:r,tag:n}=V.get(e);if(r){if(B)return z(B,r);const t=E.call(g,n);return t.setAttribute("is",r),z(P(t,e.prototype),r)}return k.call(this,t,[],e)}P(r,t),N(r.prototype=t.prototype,"constructor",{value:r}),N(self,e,{value:r})})),g.createElement=function(e,t){const r=t&&t.is;if(r){const t=T.get(r);if(t&&V.get(t).tag===e)return new t}const n=E.call(g,e);return r&&n.setAttribute("is",r),n},f.get=x,f.whenDefined=I,f.upgrade=function(e){const t=e.getAttribute("is");if(t){const r=T.get(t);if(r)return void z(P(e,r.prototype),t)}q.call(f,e)},f.define=function(e,t,r){if(x(e))throw new Error(`'${e}' has already been defined as a custom element`);let n;const o=r&&r.extends;V.set(t,o?{is:e,tag:o}:{is:"",tag:e}),o?(n=`${o}[is="${e}"]`,R.set(n,t.prototype),T.set(e,t),_.push(n)):(M.apply(f,arguments),W.push(n=e)),I(e).then((()=>{o?(D(g.querySelectorAll(n)),j.forEach(G,[n])):F(g.querySelectorAll(n))})),L.get(e)._(t)}}();
2 |
--------------------------------------------------------------------------------
/esm.js:
--------------------------------------------------------------------------------
1 | const{keys:e}=Object,t=!0,r=!1,n="querySelectorAll",o="querySelectorAll",{document:l,Element:s,MutationObserver:a,Set:c,WeakMap:u}=self,i=e=>o in e,{filter:d}=[];var h=e=>{const h=new u,f=(t,r)=>{let n;if(r)for(let o,l=(e=>e.matches||e.webkitMatchesSelector||e.msMatchesSelector)(t),s=0,{length:a}=p;s{e.handle(t,r,n)})))},g=(e,t=!0)=>{for(let r=0,{length:n}=e;r{const a=(r,o,l,s,c,u)=>{for(const i of r)(u||n in i)&&(c?l.has(i)||(l.add(i),s.delete(i),e(i,c)):s.has(i)||(s.add(i),l.delete(i),e(i,c)),u||a(i[n](o),o,l,s,c,t))},c=new l((e=>{if(s.length){const n=s.join(","),o=new Set,l=new Set;for(const{addedNodes:s,removedNodes:c}of e)a(c,n,o,l,r,r),a(s,n,o,l,t,r)}})),{observe:u}=c;return(c.observe=e=>u.call(c,e,{subtree:t,childList:t}))(o),c})(f,b,a,p),{attachShadow:w}=s.prototype;return w&&(s.prototype.attachShadow=function(e){const t=w.call(this,e);return y.observe(t),t}),p.length&&g(b[o](p)),{drop:e=>{for(let t=0,{length:r}=e;t{const e=y.takeRecords();for(let t=0,{length:r}=e;tT.get(e)||O.call(f,e),{parse:D}=h({query:_,handle:(t,r,n)=>{const o=R.get(n);if(r&&!o.isPrototypeOf(t)){const r=(t=>{const r=e(t),n=[],o=new Set,{length:l}=r;for(let e=0;e{for(let e=0;e{if(!L.has(e)){let t,r=new w((e=>{t=e}));L.set(e,{$:r,_:t})}return L.get(e).$},z=((e,t)=>{const r=e=>{for(let t=0,{length:r}=e;t{e.attributeChangedCallback(t,r,e.getAttribute(t))};return(o,l)=>{const{observedAttributes:s}=o.constructor;return s&&e(l).then((()=>{new t(r).observe(o,{attributes:!0,attributeOldValue:!0,attributeFilter:s});for(let e=0,{length:t}=s;e/^HTML.*Element$/.test(e))).forEach((e=>{const t=self[e];function r(){const{constructor:e}=this;if(!V.has(e))throw new TypeError("Illegal constructor");const{is:r,tag:n}=V.get(e);if(r){if(B)return z(B,r);const t=E.call(g,n);return t.setAttribute("is",r),z(P(t,e.prototype),r)}return k.call(this,t,[],e)}P(r,t),N(r.prototype=t.prototype,"constructor",{value:r}),N(self,e,{value:r})})),g.createElement=function(e,t){const r=t&&t.is;if(r){const t=T.get(r);if(t&&V.get(t).tag===e)return new t}const n=E.call(g,e);return r&&n.setAttribute("is",r),n},f.get=x,f.whenDefined=I,f.upgrade=function(e){const t=e.getAttribute("is");if(t){const r=T.get(t);if(r)return void z(P(e,r.prototype),t)}q.call(f,e)},f.define=function(e,t,r){if(x(e))throw new Error(`'${e}' has already been defined as a custom element`);let n;const o=r&&r.extends;V.set(t,o?{is:e,tag:o}:{is:"",tag:e}),o?(n=`${o}[is="${e}"]`,R.set(n,t.prototype),T.set(e,t),_.push(n)):(M.apply(f,arguments),W.push(n=e)),I(e).then((()=>{o?(D(g.querySelectorAll(n)),j.forEach(G,[n])):F(g.querySelectorAll(n))})),L.get(e)._(t)};
2 |
--------------------------------------------------------------------------------
/esm/index.js:
--------------------------------------------------------------------------------
1 | import attributesObserver from '@webreflection/custom-elements-attributes';
2 | import {expando} from '@webreflection/custom-elements-upgrade';
3 | import qsaObserver from 'qsa-observer';
4 |
5 | const {
6 | customElements, document,
7 | Element, MutationObserver, Object, Promise,
8 | Map, Set, WeakMap, Reflect
9 | } = self;
10 |
11 | const {createElement} = document;
12 | const {define, get, upgrade} = customElements;
13 | const {construct} = Reflect || {construct(HTMLElement) {
14 | return HTMLElement.call(this);
15 | }};
16 |
17 | const {defineProperty, getOwnPropertyNames, setPrototypeOf} = Object;
18 |
19 | const shadowRoots = new WeakMap;
20 | const shadows = new Set;
21 |
22 | const classes = new Map;
23 | const defined = new Map;
24 | const prototypes = new Map;
25 | const registry = new Map;
26 |
27 | const shadowed = [];
28 | const query = [];
29 |
30 | const getCE = is => registry.get(is) || get.call(customElements, is);
31 |
32 | const handle = (element, connected, selector) => {
33 | const proto = prototypes.get(selector);
34 | if (connected && !proto.isPrototypeOf(element)) {
35 | const redefine = expando(element);
36 | override = setPrototypeOf(element, proto);
37 | try { new proto.constructor; }
38 | finally {
39 | override = null;
40 | redefine();
41 | }
42 | }
43 | const method = `${connected ? '' : 'dis'}connectedCallback`;
44 | if (method in proto)
45 | element[method]();
46 | };
47 |
48 | const {parse} = qsaObserver({query, handle});
49 |
50 | const {parse: parseShadowed} = qsaObserver({
51 | query: shadowed,
52 | handle(element, connected) {
53 | if (shadowRoots.has(element)) {
54 | if (connected)
55 | shadows.add(element);
56 | else
57 | shadows.delete(element);
58 | if (query.length)
59 | parseShadow.call(query, element);
60 | }
61 | }
62 | });
63 |
64 | // qsaObserver also patches attachShadow
65 | // be sure this runs *after* that
66 | const {attachShadow} = Element.prototype;
67 | if (attachShadow)
68 | Element.prototype.attachShadow = function (init) {
69 | const root = attachShadow.call(this, init);
70 | shadowRoots.set(this, root);
71 | return root;
72 | };
73 |
74 | const whenDefined = name => {
75 | if (!defined.has(name)) {
76 | let _, $ = new Promise($ => { _ = $; });
77 | defined.set(name, {$, _});
78 | }
79 | return defined.get(name).$;
80 | };
81 |
82 | const augment = attributesObserver(whenDefined, MutationObserver);
83 |
84 | let override = null;
85 |
86 | getOwnPropertyNames(self)
87 | .filter(k => /^HTML.*Element$/.test(k))
88 | .forEach(k => {
89 | const HTMLElement = self[k];
90 | function HTMLBuiltIn() {
91 | const {constructor} = this;
92 | if (!classes.has(constructor))
93 | throw new TypeError('Illegal constructor');
94 | const {is, tag} = classes.get(constructor);
95 | if (is) {
96 | if (override)
97 | return augment(override, is);
98 | const element = createElement.call(document, tag);
99 | element.setAttribute('is', is);
100 | return augment(setPrototypeOf(element, constructor.prototype), is);
101 | }
102 | else
103 | return construct.call(this, HTMLElement, [], constructor);
104 | }
105 | setPrototypeOf(HTMLBuiltIn, HTMLElement);
106 | defineProperty(
107 | HTMLBuiltIn.prototype = HTMLElement.prototype,
108 | 'constructor',
109 | {value: HTMLBuiltIn}
110 | );
111 | defineProperty(self, k, {value: HTMLBuiltIn});
112 | });
113 |
114 | document.createElement = function (name, options) {
115 | const is = options && options.is;
116 | if (is) {
117 | const Class = registry.get(is);
118 | if (Class && classes.get(Class).tag === name)
119 | return new Class;
120 | }
121 | const element = createElement.call(document, name);
122 | if (is)
123 | element.setAttribute('is', is);
124 | return element;
125 | };
126 |
127 | customElements.get = getCE;
128 | customElements.whenDefined = whenDefined;
129 | customElements.upgrade = function (element) {
130 | const is = element.getAttribute('is');
131 | if (is) {
132 | const constructor = registry.get(is);
133 | if (constructor) {
134 | augment(setPrototypeOf(element, constructor.prototype), is);
135 | // apparently unnecessary because this is handled by qsa observer
136 | // if (element.isConnected && element.connectedCallback)
137 | // element.connectedCallback();
138 | return;
139 | }
140 | }
141 | upgrade.call(customElements, element);
142 | };
143 | customElements.define = function (is, Class, options) {
144 | if (getCE(is))
145 | throw new Error(`'${is}' has already been defined as a custom element`);
146 | let selector;
147 | const tag = options && options.extends;
148 | classes.set(Class, tag ? {is, tag} : {is: '', tag: is});
149 | if (tag) {
150 | selector = `${tag}[is="${is}"]`;
151 | prototypes.set(selector, Class.prototype);
152 | registry.set(is, Class);
153 | query.push(selector);
154 | }
155 | else {
156 | define.apply(customElements, arguments);
157 | shadowed.push(selector = is);
158 | }
159 | whenDefined(is).then(() => {
160 | if (tag) {
161 | parse(document.querySelectorAll(selector));
162 | shadows.forEach(parseShadow, [selector]);
163 | }
164 | else
165 | parseShadowed(document.querySelectorAll(selector));
166 | });
167 | defined.get(is)._(Class);
168 | };
169 |
170 | function parseShadow(element) {
171 | const root = shadowRoots.get(element);
172 | parse(root.querySelectorAll(this), element.isConnected);
173 | }
174 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | var attributesObserver = (whenDefined, MutationObserver) => {
5 |
6 | const attributeChanged = records => {
7 | for (let i = 0, {length} = records; i < length; i++)
8 | dispatch(records[i]);
9 | };
10 |
11 | const dispatch = ({target, attributeName, oldValue}) => {
12 | target.attributeChangedCallback(
13 | attributeName,
14 | oldValue,
15 | target.getAttribute(attributeName)
16 | );
17 | };
18 |
19 | return (target, is) => {
20 | const {observedAttributes: attributeFilter} = target.constructor;
21 | if (attributeFilter) {
22 | whenDefined(is).then(() => {
23 | new MutationObserver(attributeChanged).observe(target, {
24 | attributes: true,
25 | attributeOldValue: true,
26 | attributeFilter
27 | });
28 | for (let i = 0, {length} = attributeFilter; i < length; i++) {
29 | if (target.hasAttribute(attributeFilter[i]))
30 | dispatch({target, attributeName: attributeFilter[i], oldValue: null});
31 | }
32 | });
33 | }
34 | return target;
35 | };
36 | };
37 |
38 | const {keys} = Object;
39 |
40 | const expando = element => {
41 | const key = keys(element);
42 | const value = [];
43 | const ignore = new Set;
44 | const {length} = key;
45 | for (let i = 0; i < length; i++) {
46 | value[i] = element[key[i]];
47 | try {
48 | delete element[key[i]];
49 | }
50 | catch (SafariTP) {
51 | ignore.add(i);
52 | }
53 | }
54 | return () => {
55 | for (let i = 0; i < length; i++)
56 | ignore.has(i) || (element[key[i]] = value[i]);
57 | };
58 | };
59 |
60 | /*! (c) Andrea Giammarchi - ISC */
61 | const TRUE = true, FALSE = false, QSA$1 = 'querySelectorAll';
62 |
63 | /**
64 | * Start observing a generic document or root element.
65 | * @param {(node:Element, connected:boolean) => void} callback triggered per each dis/connected element
66 | * @param {Document|Element} [root=document] by default, the global document to observe
67 | * @param {Function} [MO=MutationObserver] by default, the global MutationObserver
68 | * @param {string[]} [query=['*']] the selectors to use within nodes
69 | * @returns {MutationObserver}
70 | */
71 | const notify = (callback, root = document, MO = MutationObserver, query = ['*']) => {
72 | const loop = (nodes, selectors, added, removed, connected, pass) => {
73 | for (const node of nodes) {
74 | if (pass || (QSA$1 in node)) {
75 | if (connected) {
76 | if (!added.has(node)) {
77 | added.add(node);
78 | removed.delete(node);
79 | callback(node, connected);
80 | }
81 | }
82 | else if (!removed.has(node)) {
83 | removed.add(node);
84 | added.delete(node);
85 | callback(node, connected);
86 | }
87 | if (!pass)
88 | loop(node[QSA$1](selectors), selectors, added, removed, connected, TRUE);
89 | }
90 | }
91 | };
92 |
93 | const mo = new MO(records => {
94 | if (query.length) {
95 | const selectors = query.join(',');
96 | const added = new Set, removed = new Set;
97 | for (const {addedNodes, removedNodes} of records) {
98 | loop(removedNodes, selectors, added, removed, FALSE, FALSE);
99 | loop(addedNodes, selectors, added, removed, TRUE, FALSE);
100 | }
101 | }
102 | });
103 |
104 | const {observe} = mo;
105 | (mo.observe = node => observe.call(mo, node, {subtree: TRUE, childList: TRUE}))(root);
106 |
107 | return mo;
108 | };
109 |
110 | const QSA = 'querySelectorAll';
111 |
112 | const {document: document$2, Element: Element$1, MutationObserver: MutationObserver$2, Set: Set$2, WeakMap: WeakMap$1} = self;
113 |
114 | const elements = element => QSA in element;
115 | const {filter} = [];
116 |
117 | var qsaObserver = options => {
118 | const live = new WeakMap$1;
119 | const drop = elements => {
120 | for (let i = 0, {length} = elements; i < length; i++)
121 | live.delete(elements[i]);
122 | };
123 | const flush = () => {
124 | const records = observer.takeRecords();
125 | for (let i = 0, {length} = records; i < length; i++) {
126 | parse(filter.call(records[i].removedNodes, elements), false);
127 | parse(filter.call(records[i].addedNodes, elements), true);
128 | }
129 | };
130 | const matches = element => (
131 | element.matches ||
132 | element.webkitMatchesSelector ||
133 | element.msMatchesSelector
134 | );
135 | const notifier = (element, connected) => {
136 | let selectors;
137 | if (connected) {
138 | for (let q, m = matches(element), i = 0, {length} = query; i < length; i++) {
139 | if (m.call(element, q = query[i])) {
140 | if (!live.has(element))
141 | live.set(element, new Set$2);
142 | selectors = live.get(element);
143 | if (!selectors.has(q)) {
144 | selectors.add(q);
145 | options.handle(element, connected, q);
146 | }
147 | }
148 | }
149 | }
150 | else if (live.has(element)) {
151 | selectors = live.get(element);
152 | live.delete(element);
153 | selectors.forEach(q => {
154 | options.handle(element, connected, q);
155 | });
156 | }
157 | };
158 | const parse = (elements, connected = true) => {
159 | for (let i = 0, {length} = elements; i < length; i++)
160 | notifier(elements[i], connected);
161 | };
162 | const {query} = options;
163 | const root = options.root || document$2;
164 | const observer = notify(notifier, root, MutationObserver$2, query);
165 | const {attachShadow} = Element$1.prototype;
166 | if (attachShadow)
167 | Element$1.prototype.attachShadow = function (init) {
168 | const shadowRoot = attachShadow.call(this, init);
169 | observer.observe(shadowRoot);
170 | return shadowRoot;
171 | };
172 | if (query.length)
173 | parse(root[QSA](query));
174 | return {drop, flush, observer, parse};
175 | };
176 |
177 | const {
178 | customElements, document: document$1,
179 | Element, MutationObserver: MutationObserver$1, Object: Object$1, Promise: Promise$1,
180 | Map, Set: Set$1, WeakMap, Reflect
181 | } = self;
182 |
183 | const {createElement} = document$1;
184 | const {define, get, upgrade} = customElements;
185 | const {construct} = Reflect || {construct(HTMLElement) {
186 | return HTMLElement.call(this);
187 | }};
188 |
189 | const {defineProperty, getOwnPropertyNames, setPrototypeOf} = Object$1;
190 |
191 | const shadowRoots = new WeakMap;
192 | const shadows = new Set$1;
193 |
194 | const classes = new Map;
195 | const defined = new Map;
196 | const prototypes = new Map;
197 | const registry = new Map;
198 |
199 | const shadowed = [];
200 | const query = [];
201 |
202 | const getCE = is => registry.get(is) || get.call(customElements, is);
203 |
204 | const handle = (element, connected, selector) => {
205 | const proto = prototypes.get(selector);
206 | if (connected && !proto.isPrototypeOf(element)) {
207 | const redefine = expando(element);
208 | override = setPrototypeOf(element, proto);
209 | try { new proto.constructor; }
210 | finally {
211 | override = null;
212 | redefine();
213 | }
214 | }
215 | const method = `${connected ? '' : 'dis'}connectedCallback`;
216 | if (method in proto)
217 | element[method]();
218 | };
219 |
220 | const {parse} = qsaObserver({query, handle});
221 |
222 | const {parse: parseShadowed} = qsaObserver({
223 | query: shadowed,
224 | handle(element, connected) {
225 | if (shadowRoots.has(element)) {
226 | if (connected)
227 | shadows.add(element);
228 | else
229 | shadows.delete(element);
230 | if (query.length)
231 | parseShadow.call(query, element);
232 | }
233 | }
234 | });
235 |
236 | // qsaObserver also patches attachShadow
237 | // be sure this runs *after* that
238 | const {attachShadow} = Element.prototype;
239 | if (attachShadow)
240 | Element.prototype.attachShadow = function (init) {
241 | const root = attachShadow.call(this, init);
242 | shadowRoots.set(this, root);
243 | return root;
244 | };
245 |
246 | const whenDefined = name => {
247 | if (!defined.has(name)) {
248 | let _, $ = new Promise$1($ => { _ = $; });
249 | defined.set(name, {$, _});
250 | }
251 | return defined.get(name).$;
252 | };
253 |
254 | const augment = attributesObserver(whenDefined, MutationObserver$1);
255 |
256 | let override = null;
257 |
258 | getOwnPropertyNames(self)
259 | .filter(k => /^HTML.*Element$/.test(k))
260 | .forEach(k => {
261 | const HTMLElement = self[k];
262 | function HTMLBuiltIn() {
263 | const {constructor} = this;
264 | if (!classes.has(constructor))
265 | throw new TypeError('Illegal constructor');
266 | const {is, tag} = classes.get(constructor);
267 | if (is) {
268 | if (override)
269 | return augment(override, is);
270 | const element = createElement.call(document$1, tag);
271 | element.setAttribute('is', is);
272 | return augment(setPrototypeOf(element, constructor.prototype), is);
273 | }
274 | else
275 | return construct.call(this, HTMLElement, [], constructor);
276 | }
277 | setPrototypeOf(HTMLBuiltIn, HTMLElement);
278 | defineProperty(
279 | HTMLBuiltIn.prototype = HTMLElement.prototype,
280 | 'constructor',
281 | {value: HTMLBuiltIn}
282 | );
283 | defineProperty(self, k, {value: HTMLBuiltIn});
284 | });
285 |
286 | document$1.createElement = function (name, options) {
287 | const is = options && options.is;
288 | if (is) {
289 | const Class = registry.get(is);
290 | if (Class && classes.get(Class).tag === name)
291 | return new Class;
292 | }
293 | const element = createElement.call(document$1, name);
294 | if (is)
295 | element.setAttribute('is', is);
296 | return element;
297 | };
298 |
299 | customElements.get = getCE;
300 | customElements.whenDefined = whenDefined;
301 | customElements.upgrade = function (element) {
302 | const is = element.getAttribute('is');
303 | if (is) {
304 | const constructor = registry.get(is);
305 | if (constructor) {
306 | augment(setPrototypeOf(element, constructor.prototype), is);
307 | // apparently unnecessary because this is handled by qsa observer
308 | // if (element.isConnected && element.connectedCallback)
309 | // element.connectedCallback();
310 | return;
311 | }
312 | }
313 | upgrade.call(customElements, element);
314 | };
315 | customElements.define = function (is, Class, options) {
316 | if (getCE(is))
317 | throw new Error(`'${is}' has already been defined as a custom element`);
318 | let selector;
319 | const tag = options && options.extends;
320 | classes.set(Class, tag ? {is, tag} : {is: '', tag: is});
321 | if (tag) {
322 | selector = `${tag}[is="${is}"]`;
323 | prototypes.set(selector, Class.prototype);
324 | registry.set(is, Class);
325 | query.push(selector);
326 | }
327 | else {
328 | define.apply(customElements, arguments);
329 | shadowed.push(selector = is);
330 | }
331 | whenDefined(is).then(() => {
332 | if (tag) {
333 | parse(document$1.querySelectorAll(selector));
334 | shadows.forEach(parseShadow, [selector]);
335 | }
336 | else
337 | parseShadowed(document$1.querySelectorAll(selector));
338 | });
339 | defined.get(is)._(Class);
340 | };
341 |
342 | function parseShadow(element) {
343 | const root = shadowRoots.get(element);
344 | parse(root.querySelectorAll(this), element.isConnected);
345 | }
346 |
347 | })();
348 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@webreflection/custom-elements-builtin",
3 | "version": "0.4.1",
4 | "description": "A better custom-elements-builtin polyfill, Safari only",
5 | "main": "./cjs/index.js",
6 | "scripts": {
7 | "build": "npm run cjs && npm run rollup:es && npm run rollup:esm && npm run rollup:index && npm run size",
8 | "cjs": "ascjs esm cjs",
9 | "rollup:es": "rollup --config rollup/es.config.js && sed -i.bck 's/^var /self./' es.js && rm -rf es.js.bck",
10 | "rollup:esm": "rollup --config rollup/esm.config.js",
11 | "rollup:index": "rollup --config rollup/index.config.js",
12 | "size": "cat es.js | brotli | wc -c && cat esm.js | brotli | wc -c"
13 | },
14 | "keywords": [
15 | "customElements",
16 | "builtin",
17 | "extends",
18 | "Safari"
19 | ],
20 | "author": "Andrea Giammarchi",
21 | "license": "ISC",
22 | "devDependencies": {
23 | "@rollup/plugin-node-resolve": "^15.0.1",
24 | "@rollup/plugin-terser": "^0.4.0",
25 | "ascjs": "^5.0.1",
26 | "rollup": "^3.18.0"
27 | },
28 | "module": "./esm/index.js",
29 | "type": "module",
30 | "exports": {
31 | "import": "./esm/index.js",
32 | "default": "./cjs/index.js"
33 | },
34 | "unpkg": "esm.js",
35 | "dependencies": {
36 | "@webreflection/custom-elements-attributes": "^0.1.4",
37 | "@webreflection/custom-elements-upgrade": "^0.1.4",
38 | "qsa-observer": "^3.0.2"
39 | },
40 | "repository": {
41 | "type": "git",
42 | "url": "git+https://github.com/WebReflection/custom-elements-builtin.git"
43 | },
44 | "bugs": {
45 | "url": "https://github.com/WebReflection/custom-elements-builtin/issues"
46 | },
47 | "homepage": "https://github.com/WebReflection/custom-elements-builtin#readme"
48 | }
49 |
--------------------------------------------------------------------------------
/rollup/es.config.js:
--------------------------------------------------------------------------------
1 | import {nodeResolve} from '@rollup/plugin-node-resolve';
2 | import terser from '@rollup/plugin-terser';
3 |
4 | export default {
5 | input: './esm/index.js',
6 | plugins: [
7 | nodeResolve(),
8 | terser()
9 | ],
10 | output: {
11 | esModule: false,
12 | exports: 'named',
13 | file: './es.js',
14 | format: 'iife',
15 | name: 'customElementsBuiltin'
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/rollup/esm.config.js:
--------------------------------------------------------------------------------
1 | import {nodeResolve} from '@rollup/plugin-node-resolve';
2 | import terser from '@rollup/plugin-terser';
3 |
4 | export default {
5 | input: './esm/index.js',
6 | plugins: [
7 | nodeResolve(),
8 | terser()
9 | ],
10 | output: {
11 | file: './esm.js',
12 | format: 'module'
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/rollup/index.config.js:
--------------------------------------------------------------------------------
1 | import {nodeResolve} from '@rollup/plugin-node-resolve';
2 |
3 | export default {
4 | input: './esm/index.js',
5 | plugins: [
6 | nodeResolve()
7 | ],
8 | output: {
9 | esModule: false,
10 | exports: 'named',
11 | file: './index.js',
12 | format: 'iife',
13 | name: 'customElementsBuiltin'
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/test/async.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | custom-elements-builtin
7 |
13 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | require('../cjs');
--------------------------------------------------------------------------------
/test/nav.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/test/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------