You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/website/public/ie-custom-properties.js

770 lines
25 KiB

/*! ie11CustomProperties.js v3.1.0 | MIT License | https://git.io/fjXMN */
!(function () {
'use strict'
// check for support
var testEl = document.createElement('i')
testEl.style.setProperty('--x', 'y')
if (testEl.style.getPropertyValue('--x') === 'y' || !testEl.msMatchesSelector)
return
if (!Element.prototype.matches)
Element.prototype.matches = Element.prototype.msMatchesSelector
var listeners = [],
root = document,
Observer
function qsa(el, selector) {
try {
return el.querySelectorAll(selector)
} catch (e) {
// console.warn('the Selector '+selector+' can not be parsed');
return []
}
}
function onElement(selector, callback) {
var listener = {
selector: selector,
callback: callback,
elements: new WeakMap(),
}
var els = qsa(root, listener.selector),
i = 0,
el
while ((el = els[i++])) {
listener.elements.set(el, true)
listener.callback.call(el, el)
}
listeners.push(listener)
if (!Observer) {
Observer = new MutationObserver(checkMutations)
Observer.observe(root, {
childList: true,
subtree: true,
})
}
checkListener(listener)
}
function checkListener(listener, target) {
var i = 0,
el,
els = []
try {
target && target.matches(listener.selector) && els.push(target)
} catch (e) {}
if (loaded) {
// ok? check inside node on innerHTML - only when loaded
Array.prototype.push.apply(els, qsa(target || root, listener.selector))
}
while ((el = els[i++])) {
if (listener.elements.has(el)) continue
listener.elements.set(el, true)
listener.callback.call(el, el)
}
}
function checkListeners(inside) {
var i = 0,
listener
while ((listener = listeners[i++])) checkListener(listener, inside)
}
function checkMutations(mutations) {
var j = 0,
i,
mutation,
nodes,
target
while ((mutation = mutations[j++])) {
;(nodes = mutation.addedNodes), (i = 0)
while ((target = nodes[i++]))
target.nodeType === 1 && checkListeners(target)
}
}
var loaded = false
document.addEventListener('DOMContentLoaded', function () {
loaded = true
})
// svg polyfills
function copyProperty(prop, from, to) {
var desc = Object.getOwnPropertyDescriptor(from, prop)
Object.defineProperty(to, prop, desc)
}
if (!('classList' in Element.prototype)) {
copyProperty('classList', HTMLElement.prototype, Element.prototype)
}
if (!('innerHTML' in Element.prototype)) {
copyProperty('innerHTML', HTMLElement.prototype, Element.prototype)
}
if (!('sheet' in SVGStyleElement.prototype)) {
Object.defineProperty(SVGStyleElement.prototype, 'sheet', {
get: function () {
var all = document.styleSheets,
i = 0,
sheet
while ((sheet = all[i++])) {
if (sheet.ownerNode === this) return sheet
}
},
})
}
// main logic
// cached regexps, better performance
const regFindSetters = /([\s{;])(--([A-Za-z0-9-_]*)\s*:([^;!}{]+)(!important)?)(?=\s*([;}]|$))/g
const regFindGetters = /([{;]\s*)([A-Za-z0-9-_]+\s*:[^;}{]*var\([^!;}{]+)(!important)?(?=\s*([;}$]|$))/g
const regRuleIEGetters = /-ieVar-([^:]+):/g
const regRuleIESetters = /-ie-([^};]+)/g
//const regHasVar = /var\(/;
const regPseudos = /:(hover|active|focus|target|visited|link|:before|:after|:first-letter|:first-line)/
onElement('link[rel="stylesheet"]', function (el) {
fetchCss(el.href, function (css) {
var newCss = rewriteCss(css)
if (css === newCss) return
newCss = relToAbs(el.href, newCss)
el.disabled = true
var style = document.createElement('style')
if (el.media) style.setAttribute('media', el.media)
el.parentNode.insertBefore(style, el)
activateStyleElement(style, newCss)
})
})
function foundStyle(el) {
if (el.ieCP_polyfilled) return
if (el.ieCP_elementSheet) return
var css = el.innerHTML
var newCss = rewriteCss(css)
if (css === newCss) return
activateStyleElement(el, newCss)
}
onElement('style', foundStyle)
// immediate, to pass w3c-tests, bud its a bad idea
// addEventListener('DOMNodeInserted',function(e){ e.target.tagName === 'STYLE' && foundStyle(e.target); });
onElement('[ie-style]', function (el) {
var newCss = rewriteCss('{' + el.getAttribute('ie-style')).substr(1)
el.style.cssText += ';' + newCss
var found = parseRewrittenStyle(el.style)
if (found.getters) addGetterElement(el, found.getters, '%styleAttr')
if (found.setters) addSetterElement(el, found.setters)
})
function relToAbs(base, css) {
return css.replace(/url\(([^)]+)\)/g, function ($0, $1) {
$1 = $1.trim().replace(/(^['"]|['"]$)/g, '')
if ($1.match(/^([a-z]+:|\/)/)) return $0
base = base.replace(/\?.*/, '')
return 'url(' + base + './../' + $1 + ')'
})
}
// ie has a bug, where unknown properties at pseudo-selectors are computed at the element
// #el::after { -content:'x'; } => getComputedStyle(el)['-content'] == 'x'
// should we add something like -ieVar-pseudo_after-content:'x'?
function rewriteCss(css) {
/* uncomment if spec finished and needed by someone
css = css.replace(/@property ([^{]+){([^}]+)}/, function($0, prop, body){
prop = prop.trim();
const declaration = {name:prop};
body.split(';').forEach(function(pair){
const x = pair.split(':');
if (x[1]) declaration[ x[0].trim() ] = x[1];
});
declaration['inherits'] = declaration['inherits'].trim()==='true' ? true : false;
declaration['initialValue'] = declaration['initial-value'];
CSS.registerProperty(declaration)
return '/*\n @property ... removed \n*'+'/';
});
*/
return css
.replace(regFindSetters, function ($0, $1, $2, $3, $4, important) {
return (
$1 + '-ie-' + (important ? '❗' : '') + $3 + ':' + encodeValue($4)
)
})
.replace(regFindGetters, function ($0, $1, $2, important) {
return $1 + '-ieVar-' + (important ? '❗' : '') + $2 + '; ' + $2 // keep the original, so chaining works "--x:var(--y)"
})
}
function encodeValue(value) {
return value
return value.replace(/ /g, '␣')
}
const keywords = { initial: 1, inherit: 1, revert: 1, unset: 1 }
function decodeValue(value) {
return value
if (value === undefined) return
value = value.replace(/␣/g, ' ')
const trimmed = value.trim()
if (keywords[trimmed]) return trimmed
return value
}
// beta
const styles_of_getter_properties = {}
function parseRewrittenStyle(style) {
// less memory then parameter cssText?
// beta
style['z-index'] // ie11 can access unknown properties in stylesheets only if accessed a dashed known property
const cssText = style.cssText
var matchesGetters = cssText.match(regRuleIEGetters),
j,
match
if (matchesGetters) {
var getters = [] // eg. [border,color]
for (j = 0; (match = matchesGetters[j++]); ) {
let propName = match.slice(7, -1)
if (propName[0] === '❗') propName = propName.substr(1)
getters.push(propName)
// beta
if (!styles_of_getter_properties[propName])
styles_of_getter_properties[propName] = []
styles_of_getter_properties[propName].push(style)
}
}
var matchesSetters = cssText.match(regRuleIESetters)
if (matchesSetters) {
var setters = {} // eg. [--color:#fff, --padding:10px];
for (j = 0; (match = matchesSetters[j++]); ) {
let x = match.substr(4).split(':')
let propName = x[0]
let propValue = x[1]
if (propName[0] === '❗') propName = propName.substr(1)
setters[propName] = propValue
}
}
return { getters: getters, setters: setters }
}
function activateStyleElement(style, css) {
style.innerHTML = css
style.ieCP_polyfilled = true
var rules = style.sheet.rules,
i = 0,
rule // cssRules = CSSRuleList, rules = MSCSSRuleList
while ((rule = rules[i++])) {
const found = parseRewrittenStyle(rule.style)
if (found.getters) addGettersSelector(rule.selectorText, found.getters)
if (found.setters) addSettersSelector(rule.selectorText, found.setters)
// mediaQueries: redraw the hole document
// better add events for each element?
const media =
rule.parentRule &&
rule.parentRule.media &&
rule.parentRule.media.mediaText
if (media && (found.getters || found.setters)) {
matchMedia(media).addListener(function () {
drawTree(document.documentElement)
})
}
}
// beta
redrawStyleSheets()
}
function addGettersSelector(selector, properties) {
selectorAddPseudoListeners(selector)
onElement(unPseudo(selector), function (el) {
addGetterElement(el, properties, selector)
drawElement(el)
})
}
function addGetterElement(el, properties, selector) {
var i = 0,
prop,
j
const selectors = selector.split(',') // split grouped selectors
el.setAttribute('iecp-needed', true)
if (!el.ieCPSelectors) el.ieCPSelectors = {}
while ((prop = properties[i++])) {
for (j = 0; (selector = selectors[j++]); ) {
const parts = selector.trim().split('::')
if (!el.ieCPSelectors[prop]) el.ieCPSelectors[prop] = []
el.ieCPSelectors[prop].push({
selector: parts[0],
pseudo: parts[1] ? '::' + parts[1] : '',
})
}
}
}
function addSettersSelector(selector, propVals) {
selectorAddPseudoListeners(selector)
onElement(unPseudo(selector), function (el) {
addSetterElement(el, propVals)
})
}
function addSetterElement(el, propVals) {
if (!el.ieCP_setters) el.ieCP_setters = {}
for (var prop in propVals) {
// eg. {foo:#fff, bar:baz}
el.ieCP_setters['--' + prop] = 1
}
drawTree(el)
}
//beta
function redrawStyleSheets() {
for (var prop in styles_of_getter_properties) {
let styles = styles_of_getter_properties[prop]
for (var i = 0, style; (style = styles[i++]); ) {
if (style.owningElement) continue
var value = style['-ieVar-' + prop]
if (!value) continue
value = styleComputeValueWidthVars(
getComputedStyle(document.documentElement),
value
)
if (value === '') continue
try {
style[prop] = value
} catch (e) {}
}
}
}
const pseudos = {
hover: {
on: 'mouseenter',
off: 'mouseleave',
},
focus: {
on: 'focusin',
off: 'focusout',
},
active: {
on: 'CSSActivate',
off: 'CSSDeactivate',
},
}
function selectorAddPseudoListeners(selector) {
// ie11 has the strange behavoir, that groups of selectors are individual rules, but starting with the full selector:
// td, th, button { color:red } results in this rules:
// "td, th, button" | "th, th" | "th"
selector = selector.split(',')[0]
for (var pseudo in pseudos) {
var parts = selector.split(':' + pseudo)
if (parts.length > 1) {
var ending = parts[1].match(/^[^\s]*/) // ending elementpart of selector (used for not(:active))
let sel = unPseudo(parts[0] + ending)
const listeners = pseudos[pseudo]
onElement(sel, function (el) {
el.addEventListener(listeners.on, drawTreeEvent)
el.addEventListener(listeners.off, drawTreeEvent)
})
}
}
}
let CSSActive = null
document.addEventListener('mousedown', function (e) {
setTimeout(function () {
if (e.target === document.activeElement) {
var evt = document.createEvent('Event')
evt.initEvent('CSSActivate', true, true)
CSSActive = e.target
CSSActive.dispatchEvent(evt)
}
})
})
document.addEventListener('mouseup', function () {
if (CSSActive) {
var evt = document.createEvent('Event')
evt.initEvent('CSSDeactivate', true, true)
CSSActive.dispatchEvent(evt)
CSSActive = null
}
})
function unPseudo(selector) {
return selector.replace(regPseudos, '').replace(':not()', '')
}
var uniqueCounter = 0
/* old *
function _drawElement(el) {
if (!el.ieCP_unique) { // use el.uniqueNumber? but needs class for the css-selector => test performance
el.ieCP_unique = ++uniqueCounter;
el.classList.add('iecp-u' + el.ieCP_unique);
}
var style = getComputedStyle(el);
if (el.ieCP_sheet) while (el.ieCP_sheet.rules[0]) el.ieCP_sheet.deleteRule(0);
for (var prop in el.ieCPSelectors) {
var important = style['-ieVar-❗' + prop];
let valueWithVar = important || style['-ieVar-' + prop];
if (!valueWithVar) continue; // todo, what if '0'
var details = {};
var value = styleComputeValueWidthVars(style, valueWithVar, details);
if (important) value += ' !important';
for (var i=0, item; item=el.ieCPSelectors[prop][i++];) { // todo: split and use requestAnimationFrame?
if (item.selector === '%styleAttr') {
el.style[prop] = value;
} else {
// beta
if (!important && details.allByRoot !== false) continue; // dont have to draw root-properties
//let selector = item.selector.replace(/>? \.[^ ]+/, ' ', item.selector); // todo: try to equalize specificity
let selector = item.selector;
elementStyleSheet(el).insertRule(selector + '.iecp-u' + el.ieCP_unique + item.pseudo + ' {' + prop + ':' + value + '}', 0);
}
}
}
}
function elementStyleSheet(el){
if (!el.ieCP_sheet) {
const styleEl = document.createElement('style');
styleEl.ieCP_elementSheet = 1;
//el.appendChild(styleEl); // yes! self-closing tags can have style as children, but - if i set innerHTML, the stylesheet is lost
document.head.appendChild(styleEl);
el.ieCP_sheet = styleEl.sheet;
}
return el.ieCP_sheet;
}
/* */
function _drawElement(el) {
if (!el.ieCP_unique) {
// use el.uniqueNumber? but needs class for the css-selector => test performance
el.ieCP_unique = ++uniqueCounter
el.classList.add('iecp-u' + el.ieCP_unique)
}
var style = getComputedStyle(el)
let css = ''
for (var prop in el.ieCPSelectors) {
var important = style['-ieVar-❗' + prop]
let valueWithVar = important || style['-ieVar-' + prop]
if (!valueWithVar) continue // todo, what if '0'
var details = {}
var value = styleComputeValueWidthVars(style, valueWithVar, details)
//if (value==='initial') value = initials[prop];
if (important) value += ' !important'
for (var i = 0, item; (item = el.ieCPSelectors[prop][i++]); ) {
// todo: split and use requestAnimationFrame?
if (item.selector === '%styleAttr') {
el.style[prop] = value
} else {
// beta
if (!important && details.allByRoot !== false) continue // dont have to draw root-properties
//let selector = item.selector.replace(/>? \.[^ ]+/, ' ', item.selector); // todo: try to equalize specificity
let selector = item.selector
css +=
selector +
'.iecp-u' +
el.ieCP_unique +
item.pseudo +
'{' +
prop +
':' +
value +
'}\n'
// el.runtimeStyle[prop] = value; // todo
}
}
}
elementSetCss(el, css)
}
function elementSetCss(el, css) {
if (!el.ieCP_styleEl && css) {
const styleEl = document.createElement('style')
styleEl.ieCP_elementSheet = 1
//el.appendChild(styleEl); // yes! self-closing tags can have style as children, but - if i set innerHTML, the stylesheet is lost
document.head.appendChild(styleEl)
el.ieCP_styleEl = styleEl
}
if (el.ieCP_styleEl) el.ieCP_styleEl.innerHTML = css
}
/* */
function drawTree(target) {
if (!target) return
var els = target.querySelectorAll('[iecp-needed]')
if (target.hasAttribute && target.hasAttribute('iecp-needed'))
drawElement(target) // self
for (var i = 0, el; (el = els[i++]); ) drawElement(el) // tree
}
// draw queue
let drawQueue = new Set()
let collecting = false
let drawing = false
function drawElement(el) {
drawQueue.add(el)
if (collecting) return
collecting = true
requestAnimationFrame(function () {
//setImmediate(function(){
collecting = false
drawing = true
drawQueue.forEach(_drawElement)
drawQueue.clear()
setTimeout(function () {
// mutationObserver will trigger delayed, requestAnimationFrame will miss some changes
drawing = false
})
})
}
function drawTreeEvent(e) {
drawTree(e.target)
}
function findVars(str, cb) {
// css value parser
let level = 0,
openedLevel = null,
lastPoint = 0,
newStr = '',
i = 0,
char,
insideCalc
while ((char = str[i++])) {
if (char === '(') {
++level
if (
openedLevel === null &&
str[i - 4] + str[i - 3] + str[i - 2] === 'var'
) {
openedLevel = level
newStr += str.substring(lastPoint, i - 4)
lastPoint = i
}
if (str[i - 5] + str[i - 4] + str[i - 3] + str[i - 2] === 'calc') {
insideCalc = level
}
}
if (char === ')' && openedLevel === level) {
let variable = str.substring(lastPoint, i - 1).trim(),
fallback
let x = variable.indexOf(',')
if (x !== -1) {
fallback = variable.slice(x + 1)
variable = variable.slice(0, x)
}
newStr += cb(variable, fallback, insideCalc)
lastPoint = i
openedLevel = null
}
if (char === ')') {
--level
if (insideCalc === level) insideCalc = null
}
}
newStr += str.substring(lastPoint)
return newStr
}
function styleComputeValueWidthVars(style, valueWithVars, details) {
return findVars(valueWithVars, function (variable, fallback, insideCalc) {
var value = style.getPropertyValue(variable)
if (insideCalc) value = value.replace(/^calc\(/, '(') // prevent nested calc
if (details && style.lastPropertyServedBy !== document.documentElement)
details.allByRoot = false
if (value === '' && fallback)
value = styleComputeValueWidthVars(style, fallback, details)
return value
})
}
// mutation listener
var observer = new MutationObserver(function (mutations) {
if (drawing) return
for (var i = 0, mutation; (mutation = mutations[i++]); ) {
if (mutation.attributeName === 'iecp-needed') continue // why?
// recheck all selectors if it targets new elements?
drawTree(mutation.target)
}
})
setTimeout(function () {
observer.observe(document, { attributes: true, subtree: true })
})
// :target listener
var oldHash = location.hash
addEventListener('hashchange', function (e) {
var newEl = document.getElementById(location.hash.substr(1))
if (newEl) {
var oldEl = document.getElementById(oldHash.substr(1))
drawTree(newEl)
drawTree(oldEl)
} else {
drawTree(document)
}
oldHash = location.hash
})
// add owningElement to Element.style
var descriptor = Object.getOwnPropertyDescriptor(
HTMLElement.prototype,
'style'
)
var styleGetter = descriptor.get
descriptor.get = function () {
const style = styleGetter.call(this)
style.owningElement = this
return style
}
Object.defineProperty(HTMLElement.prototype, 'style', descriptor)
// add computedFor to computed style-objects
var originalGetComputed = getComputedStyle
window.getComputedStyle = function (el) {
var style = originalGetComputed.apply(this, arguments)
style.computedFor = el
//style.pseudoElt = pseudoElt; //not needed at the moment
return style
}
// getPropertyValue / setProperty hooks
const StyleProto = CSSStyleDeclaration.prototype
const oldGetP = StyleProto.getPropertyValue
StyleProto.getPropertyValue = function (property) {
this.lastPropertyServedBy = false
property = property.trim()
/* *
if (this.owningElement) {
const ieProperty = '-ieVar-'+property;
const iePropertyImportant = '-ieVar-❗'+property;
let value = this[iePropertyImportant] || this[ieProperty];
if (value !== undefined) {
// todo, test if syntax valid
return value;
}
}
/* */
if (property[0] !== '-' || property[1] !== '-')
return oldGetP.apply(this, arguments)
const undashed = property.substr(2)
const ieProperty = '-ie-' + undashed
const iePropertyImportant = '-ie-❗' + undashed
let value = decodeValue(this[iePropertyImportant] || this[ieProperty])
if (this.computedFor) {
// computedStyle
if (value !== undefined && !inheritingKeywords[value]) {
//if (regHasVar.test(value)) // todo: to i need this check?!!! i think its faster without
value = styleComputeValueWidthVars(this, value)
this.lastPropertyServedBy = this.computedFor
} else {
// inherited
if (
inheritingKeywords[value] ||
!register[property] ||
register[property].inherits
) {
//let el = this.pseudoElt ? this.computedFor : this.computedFor.parentNode;
let el = this.computedFor.parentNode
while (el.nodeType === 1) {
// how slower would it be to getComputedStyle for every element, not just with defined ieCP_setters
if (el.ieCP_setters && el.ieCP_setters[property]) {
// i could make
// value = el.nodeType ? getComputedStyle(this.computedFor.parentNode).getPropertyValue(property)
// but i fear performance, stupid?
var style = getComputedStyle(el)
var tmpVal = decodeValue(
style[iePropertyImportant] || style[ieProperty]
)
if (tmpVal !== undefined) {
// calculated style from current element not from the element the value was inherited from! (style, value)
//value = tmpVal; if (regHasVar.test(tmpVal)) // todo: to i need this check?!!! i think its faster without
value = styleComputeValueWidthVars(this, tmpVal)
this.lastPropertyServedBy = el
break
}
}
el = el.parentNode
}
}
}
if (value === 'initial') return ''
}
//if ((value === undefined || value === 'initial') && register[property]) value = register[property].initialValue; // todo?
if (value === undefined && register[property])
value = register[property].initialValue
if (value === undefined) return ''
return value
}
const inheritingKeywords = { inherit: 1, revert: 1, unset: 1 }
const oldSetP = StyleProto.setProperty
StyleProto.setProperty = function (property, value, prio) {
if (property[0] !== '-' || property[1] !== '-')
return oldSetP.apply(this, arguments)
const el = this.owningElement
if (el) {
if (!el.ieCP_setters) el.ieCP_setters = {}
el.ieCP_setters[property] = 1
}
property = '-ie-' + (prio === 'important' ? '❗' : '') + property.substr(2)
this.cssText += '; ' + property + ':' + encodeValue(value) + ';'
//this[property] = value;
el === document.documentElement && redrawStyleSheets()
el && drawTree(el) // its delayed internal
}
/*
var descriptor = Object.getOwnPropertyDescriptor(StyleProto, 'cssText');
var cssTextGetter = descriptor.get;
var cssTextSetter = descriptor.set;
// descriptor.get = function () {
// const style = styleGetter.call(this);
// style.owningElement = this;
// return style;
// }
descriptor.set = function (css) {
var el = this.owningElement;
if (el) {
css = rewriteCss('{'+css).substr(1);
cssTextSetter.call(this, css);
var found = parseRewrittenStyle(this);
if (found.getters) addGetterElement(el, found.getters, '%styleAttr');
if (found.setters) addSetterElement(el, found.setters);
return;
}
return cssTextSetter.call(this, css);
}
Object.defineProperty(StyleProto, 'cssText', descriptor);
*/
if (!window.CSS) window.CSS = {}
const register = {}
CSS.registerProperty = function (options) {
register[options.name] = options
}
// fix "initial" keyword with generated custom properties, this is not supported ad all by ie, should i make a separate "inherit"-polyfill?
/*
const computed = getComputedStyle(document.documentElement)
const initials = {};
for (let i in computed) {
initials[i.replace(/([A-Z])/, function(x){ return '-'+x.toLowerCase(x) })] = computed[i];
}
initials['display'] = 'inline';
*/
// utils
function fetchCss(url, callback) {
var request = new XMLHttpRequest()
request.open('GET', url)
request.overrideMimeType('text/css')
request.onload = function () {
if (request.status >= 200 && request.status < 400) {
callback(request.responseText)
}
}
request.send()
}
})()