github.com/uchennaokeke444/nomad@v0.11.8/website/public/ie-custom-properties.js (about) 1 /*! ie11CustomProperties.js v2.7.2 | MIT License | https://git.io/fjXMN */ 2 // c1.onElement helper 3 !(function() { 4 'use strict' 5 6 if (!Element.prototype.matches) 7 Element.prototype.matches = Element.prototype.msMatchesSelector 8 9 var w = window 10 if (!w.c1) w.c1 = {} 11 var listeners = [], 12 root = document, 13 Observer 14 15 c1.onElement = function(selector, options /*, disconnectedCallback*/) { 16 if (typeof options === 'function') { 17 options = { parsed: options } 18 } 19 var listener = { 20 selector: selector, 21 immediate: options.immediate, 22 //disconnectedCallback: disconnectedCallback, 23 elements: new WeakMap() 24 } 25 26 if (options.parsed) { 27 listener.parsed = function(el) { 28 requestAnimationFrame(function() { 29 options.parsed(el) 30 }) 31 } 32 } 33 34 var els = root.querySelectorAll(listener.selector), 35 i = 0, 36 el 37 while ((el = els[i++])) { 38 listener.elements.set(el, true) 39 listener.parsed && listener.parsed.call(el, el) 40 listener.immediate && listener.immediate.call(el, el) 41 } 42 43 listeners.push(listener) 44 if (!Observer) { 45 Observer = new MutationObserver(checkMutations) 46 Observer.observe(root, { 47 childList: true, 48 subtree: true 49 }) 50 } 51 checkListener(listener) 52 } 53 function checkListener(listener, target) { 54 var i = 0, 55 el, 56 els = [] 57 target && target.matches(listener.selector) && els.push(target) 58 if (loaded) { 59 // ok? check inside node on innerHTML - only when loaded 60 Array.prototype.push.apply( 61 els, 62 (target || root).querySelectorAll(listener.selector) 63 ) 64 } 65 while ((el = els[i++])) { 66 if (listener.elements.has(el)) continue 67 listener.elements.set(el, true) 68 //listener.connectedCallback.call(el, el); 69 listener.parsed && listener.parsed.call(el, el) 70 listener.immediate && listener.immediate.call(el, el) 71 } 72 } 73 function checkListeners(inside) { 74 var i = 0, 75 listener 76 while ((listener = listeners[i++])) checkListener(listener, inside) 77 } 78 function checkMutations(mutations) { 79 var j = 0, 80 i, 81 mutation, 82 nodes, 83 target 84 while ((mutation = mutations[j++])) { 85 ;(nodes = mutation.addedNodes), (i = 0) 86 while ((target = nodes[i++])) 87 target.nodeType === 1 && checkListeners(target) 88 } 89 } 90 91 var loaded = false 92 document.addEventListener('DOMContentLoaded', function() { 93 loaded = true 94 }) 95 96 // svg polyfills 97 function copyProperty(prop, from, to) { 98 var desc = Object.getOwnPropertyDescriptor(from, prop) 99 Object.defineProperty(to, prop, desc) 100 } 101 if (!('classList' in Element.prototype)) { 102 copyProperty('classList', HTMLElement.prototype, Element.prototype) 103 } 104 if (!('innerHTML' in Element.prototype)) { 105 copyProperty('innerHTML', HTMLElement.prototype, Element.prototype) 106 } 107 if (!('sheet' in SVGStyleElement.prototype)) { 108 Object.defineProperty(SVGStyleElement.prototype, 'sheet', { 109 get: function() { 110 var all = document.styleSheets 111 for (var i = 0, sheet; (sheet = all[i++]); ) { 112 if (sheet.ownerNode === this) return sheet 113 } 114 } 115 }) 116 } 117 // if ('children' in HTMLElement.prototype && !('children' in Element.prototype)) { 118 // copyProperty('children', HTMLElement.prototype, Element.prototype); 119 // } 120 // if ('contains' in HTMLElement.prototype && !('contains' in Element.prototype)) { 121 // copyProperty('contains', HTMLElement.prototype, Element.prototype); 122 // } 123 // if ('getElementsByClassName' in HTMLElement.prototype && !('getElementsByClassName' in Element.prototype)) { 124 // copyProperty('getElementsByClassName', HTMLElement.prototype, Element.prototype); 125 // } 126 })() 127 128 // main logic 129 !(function() { 130 'use strict' 131 var testEl = document.createElement('i') 132 testEl.style.setProperty('--x', 'y') 133 if (testEl.style.getPropertyValue('--x') === 'y' || !testEl.msMatchesSelector) 134 return 135 136 // cached regexps, better performance 137 const regFindSetters = /([\s{;])(--([A-Za-z0-9-_]+\s*:[^;!}{]+)(!important)?)(?=\s*([;}]|$))/g 138 const regFindGetters = /([{;]\s*)([A-Za-z0-9-_]+\s*:[^;}{]*var\([^!;}{]+)(!important)?(?=\s*([;}$]|$))/g 139 const regRuleIEGetters = /-ieVar-([^:]+):/g 140 const regRuleIESetters = /-ie-([^};]+)/g 141 const regHasVar = /var\(/ 142 const regPseudos = /:(hover|active|focus|target|:before|:after)/ 143 144 c1.onElement('link[rel="stylesheet"]', { 145 immediate: function(el) { 146 fetchCss(el.href, function(css) { 147 var newCss = rewriteCss(css) 148 if (css === newCss) return 149 newCss = relToAbs(el.href, newCss) 150 el.disabled = true 151 var style = document.createElement('style') 152 el.parentNode.insertBefore(style, el) 153 activateStyleElement(style, newCss) 154 }) 155 } 156 }) 157 c1.onElement('style', { 158 immediate: function(el) { 159 if (el.hasAttribute('ie-polyfilled')) return 160 if (el.ieCP_elementSheet) return 161 var css = el.innerHTML 162 var newCss = rewriteCss(css) 163 if (css === newCss) return 164 activateStyleElement(el, newCss) 165 } 166 }) 167 c1.onElement('[ie-style]', { 168 immediate: function(el) { 169 var newCss = rewriteCss('{' + el.getAttribute('ie-style')).substr(1) 170 el.style.cssText += ';' + newCss 171 var found = parseRewrittenStyle(el.style) 172 if (found.getters) addGetterElement(el, found.getters, '%styleAttr') 173 if (found.setters) addSetterElement(el, found.setters) 174 } 175 }) 176 177 function relToAbs(base, css) { 178 return css.replace(/url\(([^)]+)\)/g, function($0, $1) { 179 $1 = $1.trim().replace(/(^['"]|['"]$)/g, '') 180 if ($1.match(/^([a-z]+:|\/)/)) return $0 181 base = base.replace(/\?.*/, '') 182 return 'url(' + base + './../' + $1 + ')' 183 }) 184 } 185 186 // ie has a bug, where unknown properties at pseudo-selectors are computed at the element 187 // #el::after { -content:'x'; } => getComputedStyle(el)['-content'] == 'x' 188 // should we add something like -ieVar-pseudo_after-content:'x'? 189 function rewriteCss(css) { 190 /* uncomment if spec finished and needed by someone 191 css = css.replace(/@property ([^{]+){([^}]+)}/, function($0, prop, body){ 192 prop = prop.trim(); 193 const declaration = {name:prop}; 194 body.split(';').forEach(function(pair){ 195 const x = pair.split(':'); 196 if (x[1]) declaration[ x[0].trim() ] = x[1]; 197 }); 198 declaration['inherits'] = declaration['inherits'].trim()==='true' ? true : false; 199 declaration['initialValue'] = declaration['initial-value']; 200 CSS.registerProperty(declaration) 201 return '/*\n @property ... removed \n*'+'/'; 202 }); 203 */ 204 205 return css 206 .replace(regFindSetters, function($0, $1, $2, $3, important) { 207 return $1 + '-ie-' + (important ? '❗' : '') + $3 208 }) 209 .replace(regFindGetters, function($0, $1, $2, important) { 210 return $1 + '-ieVar-' + (important ? '❗' : '') + $2 + '; ' + $2 // keep the original, so chaining works "--x:var(--y)" 211 }) 212 } 213 214 // beta 215 const styles_of_getter_properties = {} 216 217 function parseRewrittenStyle(style) { 218 // less memory then parameter cssText? 219 220 // beta 221 style['z-index'] // ie11 can access unknown properties in stylesheets only if accessed a dashed known property 222 223 const cssText = style.cssText 224 var matchesGetters = cssText.match(regRuleIEGetters), 225 j, 226 match 227 if (matchesGetters) { 228 var getters = [] // eg. [border,color] 229 for (j = 0; (match = matchesGetters[j++]); ) { 230 let propName = match.slice(7, -1) 231 if (propName[0] === '❗') propName = propName.substr(1) 232 getters.push(propName) 233 234 // beta 235 if (!styles_of_getter_properties[propName]) 236 styles_of_getter_properties[propName] = [] 237 styles_of_getter_properties[propName].push(style) 238 } 239 } 240 var matchesSetters = cssText.match(regRuleIESetters) 241 if (matchesSetters) { 242 var setters = {} // eg. [--color:#fff, --padding:10px]; 243 for (j = 0; (match = matchesSetters[j++]); ) { 244 let x = match.substr(4).split(':') 245 let propName = x[0] 246 let propValue = x[1] 247 if (propName[0] === '❗') propName = propName.substr(1) 248 setters[propName] = propValue 249 } 250 } 251 return { getters: getters, setters: setters } 252 } 253 function activateStyleElement(style, css) { 254 style.innerHTML = css 255 style.setAttribute('ie-polyfilled', true) 256 var rules = style.sheet.rules, 257 i = 0, 258 rule // cssRules = CSSRuleList, rules = MSCSSRuleList 259 while ((rule = rules[i++])) { 260 const found = parseRewrittenStyle(rule.style) 261 if (found.getters) addGettersSelector(rule.selectorText, found.getters) 262 if (found.setters) addSettersSelector(rule.selectorText, found.setters) 263 264 // mediaQueries: redraw the hole document 265 // better add events for each element? 266 const media = 267 rule.parentRule && 268 rule.parentRule.media && 269 rule.parentRule.media.mediaText 270 if (media && (found.getters || found.setters)) { 271 matchMedia(media).addListener(function() { 272 drawTree(document.documentElement) 273 }) 274 } 275 } 276 277 // beta 278 redrawStyleSheets() 279 } 280 281 function addGettersSelector(selector, properties) { 282 selectorAddPseudoListeners(selector) 283 c1.onElement(unPseudo(selector), { 284 immediate: function(el) { 285 addGetterElement(el, properties, selector) 286 drawElement(el) 287 } 288 }) 289 } 290 function addGetterElement(el, properties, selector) { 291 var i = 0, 292 prop, 293 j 294 const selectors = selector.split(',') // split grouped selectors 295 el.setAttribute('iecp-needed', true) 296 if (!el.ieCPSelectors) el.ieCPSelectors = {} 297 while ((prop = properties[i++])) { 298 for (j = 0; (selector = selectors[j++]); ) { 299 const parts = selector.trim().split('::') 300 if (!el.ieCPSelectors[prop]) el.ieCPSelectors[prop] = [] 301 el.ieCPSelectors[prop].push({ 302 selector: parts[0], 303 pseudo: parts[1] ? '::' + parts[1] : '' 304 }) 305 } 306 } 307 } 308 function addSettersSelector(selector, propVals) { 309 selectorAddPseudoListeners(selector) 310 c1.onElement(unPseudo(selector), { 311 immediate: function(el) { 312 addSetterElement(el, propVals) 313 } 314 }) 315 } 316 function addSetterElement(el, propVals) { 317 if (!el.ieCP_setters) el.ieCP_setters = {} 318 for (var prop in propVals) { 319 // eg. {foo:#fff, bar:baz} 320 el.ieCP_setters['--' + prop] = 1 321 } 322 drawTree(el) 323 } 324 325 //beta 326 function redrawStyleSheets() { 327 for (var prop in styles_of_getter_properties) { 328 let styles = styles_of_getter_properties[prop] 329 for (var i = 0, style; (style = styles[i++]); ) { 330 if (style.owningElement) continue 331 var value = style['-ieVar-' + prop] 332 if (!value) continue 333 var value = styleComputeValueWidthVars( 334 getComputedStyle(document.documentElement), 335 value 336 ) 337 if (value === '') continue 338 style[prop] = value 339 } 340 } 341 } 342 343 const pseudos = { 344 hover: { 345 on: 'mouseenter', 346 off: 'mouseleave' 347 }, 348 focus: { 349 on: 'focusin', 350 off: 'focusout' 351 }, 352 active: { 353 on: 'CSSActivate', 354 off: 'CSSDeactivate' 355 } 356 } 357 function selectorAddPseudoListeners(selector) { 358 // ie11 has the strange behavoir, that groups of selectors are individual rules, but starting with the full selector: 359 // td, th, button { color:red } results in this rules: 360 // "td, th, button" | "th, th" | "th" 361 selector = selector.split(',')[0] 362 for (var pseudo in pseudos) { 363 var parts = selector.split(':' + pseudo) 364 if (parts.length > 1) { 365 var ending = parts[1].match(/^[^\s]*/) // ending elementpart of selector (used for not(:active)) 366 let sel = unPseudo(parts[0] + ending) 367 const listeners = pseudos[pseudo] 368 c1.onElement(sel, function(el) { 369 el.addEventListener(listeners.on, drawTreeEvent) 370 el.addEventListener(listeners.off, drawTreeEvent) 371 }) 372 } 373 } 374 } 375 let CSSActive = null 376 document.addEventListener('mousedown', function(e) { 377 setTimeout(function() { 378 if (e.target === document.activeElement) { 379 var evt = document.createEvent('Event') 380 evt.initEvent('CSSActivate', true, true) 381 CSSActive = e.target 382 CSSActive.dispatchEvent(evt) 383 } 384 }) 385 }) 386 document.addEventListener('mouseup', function() { 387 if (CSSActive) { 388 var evt = document.createEvent('Event') 389 evt.initEvent('CSSDeactivate', true, true) 390 CSSActive.dispatchEvent(evt) 391 CSSActive = null 392 } 393 }) 394 395 function unPseudo(selector) { 396 return selector.replace(regPseudos, '').replace(':not()', '') 397 } 398 399 var uniqueCounter = 0 400 401 function _drawElement(el) { 402 if (!el.ieCP_unique) { 403 // use el.uniqueNumber? but needs class for the css-selector => test performance 404 el.ieCP_unique = ++uniqueCounter 405 el.classList.add('iecp-u' + el.ieCP_unique) 406 } 407 var style = getComputedStyle(el) 408 if (el.ieCP_sheet) 409 while (el.ieCP_sheet.rules[0]) el.ieCP_sheet.deleteRule(0) 410 for (var prop in el.ieCPSelectors) { 411 var important = style['-ieVar-❗' + prop] 412 let valueWithVar = important || style['-ieVar-' + prop] 413 if (!valueWithVar) continue // todo, what if '0' 414 415 var details = {} 416 var value = styleComputeValueWidthVars(style, valueWithVar, details) 417 418 if (important) value += ' !important' 419 for (var i = 0, item; (item = el.ieCPSelectors[prop][i++]); ) { 420 // todo: split and use requestAnimationFrame? 421 if (item.selector === '%styleAttr') { 422 el.style[prop] = value 423 } else { 424 // beta 425 if (!important && details.allByRoot !== false) continue // dont have to draw root-properties 426 427 //let selector = item.selector.replace(/>? \.[^ ]+/, ' ', item.selector); // todo: try to equalize specificity 428 let selector = item.selector 429 elementStyleSheet(el).insertRule( 430 selector + 431 '.iecp-u' + 432 el.ieCP_unique + 433 item.pseudo + 434 ' {' + 435 prop + 436 ':' + 437 value + 438 '}', 439 0 440 ) // faster then innerHTML 441 } 442 } 443 } 444 } 445 function elementStyleSheet(el) { 446 if (!el.ieCP_sheet) { 447 var tag = document.createElement('style') 448 tag.ieCP_elementSheet = 1 449 //el.appendChild(tag); // yes! self-closing tags can have style as children, but - if i set innerHTML, the stylesheet is lost 450 document.head.appendChild(tag) 451 el.ieCP_sheet = tag.sheet 452 } 453 return el.ieCP_sheet 454 } 455 function drawTree(target) { 456 if (!target) return 457 var els = target.querySelectorAll('[iecp-needed]') 458 if (target.hasAttribute && target.hasAttribute('iecp-needed')) 459 drawElement(target) // self 460 for (var i = 0, el; (el = els[i++]); ) drawElement(el) // tree 461 } 462 // draw queue 463 let drawQueue = new Set() 464 let collecting = false 465 let drawing = false 466 function drawElement(el) { 467 drawQueue.add(el) 468 if (collecting) return 469 collecting = true 470 requestAnimationFrame(function() { 471 collecting = false 472 drawing = true 473 drawQueue.forEach(_drawElement) 474 requestAnimationFrame(function() { 475 // mutationObserver will trigger delayed 476 drawing = false 477 }) 478 drawQueue.clear() 479 }) 480 } 481 482 function drawTreeEvent(e) { 483 drawTree(e.target) 484 } 485 486 const regValueGetters = /var\(([^),]+)(\,(.+))?\)/g 487 function styleComputeValueWidthVars(style, valueWithVar, details) { 488 return valueWithVar.replace(regValueGetters, function( 489 full, 490 variable, 491 x, 492 fallback 493 ) { 494 variable = variable.trim() 495 var pValue = style.getPropertyValue(variable) 496 if (details && style.lastPropertyServedBy !== document.documentElement) 497 details.allByRoot = false 498 if (pValue === '' && fallback !== undefined) pValue = fallback.trim() // fallback 499 return pValue 500 }) 501 } 502 503 // mutation listener 504 var observer = new MutationObserver(function(mutations) { 505 if (drawing) return 506 for (var i = 0, mutation; (mutation = mutations[i++]); ) { 507 if (mutation.attributeName === 'ie-polyfilled') continue 508 if (mutation.attributeName === 'iecp-needed') continue 509 // recheck all selectors if it targets new elements? 510 drawTree(mutation.target) 511 } 512 }) 513 setTimeout(function() { 514 observer.observe(document, { attributes: true, subtree: true }) 515 }) 516 517 // :target listener 518 var oldHash = location.hash 519 addEventListener('hashchange', function(e) { 520 var newEl = document.getElementById(location.hash.substr(1)) 521 if (newEl) { 522 var oldEl = document.getElementById(oldHash.substr(1)) 523 drawTree(newEl) 524 drawTree(oldEl) 525 } else { 526 drawTree(document) 527 } 528 oldHash = location.hash 529 }) 530 531 // add owningElement to Element.style 532 var descriptor = Object.getOwnPropertyDescriptor( 533 HTMLElement.prototype, 534 'style' 535 ) 536 var styleGetter = descriptor.get 537 descriptor.get = function() { 538 const style = styleGetter.call(this) 539 style.owningElement = this 540 return style 541 } 542 Object.defineProperty(HTMLElement.prototype, 'style', descriptor) 543 544 // add computedFor to computed style-objects 545 var originalGetComputed = getComputedStyle 546 window.getComputedStyle = function(el) { 547 var style = originalGetComputed.apply(this, arguments) 548 style.computedFor = el 549 //style.pseudoElt = pseudoElt; //not needed at the moment 550 return style 551 } 552 553 // getPropertyValue / setProperty hooks 554 const StyleProto = CSSStyleDeclaration.prototype 555 556 const oldGetP = StyleProto.getPropertyValue 557 StyleProto.getPropertyValue = function(property) { 558 this.lastPropertyServedBy = false 559 if (property[0] !== '-' || property[1] !== '-') 560 return oldGetP.apply(this, arguments) 561 const undashed = property.substr(2) 562 const ieProperty = '-ie-' + undashed 563 const iePropertyImportant = '-ie-❗' + undashed 564 let value = this[iePropertyImportant] || this[ieProperty] 565 if (this.computedFor) { 566 // computedStyle 567 if (value !== undefined) { 568 if (regHasVar.test(value)) { 569 value = styleComputeValueWidthVars(this, value) 570 } 571 this.lastPropertyServedBy = this.computedFor 572 } else { 573 if (!register[property] || register[property].inherits) { 574 // inherited 575 //let el = this.pseudoElt ? this.computedFor : this.computedFor.parentNode; 576 let el = this.computedFor.parentNode 577 while (el.nodeType === 1) { 578 // how slower would it be to getComputedStyle for every element, not just with defined ieCP_setters 579 if (el.ieCP_setters && el.ieCP_setters[property]) { 580 // i could make 581 // value = el.nodeType ? getComputedStyle(this.computedFor.parentNode).getPropertyValue(property) 582 // but i fear performance, stupid? 583 var style = getComputedStyle(el) 584 var tmpVal = style[iePropertyImportant] || style[ieProperty] 585 if (tmpVal !== undefined) { 586 value = tmpVal 587 if (regHasVar.test(value)) { 588 // calculated style from current element not from the element the value was inherited from! (style, value) 589 value = styleComputeValueWidthVars(this, value) 590 } 591 this.lastPropertyServedBy = el 592 break 593 } 594 } 595 el = el.parentNode 596 } 597 } 598 } 599 } 600 if (value === undefined && register[property]) 601 value = register[property].initialValue 602 if (value === undefined) value = '' 603 return value 604 } 605 606 const oldSetP = StyleProto.setProperty 607 StyleProto.setProperty = function(property, value, prio) { 608 if (property[0] !== '-' || property[1] !== '-') 609 return oldSetP.apply(this, arguments) 610 if (this.owningElement) { 611 const el = this.owningElement 612 if (!el.ieCP_setters) el.ieCP_setters = {} 613 el.ieCP_setters[property] = 1 614 drawTree(el) 615 } 616 property = '-ie-' + (prio === 'important' ? '❗' : '') + property.substr(2) 617 this.cssText += '; ' + property + ':' + value + ';' 618 //this[property] = value; 619 } 620 621 if (!window.CSS) window.CSS = {} 622 const register = {} 623 CSS.registerProperty = function(options) { 624 register[options.name] = options 625 } 626 627 // utils 628 function fetchCss(url, callback) { 629 var request = new XMLHttpRequest() 630 request.open('GET', url) 631 request.overrideMimeType('text/css') 632 request.onload = function() { 633 if (request.status >= 200 && request.status < 400) { 634 callback(request.responseText) 635 } 636 } 637 request.send() 638 } 639 })()