├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── cjs └── index.js ├── coverage ├── lcov-report │ ├── base.css │ ├── html-parsed-element │ │ ├── index.html │ │ └── index.js.html │ ├── index.html │ ├── prettify.css │ ├── prettify.js │ ├── sort-arrow-sprite.png │ └── sorter.js └── lcov.info ├── esm └── index.js ├── index.js ├── min.js ├── package.json └── test ├── html-parsed-element.js ├── index.html └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | test/coverage.json 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/* 2 | node_modules/* 3 | test/* 4 | .DS_Store 5 | .gitignore 6 | .travis.yml 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | git: 5 | depth: 1 6 | branches: 7 | only: 8 | - master 9 | after_success: 10 | - "npm run coveralls" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # html-parsed-element 2 | 3 | [![Build Status](https://travis-ci.com/WebReflection/html-parsed-element.svg?branch=master)](https://travis-ci.com/WebReflection/html-parsed-element) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/html-parsed-element/badge.svg?branch=master)](https://coveralls.io/github/WebReflection/html-parsed-element?branch=master) ![WebReflection status](https://offline.report/status/webreflection.svg) 4 | 5 | A base custom element class with a reliable `parsedCallback` method and a `parsed` getter. 6 | 7 | It can be used as base class to extend or through its public static `withParsedCallback(Class, name = 'parsed'):Class` method; 8 | 9 | Class born [after discussing why `connectedCallback` is considered harmful](https://github.com/w3c/webcomponents/issues/551#issuecomment-429262811) and how to properly setup any custom element. 10 | 11 | Based off the [contributions](https://github.com/w3c/webcomponents/issues/551#issuecomment-431258689) by [@franktopel](https://github.com/franktopel) and [@irhadkul](https://github.com/irhadkul). 12 | 13 | ```js 14 | customElements.define( 15 | 'custom-element', 16 | class extends HTMLParsedElement { 17 | parsedCallback() { 18 | this.innerHTML = 'always safe!'; 19 | console.log(this.parsed); // always true here 20 | } 21 | } 22 | ); 23 | 24 | // or ... 25 | const {withParsedCallback} = HTMLParsedElement; 26 | customElements.define( 27 | 'other-element', 28 | withParsedCallback(class extends HTMLElement { 29 | parsedCallback() { 30 | this.innerHTML = 'always safe!'; 31 | console.log(this.parsed); // always true here 32 | } 33 | }) 34 | ); 35 | ``` 36 | 37 | ## How to install: 38 | 39 | ```js 40 | // esm with a good bundler 41 | import HTMLParsedElement from 'html-parsed-element'; 42 | 43 | // esm with a less good bundler 44 | import HTMLParsedElement from 'html-parsed-element/esm'; 45 | 46 | // esm via CDN (or you can use a relative/absolute path) 47 | import HTMLParsedElement from 'https://unpkg.com/html-parsed-element/esm/index.js'; 48 | 49 | // cjs 50 | const HTMLParsedElement = require('html-parsed-element'); 51 | 52 | // bad cjs bundler 53 | const HTMLParsedElement = require('html-parsed-element/cjs'); 54 | ``` 55 | 56 | ## Common gotcha 57 | 58 | As of now, `html-parsed-element` is written and will be returned for `import` and `require()` in ES2015 (formerly known as "ES6"), so **make sure your build process properly transpiles `html-parsed-element` if you need to support less capable browsers.** 59 | -------------------------------------------------------------------------------- /cjs/index.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi - ISC */ 2 | const HTMLParsedElement = (() => { 3 | const DCL = 'DOMContentLoaded'; 4 | const init = new WeakMap; 5 | const queue = []; 6 | const isParsed = el => { 7 | do { 8 | if (el.nextSibling) 9 | return true; 10 | } while (el = el.parentNode); 11 | return false; 12 | }; 13 | const upgrade = () => { 14 | queue.splice(0).forEach(info => { 15 | if (init.get(info[0]) !== true) { 16 | init.set(info[0], true); 17 | info[0][info[1]](); 18 | } 19 | }); 20 | }; 21 | document.addEventListener(DCL, upgrade); 22 | class HTMLParsedElement extends HTMLElement { 23 | static withParsedCallback(Class, name = 'parsed') { 24 | const {prototype} = Class; 25 | const {connectedCallback} = prototype; 26 | const method = name + 'Callback'; 27 | const cleanUp = (el, observer, ownerDocument, onDCL) => { 28 | observer.disconnect(); 29 | ownerDocument.removeEventListener(DCL, onDCL); 30 | parsedCallback(el); 31 | }; 32 | const parsedCallback = el => { 33 | if (!queue.length) 34 | requestAnimationFrame(upgrade); 35 | queue.push([el, method]); 36 | }; 37 | Object.defineProperties( 38 | prototype, 39 | { 40 | connectedCallback: { 41 | configurable: true, 42 | writable: true, 43 | value() { 44 | if (connectedCallback) 45 | connectedCallback.apply(this, arguments); 46 | if (method in this && !init.has(this)) { 47 | const self = this; 48 | const {ownerDocument} = self; 49 | init.set(self, false); 50 | if (ownerDocument.readyState === 'complete' || isParsed(self)) 51 | parsedCallback(self); 52 | else { 53 | const onDCL = () => cleanUp(self, observer, ownerDocument, onDCL); 54 | ownerDocument.addEventListener(DCL, onDCL); 55 | const observer = new MutationObserver(() => { 56 | /* istanbul ignore else */ 57 | if (isParsed(self)) 58 | cleanUp(self, observer, ownerDocument, onDCL); 59 | }); 60 | observer.observe(self.parentNode, {childList: true, subtree: true}); 61 | } 62 | } 63 | } 64 | }, 65 | [name]: { 66 | configurable: true, 67 | get() { 68 | return init.get(this) === true; 69 | } 70 | } 71 | } 72 | ); 73 | return Class; 74 | } 75 | } 76 | return HTMLParsedElement.withParsedCallback(HTMLParsedElement); 77 | })(); 78 | module.exports = HTMLParsedElement; 79 | -------------------------------------------------------------------------------- /coverage/lcov-report/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* dark red */ 156 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 157 | .low .chart { border:1px solid #C21F39 } 158 | /* medium red */ 159 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 160 | /* light red */ 161 | .low, .cline-no { background:#FCE1E5 } 162 | /* light green */ 163 | .high, .cline-yes { background:rgb(230,245,208) } 164 | /* medium green */ 165 | .cstat-yes { background:rgb(161,215,106) } 166 | /* dark green */ 167 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 168 | .high .chart { border:1px solid rgb(77,146,33) } 169 | /* dark yellow (gold) */ 170 | .medium .chart { border:1px solid #f9cd0b; } 171 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 172 | /* light yellow */ 173 | .medium { background: #fff4c2; } 174 | /* light gray */ 175 | span.cline-neutral { background: #eaeaea; } 176 | 177 | .cbranch-no { background: yellow !important; color: #111; } 178 | 179 | .cstat-skip { background: #ddd; color: #111; } 180 | .fstat-skip { background: #ddd; color: #111 !important; } 181 | .cbranch-skip { background: #ddd !important; color: #111; } 182 | 183 | 184 | .cover-fill, .cover-empty { 185 | display:inline-block; 186 | height: 12px; 187 | } 188 | .chart { 189 | line-height: 0; 190 | } 191 | .cover-empty { 192 | background: white; 193 | } 194 | .cover-full { 195 | border-right: none !important; 196 | } 197 | pre.prettyprint { 198 | border: none !important; 199 | padding: 0 !important; 200 | margin: 0 !important; 201 | } 202 | .com { color: #999 !important; } 203 | .ignore-none { color: #999; font-weight: normal; } 204 | 205 | .wrapper { 206 | min-height: 100%; 207 | height: auto !important; 208 | height: 100%; 209 | margin: 0 auto -48px; 210 | } 211 | .footer, .push { 212 | height: 48px; 213 | } 214 | -------------------------------------------------------------------------------- /coverage/lcov-report/html-parsed-element/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for html-parsed-element/ 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files html-parsed-element/ 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 31/31 26 |
27 |
28 | 100% 29 | Branches 30 | 14/14 31 |
32 |
33 | 100% 34 | Functions 35 | 2/2 36 |
37 |
38 | 100% 39 | Lines 40 | 30/30 41 |
42 |
43 | 1 branch 44 | Ignored      45 |
46 |
47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
FileStatementsBranchesFunctionsLines
index.js
100%31/31100%14/14100%2/2100%30/30
80 |
81 |
82 | 86 | 87 | 88 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /coverage/lcov-report/html-parsed-element/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for html-parsed-element/index.js 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / html-parsed-element/ index.js 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 31/31 26 |
27 |
28 | 100% 29 | Branches 30 | 14/14 31 |
32 |
33 | 100% 34 | Functions 35 | 2/2 36 |
37 |
38 | 100% 39 | Lines 40 | 30/30 41 |
42 |
43 | 1 branch 44 | Ignored      45 |
46 |
47 |
48 |
49 |

 50 | 
279 | 
1 51 | 2 52 | 3 53 | 4 54 | 5 55 | 6 56 | 7 57 | 8 58 | 9 59 | 10 60 | 11 61 | 12 62 | 13 63 | 14 64 | 15 65 | 16 66 | 17 67 | 18 68 | 19 69 | 20 70 | 21 71 | 22 72 | 23 73 | 24 74 | 25 75 | 26 76 | 27 77 | 28 78 | 29 79 | 30 80 | 31 81 | 32 82 | 33 83 | 34 84 | 35 85 | 36 86 | 37 87 | 38 88 | 39 89 | 40 90 | 41 91 | 42 92 | 43 93 | 44 94 | 45 95 | 46 96 | 47 97 | 48 98 | 49 99 | 50 100 | 51 101 | 52 102 | 53 103 | 54 104 | 55 105 | 56 106 | 57 107 | 58 108 | 59 109 | 60 110 | 61 111 | 62 112 | 63 113 | 64 114 | 65 115 | 66 116 | 67 117 | 68 118 | 69 119 | 70 120 | 71 121 | 72 122 | 73 123 | 74 124 | 75 125 | 76 126 | 77  127 | 128 | 129 | 130 | 131 | 132 | 12× 133 | 134 |   135 | 136 |   137 | 138 | 139 | 140 | 141 |   142 | 143 | 144 | 145 |   146 | 147 |   148 | 149 | 150 | 151 | 152 | 153 | 154 |   155 | 156 | 157 | 158 |   159 | 160 | 161 | 162 |   163 |   164 | 165 |   166 |   167 |   168 |   169 | 170 |   171 |   172 |   173 |   174 |   175 |   176 |   177 |   178 |   179 |   180 |   181 |   182 |   183 |   184 |   185 |   186 |   187 |   188 |   189 |   190 |   191 |   192 |   193 |   194 |   195 |   196 |   197 |   198 |   199 |   200 |   201 |   202 |  
/*! (c) Andrea Giammarchi - ISC */
203 | const HTMLParsedElement = (() => {
204 |   const DCL = 'DOMContentLoaded';
205 |   const init = new WeakMap;
206 |   const queue = [];
207 |   const isParsed = el => {
208 |     do {
209 |       if (el.nextSibling)
210 |         return true;
211 |     } while (el = el.parentNode);
212 |     return false;
213 |   };
214 |   const upgrade = () => {
215 |     queue.splice(0).forEach(info => {
216 |       if (init.get(info[0]) !== true) {
217 |         init.set(info[0], true);
218 |         info[0][info[1]]();
219 |       }
220 |     });
221 |   };
222 |   document.addEventListener(DCL, upgrade);
223 |   class HTMLParsedElement extends HTMLElement {
224 |     static withParsedCallback(Class, name = 'parsed') {
225 |       const {prototype} = Class;
226 |       const {connectedCallback} = prototype;
227 |       const method = name + 'Callback';
228 |       const cleanUp = (el, observer, ownerDocument, onDCL) => {
229 |         observer.disconnect();
230 |         ownerDocument.removeEventListener(DCL, onDCL);
231 |         parsedCallback(el);
232 |       };
233 |       const parsedCallback = el => {
234 |         if (!queue.length)
235 |           reEquestAnimationFrame(upgrade);
236 |         queue.push([el, method]);
237 |       };
238 |       Object.defineProperties(
239 |         prototype,
240 |         {
241 |           connectedCallback: {
242 |             configurable: true,
243 |             value() {
244 |               if (connectedCallback)
245 |                 connectedCallback.apply(this, arguments);
246 |               if (method in this && !init.has(this)) {
247 |                 const self = this;
248 |                 const {ownerDocument} = self;
249 |                 init.set(self, false);
250 |                 if (ownerDocument.readyState === 'complete' || isParsed(self))
251 |                   parsedCallback(self);
252 |                 else {
253 |                   const onDCL = () => cleanUp(self, observer, ownerDocument, onDCL);
254 |                   ownerDocument.addEventListener(DCL, onDCL);
255 |                   const observer = new MutationObserver(() => {
256 |                     /* istanbul ignore else */
257 |                     if (isParsed(self))
258 |                       cleanUp(self, observer, ownerDocument, onDCL);
259 |                   });
260 |                   observer.observe(self.parentNode, {childList: true, subtree: true});
261 |                 }
262 |               }
263 |             }
264 |           },
265 |           [name]: {
266 |             configurable: true,
267 |             get() {
268 |               return init.get(this) === true;
269 |             }
270 |           }
271 |         }
272 |       );
273 |       return Class;
274 |     }
275 |   }
276 |   return HTMLParsedElement.withParsedCallback(HTMLParsedElement);
277 | })();
278 |  
280 |
281 |
282 | 286 | 287 | 288 | 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | / 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 31/31 26 |
27 |
28 | 100% 29 | Branches 30 | 14/14 31 |
32 |
33 | 100% 34 | Functions 35 | 2/2 36 |
37 |
38 | 100% 39 | Lines 40 | 30/30 41 |
42 |
43 | 1 branch 44 | Ignored      45 |
46 |
47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
FileStatementsBranchesFunctionsLines
html-parsed-element/
100%31/31100%14/14100%2/2100%30/30
80 |
81 |
82 | 86 | 87 | 88 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.js: -------------------------------------------------------------------------------- 1 | window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); 2 | -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebReflection/html-parsed-element/11081fc102e2eec745a1a1d32723eef342df2452/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | // add the click event handler on the th so users 136 | // dont have to click on those tiny arrows 137 | el = getNthColumn(i).querySelector('.sorter').parentElement; 138 | if (el.addEventListener) { 139 | el.addEventListener('click', ithSorter(i)); 140 | } else { 141 | el.attachEvent('onclick', ithSorter(i)); 142 | } 143 | } 144 | } 145 | } 146 | // adds sorting functionality to the UI 147 | return function () { 148 | if (!getTable()) { 149 | return; 150 | } 151 | cols = loadColumns(); 152 | loadData(cols); 153 | addSortIndicators(); 154 | enableUI(); 155 | }; 156 | })(); 157 | 158 | window.addEventListener('load', addSorting); 159 | -------------------------------------------------------------------------------- /coverage/lcov.info: -------------------------------------------------------------------------------- 1 | TN: 2 | SF:/Users/agiammarchi/git/html-parsed-element/index.js 3 | FN:22,(anonymous_1) 4 | FN:43,(anonymous_2) 5 | FNF:2 6 | FNH:2 7 | FNDA:4,(anonymous_1) 8 | FNDA:5,(anonymous_2) 9 | DA:2,1 10 | DA:3,1 11 | DA:4,1 12 | DA:5,1 13 | DA:6,5 14 | DA:7,12 15 | DA:8,3 16 | DA:10,2 17 | DA:12,1 18 | DA:13,2 19 | DA:14,2 20 | DA:15,2 21 | DA:17,1 22 | DA:18,3 23 | DA:19,3 24 | DA:21,1 25 | DA:23,4 26 | DA:24,3 27 | DA:25,3 28 | DA:26,3 29 | DA:27,3 30 | DA:28,1 31 | DA:30,2 32 | DA:31,2 33 | DA:32,2 34 | DA:34,1 35 | DA:35,1 36 | DA:36,1 37 | DA:39,2 38 | DA:44,5 39 | LF:30 40 | LH:30 41 | BRDA:7,1,0,3 42 | BRDA:7,1,1,9 43 | BRDA:23,2,0,3 44 | BRDA:23,2,1,1 45 | BRDA:23,3,0,4 46 | BRDA:23,3,1,3 47 | BRDA:27,4,0,1 48 | BRDA:27,4,1,2 49 | BRDA:27,5,0,3 50 | BRDA:27,5,1,3 51 | BRDA:34,6,0,1 52 | BRDA:34,6,1,0 53 | BRDA:44,7,0,4 54 | BRDA:44,7,1,1 55 | BRF:14 56 | BRH:14 57 | end_of_record 58 | -------------------------------------------------------------------------------- /esm/index.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi - ISC */ 2 | const HTMLParsedElement = (() => { 3 | const DCL = 'DOMContentLoaded'; 4 | const init = new WeakMap; 5 | const queue = []; 6 | const isParsed = el => { 7 | do { 8 | if (el.nextSibling) 9 | return true; 10 | } while (el = el.parentNode); 11 | return false; 12 | }; 13 | const upgrade = () => { 14 | queue.splice(0).forEach(info => { 15 | if (init.get(info[0]) !== true) { 16 | init.set(info[0], true); 17 | info[0][info[1]](); 18 | } 19 | }); 20 | }; 21 | document.addEventListener(DCL, upgrade); 22 | class HTMLParsedElement extends HTMLElement { 23 | static withParsedCallback(Class, name = 'parsed') { 24 | const {prototype} = Class; 25 | const {connectedCallback} = prototype; 26 | const method = name + 'Callback'; 27 | const cleanUp = (el, observer, ownerDocument, onDCL) => { 28 | observer.disconnect(); 29 | ownerDocument.removeEventListener(DCL, onDCL); 30 | parsedCallback(el); 31 | }; 32 | const parsedCallback = el => { 33 | if (!queue.length) 34 | requestAnimationFrame(upgrade); 35 | queue.push([el, method]); 36 | }; 37 | Object.defineProperties( 38 | prototype, 39 | { 40 | connectedCallback: { 41 | configurable: true, 42 | writable: true, 43 | value() { 44 | if (connectedCallback) 45 | connectedCallback.apply(this, arguments); 46 | if (method in this && !init.has(this)) { 47 | const self = this; 48 | const {ownerDocument} = self; 49 | init.set(self, false); 50 | if (ownerDocument.readyState === 'complete' || isParsed(self)) 51 | parsedCallback(self); 52 | else { 53 | const onDCL = () => cleanUp(self, observer, ownerDocument, onDCL); 54 | ownerDocument.addEventListener(DCL, onDCL); 55 | const observer = new MutationObserver(() => { 56 | /* istanbul ignore else */ 57 | if (isParsed(self)) 58 | cleanUp(self, observer, ownerDocument, onDCL); 59 | }); 60 | observer.observe(self.parentNode, {childList: true, subtree: true}); 61 | } 62 | } 63 | } 64 | }, 65 | [name]: { 66 | configurable: true, 67 | get() { 68 | return init.get(this) === true; 69 | } 70 | } 71 | } 72 | ); 73 | return Class; 74 | } 75 | } 76 | return HTMLParsedElement.withParsedCallback(HTMLParsedElement); 77 | })(); 78 | export default HTMLParsedElement; 79 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi - ISC */ 2 | const HTMLParsedElement = (() => { 3 | const DCL = 'DOMContentLoaded'; 4 | const init = new WeakMap; 5 | const queue = []; 6 | const isParsed = el => { 7 | do { 8 | if (el.nextSibling) 9 | return true; 10 | } while (el = el.parentNode); 11 | return false; 12 | }; 13 | const upgrade = () => { 14 | queue.splice(0).forEach(info => { 15 | if (init.get(info[0]) !== true) { 16 | init.set(info[0], true); 17 | info[0][info[1]](); 18 | } 19 | }); 20 | }; 21 | document.addEventListener(DCL, upgrade); 22 | class HTMLParsedElement extends HTMLElement { 23 | static withParsedCallback(Class, name = 'parsed') { 24 | const {prototype} = Class; 25 | const {connectedCallback} = prototype; 26 | const method = name + 'Callback'; 27 | const cleanUp = (el, observer, ownerDocument, onDCL) => { 28 | observer.disconnect(); 29 | ownerDocument.removeEventListener(DCL, onDCL); 30 | parsedCallback(el); 31 | }; 32 | const parsedCallback = el => { 33 | if (!queue.length) 34 | requestAnimationFrame(upgrade); 35 | queue.push([el, method]); 36 | }; 37 | Object.defineProperties( 38 | prototype, 39 | { 40 | connectedCallback: { 41 | configurable: true, 42 | writable: true, 43 | value() { 44 | if (connectedCallback) 45 | connectedCallback.apply(this, arguments); 46 | if (method in this && !init.has(this)) { 47 | const self = this; 48 | const {ownerDocument} = self; 49 | init.set(self, false); 50 | if (ownerDocument.readyState === 'complete' || isParsed(self)) 51 | parsedCallback(self); 52 | else { 53 | const onDCL = () => cleanUp(self, observer, ownerDocument, onDCL); 54 | ownerDocument.addEventListener(DCL, onDCL); 55 | const observer = new MutationObserver(() => { 56 | /* istanbul ignore else */ 57 | if (isParsed(self)) 58 | cleanUp(self, observer, ownerDocument, onDCL); 59 | }); 60 | observer.observe(self.parentNode, {childList: true, subtree: true}); 61 | } 62 | } 63 | } 64 | }, 65 | [name]: { 66 | configurable: true, 67 | get() { 68 | return init.get(this) === true; 69 | } 70 | } 71 | } 72 | ); 73 | return Class; 74 | } 75 | } 76 | return HTMLParsedElement.withParsedCallback(HTMLParsedElement); 77 | })(); 78 | -------------------------------------------------------------------------------- /min.js: -------------------------------------------------------------------------------- 1 | /*! (c) Andrea Giammarchi - ISC */ 2 | const HTMLParsedElement=(()=>{const e="DOMContentLoaded",t=new WeakMap,n=[],a=e=>{do{if(e.nextSibling)return!0}while(e=e.parentNode);return!1},r=()=>{n.splice(0).forEach(e=>{!0!==t.get(e[0])&&(t.set(e[0],!0),e[0][e[1]]())})};document.addEventListener(e,r);class s extends HTMLElement{static withParsedCallback(s,i="parsed"){const{prototype:c}=s,{connectedCallback:o}=c,l=i+"Callback",d=(t,n,a,r)=>{n.disconnect(),a.removeEventListener(e,r),u(t)},u=e=>{n.length||requestAnimationFrame(r),n.push([e,l])};return Object.defineProperties(c,{connectedCallback:{configurable:!0,writable:!0,value(){if(o&&o.apply(this,arguments),l in this&&!t.has(this)){const n=this,{ownerDocument:r}=n;if(t.set(n,!1),"complete"===r.readyState||a(n))u(n);else{const t=()=>d(n,s,r,t);r.addEventListener(e,t);const s=new MutationObserver(()=>{a(n)&&d(n,s,r,t)});s.observe(n.parentNode,{childList:!0,subtree:!0})}}}},[i]:{configurable:!0,get(){return!0===t.get(this)}}}),s}}return s.withParsedCallback(s)})(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-parsed-element", 3 | "version": "0.4.1", 4 | "description": "A base custom element class with a reliable `parsedCallback` method", 5 | "main": "cjs/index.js", 6 | "module": "esm/index.js", 7 | "unpkg": "min.js", 8 | "scripts": { 9 | "build": "npm run cjs && npm run esm && npm run min && npm run test && npm run lcov && npm run size", 10 | "coveralls": "cat ./coverage/lcov.info | coveralls", 11 | "cjs": "cp index.js cjs && echo 'module.exports = HTMLParsedElement;' >> cjs/index.js", 12 | "esm": "cp index.js esm && echo 'export default HTMLParsedElement;' >> esm/index.js", 13 | "instrument": "istanbul instrument ./index.js -o ./test/html-parsed-element.js", 14 | "lcov": "istanbul report --include=test/coverage.json lcov", 15 | "min": "uglifyjs index.js --comments=/^!/ -c -m -o min.js", 16 | "size": "cat index.js | wc -c;cat min.js | wc -c;gzip -c9 min.js | wc -c;cat min.js | brotli | wc -c && rm -f min.js.br", 17 | "test": "npm run server & (sleep 1 && npm run nightmare && npm run report && npm run kill)", 18 | "nightmare": "node test || (npm run kill && exit 1)", 19 | "report": "istanbul report --include=test/coverage.json text-summary", 20 | "server": "node -e 'require(`fs`).writeFileSync(`pid`,require(`child_process`).spawn(`http-server`,[`test`,`-s`]).pid.toString());'", 21 | "kill": "kill -9 $(cat pid) && rm -f pid" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/WebReflection/html-parsed-element.git" 26 | }, 27 | "keywords": [ 28 | "web", 29 | "components", 30 | "custom", 31 | "elements" 32 | ], 33 | "author": "Andrea Giammarchi", 34 | "license": "ISC", 35 | "bugs": { 36 | "url": "https://github.com/WebReflection/html-parsed-element/issues" 37 | }, 38 | "homepage": "https://github.com/WebReflection/html-parsed-element#readme", 39 | "devDependencies": { 40 | "coveralls": "^3.0.2", 41 | "http-server": "^0.11.1", 42 | "istanbul": "^0.4.5", 43 | "nightmare": "^3.0.1", 44 | "uglify-es": "^3.3.9" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/html-parsed-element.js: -------------------------------------------------------------------------------- 1 | 2 | var __cov_JrwY82jsenQzSZciLCPfBw = (Function('return this'))(); 3 | if (!__cov_JrwY82jsenQzSZciLCPfBw.__coverage__) { __cov_JrwY82jsenQzSZciLCPfBw.__coverage__ = {}; } 4 | __cov_JrwY82jsenQzSZciLCPfBw = __cov_JrwY82jsenQzSZciLCPfBw.__coverage__; 5 | if (!(__cov_JrwY82jsenQzSZciLCPfBw['/Users/agiammarchi/git/html-parsed-element/index.js'])) { 6 | __cov_JrwY82jsenQzSZciLCPfBw['/Users/agiammarchi/git/html-parsed-element/index.js'] = {"path":"/Users/agiammarchi/git/html-parsed-element/index.js","s":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0},"b":{"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0],"5":[0,0],"6":[0,0],"7":[0,0]},"f":{"1":0,"2":0},"fnMap":{"1":{"name":"(anonymous_1)","line":22,"loc":{"start":{"line":22,"column":21},"end":{"line":22,"column":24}}},"2":{"name":"(anonymous_2)","line":43,"loc":{"start":{"line":43,"column":14},"end":{"line":43,"column":17}}}},"statementMap":{"1":{"start":{"line":2,"column":0},"end":{"line":47,"column":5}},"2":{"start":{"line":3,"column":2},"end":{"line":3,"column":33}},"3":{"start":{"line":4,"column":2},"end":{"line":4,"column":27}},"4":{"start":{"line":5,"column":2},"end":{"line":11,"column":4}},"5":{"start":{"line":6,"column":4},"end":{"line":9,"column":33}},"6":{"start":{"line":7,"column":6},"end":{"line":8,"column":20}},"7":{"start":{"line":8,"column":8},"end":{"line":8,"column":20}},"8":{"start":{"line":10,"column":4},"end":{"line":10,"column":17}},"9":{"start":{"line":12,"column":2},"end":{"line":16,"column":4}},"10":{"start":{"line":13,"column":4},"end":{"line":13,"column":26}},"11":{"start":{"line":14,"column":4},"end":{"line":14,"column":50}},"12":{"start":{"line":15,"column":4},"end":{"line":15,"column":23}},"13":{"start":{"line":17,"column":2},"end":{"line":20,"column":4}},"14":{"start":{"line":18,"column":4},"end":{"line":18,"column":23}},"15":{"start":{"line":19,"column":4},"end":{"line":19,"column":24}},"16":{"start":{"line":21,"column":2},"end":{"line":46,"column":4}},"17":{"start":{"line":23,"column":6},"end":{"line":41,"column":7}},"18":{"start":{"line":24,"column":8},"end":{"line":24,"column":26}},"19":{"start":{"line":25,"column":8},"end":{"line":25,"column":37}},"20":{"start":{"line":26,"column":8},"end":{"line":26,"column":30}},"21":{"start":{"line":27,"column":8},"end":{"line":40,"column":9}},"22":{"start":{"line":28,"column":10},"end":{"line":28,"column":53}},"23":{"start":{"line":30,"column":10},"end":{"line":30,"column":76}},"24":{"start":{"line":30,"column":30},"end":{"line":30,"column":75}},"25":{"start":{"line":31,"column":10},"end":{"line":31,"column":53}},"26":{"start":{"line":32,"column":10},"end":{"line":38,"column":13}},"27":{"start":{"line":34,"column":12},"end":{"line":37,"column":13}},"28":{"start":{"line":35,"column":14},"end":{"line":35,"column":60}},"29":{"start":{"line":36,"column":14},"end":{"line":36,"column":26}},"30":{"start":{"line":39,"column":10},"end":{"line":39,"column":78}},"31":{"start":{"line":44,"column":6},"end":{"line":44,"column":73}}},"branchMap":{"1":{"line":7,"type":"if","locations":[{"start":{"line":7,"column":6},"end":{"line":7,"column":6}},{"start":{"line":7,"column":6},"end":{"line":7,"column":6}}]},"2":{"line":23,"type":"if","locations":[{"start":{"line":23,"column":6},"end":{"line":23,"column":6}},{"start":{"line":23,"column":6},"end":{"line":23,"column":6}}]},"3":{"line":23,"type":"binary-expr","locations":[{"start":{"line":23,"column":10},"end":{"line":23,"column":34}},{"start":{"line":23,"column":38},"end":{"line":23,"column":53}}]},"4":{"line":27,"type":"if","locations":[{"start":{"line":27,"column":8},"end":{"line":27,"column":8}},{"start":{"line":27,"column":8},"end":{"line":27,"column":8}}]},"5":{"line":27,"type":"binary-expr","locations":[{"start":{"line":27,"column":12},"end":{"line":27,"column":51}},{"start":{"line":27,"column":55},"end":{"line":27,"column":69}}]},"6":{"line":34,"type":"if","locations":[{"start":{"line":34,"column":12},"end":{"line":34,"column":12}},{"start":{"line":34,"column":12},"end":{"line":34,"column":12},"skip":true}]},"7":{"line":44,"type":"cond-expr","locations":[{"start":{"line":44,"column":31},"end":{"line":44,"column":54}},{"start":{"line":44,"column":58},"end":{"line":44,"column":72}}]}}}; 7 | } 8 | __cov_JrwY82jsenQzSZciLCPfBw = __cov_JrwY82jsenQzSZciLCPfBw['/Users/agiammarchi/git/html-parsed-element/index.js']; 9 | __cov_JrwY82jsenQzSZciLCPfBw.s['1']++;const HTMLParsedElement=(()=>{__cov_JrwY82jsenQzSZciLCPfBw.s['2']++;const DCL='DOMContentLoaded';__cov_JrwY82jsenQzSZciLCPfBw.s['3']++;const init=new WeakMap();__cov_JrwY82jsenQzSZciLCPfBw.s['4']++;const isParsed=el=>{__cov_JrwY82jsenQzSZciLCPfBw.s['5']++;do{__cov_JrwY82jsenQzSZciLCPfBw.s['6']++;if(el.nextSibling){__cov_JrwY82jsenQzSZciLCPfBw.b['1'][0]++;__cov_JrwY82jsenQzSZciLCPfBw.s['7']++;return true;}else{__cov_JrwY82jsenQzSZciLCPfBw.b['1'][1]++;}}while(el=el.parentNode);__cov_JrwY82jsenQzSZciLCPfBw.s['8']++;return false;};__cov_JrwY82jsenQzSZciLCPfBw.s['9']++;const cleanUp=(el,observer,ownerDocument,onDCL)=>{__cov_JrwY82jsenQzSZciLCPfBw.s['10']++;observer.disconnect();__cov_JrwY82jsenQzSZciLCPfBw.s['11']++;ownerDocument.removeEventListener(DCL,onDCL);__cov_JrwY82jsenQzSZciLCPfBw.s['12']++;parsedCallback(el);};__cov_JrwY82jsenQzSZciLCPfBw.s['13']++;const parsedCallback=el=>{__cov_JrwY82jsenQzSZciLCPfBw.s['14']++;init.set(el,true);__cov_JrwY82jsenQzSZciLCPfBw.s['15']++;el.parsedCallback();};__cov_JrwY82jsenQzSZciLCPfBw.s['16']++;return class HTMLParsedElement extends HTMLElement{connectedCallback(){__cov_JrwY82jsenQzSZciLCPfBw.f['1']++;__cov_JrwY82jsenQzSZciLCPfBw.s['17']++;if((__cov_JrwY82jsenQzSZciLCPfBw.b['3'][0]++,'parsedCallback'in this)&&(__cov_JrwY82jsenQzSZciLCPfBw.b['3'][1]++,!init.has(this))){__cov_JrwY82jsenQzSZciLCPfBw.b['2'][0]++;__cov_JrwY82jsenQzSZciLCPfBw.s['18']++;const self=this;__cov_JrwY82jsenQzSZciLCPfBw.s['19']++;const {ownerDocument}=self;__cov_JrwY82jsenQzSZciLCPfBw.s['20']++;init.set(self,false);__cov_JrwY82jsenQzSZciLCPfBw.s['21']++;if((__cov_JrwY82jsenQzSZciLCPfBw.b['5'][0]++,ownerDocument.readyState==='complete')||(__cov_JrwY82jsenQzSZciLCPfBw.b['5'][1]++,isParsed(self))){__cov_JrwY82jsenQzSZciLCPfBw.b['4'][0]++;__cov_JrwY82jsenQzSZciLCPfBw.s['22']++;Promise.resolve(self).then(parsedCallback);}else{__cov_JrwY82jsenQzSZciLCPfBw.b['4'][1]++;__cov_JrwY82jsenQzSZciLCPfBw.s['23']++;const onDCL=()=>{__cov_JrwY82jsenQzSZciLCPfBw.s['24']++;return cleanUp(self,observer,ownerDocument,onDCL);};__cov_JrwY82jsenQzSZciLCPfBw.s['25']++;ownerDocument.addEventListener(DCL,onDCL);__cov_JrwY82jsenQzSZciLCPfBw.s['26']++;const observer=new MutationObserver(()=>{__cov_JrwY82jsenQzSZciLCPfBw.s['27']++;if(isParsed(self)){__cov_JrwY82jsenQzSZciLCPfBw.b['6'][0]++;__cov_JrwY82jsenQzSZciLCPfBw.s['28']++;cleanUp(self,observer,ownerDocument,onDCL);__cov_JrwY82jsenQzSZciLCPfBw.s['29']++;return true;}else{__cov_JrwY82jsenQzSZciLCPfBw.b['6'][1]++;}});__cov_JrwY82jsenQzSZciLCPfBw.s['30']++;observer.observe(self.parentNode,{childList:true,subtree:true});}}else{__cov_JrwY82jsenQzSZciLCPfBw.b['2'][1]++;}}get parsed(){__cov_JrwY82jsenQzSZciLCPfBw.f['2']++;__cov_JrwY82jsenQzSZciLCPfBw.s['31']++;return init.has(this)?(__cov_JrwY82jsenQzSZciLCPfBw.b['7'][0]++,init.get(this)===true):(__cov_JrwY82jsenQzSZciLCPfBw.b['7'][1]++,isParsed(this));}};})(); 10 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 18 | 19 |
-------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const Nightmare = require('nightmare'); 2 | const nightmare = Nightmare({show: true}); 3 | 4 | nightmare 5 | .goto(`http://localhost:8080/`) 6 | // basic assert utility (console.assert is not intercepted) 7 | .evaluate(() => { 8 | window.assert = (ok, message) => 9 | console.warn('nightmare', !!ok, message || 'unknown'); 10 | }) 11 | // provide a mechanism to intercept asserts 12 | .on('console', (type, ...args) => { 13 | if (type === 'warn' && args[0] === 'nightmare') { 14 | type = 'assert'; 15 | args.shift(); 16 | } 17 | switch (type) { 18 | case 'assert': 19 | const [ok, message] = args; 20 | if (!ok) exit(new Error(message)); 21 | else console.log(` \x1B[0;32m✔\x1B[0;0m ${message}`); 22 | break; 23 | case 'error': 24 | exit(new Error(args[0])); 25 | default: 26 | console[type](...args); 27 | } 28 | }) 29 | .evaluate(() => { 30 | const ed = document.querySelectorAll('early-definition'); 31 | assert(ed[0].parsed, 'node with sibling parsed'); 32 | assert(ed[1].parsed, 'node without sibling parsed'); 33 | const ld = document.querySelectorAll('lazy-definition'); 34 | assert(ld[0].parsed, 'lazy definition parsed'); 35 | const np = document.querySelectorAll('not-parsed'); 36 | assert(np[0].parsed, 'node without parsedCallback is parsed anyway'); 37 | }) 38 | .evaluate(() => window.__coverage__) 39 | .end() 40 | .then(coverage => { 41 | require('fs').writeFile( 42 | require('path').join(__dirname, 'coverage.json'), 43 | JSON.stringify(coverage), 44 | (err) => { 45 | if (err) exit(err); 46 | } 47 | ); 48 | }) 49 | .catch(exit); 50 | 51 | function exit(error) { 52 | console.error(error); 53 | process.exit(1); 54 | } --------------------------------------------------------------------------------