infamous
Version:
A CSS3D/WebGL UI library.
135 lines (111 loc) • 5.22 kB
JavaScript
function epsilon(value) {
return Math.abs(value) < 0.000001 ? 0 : value;
}
function applyCSSLabel(value, label) {
if (value === 0) {
return '0px'
} else if (label === '%') {
return value * 100 + '%';
} else if (label === 'px') {
return value + 'px'
}
}
function observeChildren(target, onConnect, onDisconnect) {
// TODO this Map is never cleaned, leaks memory. Maybe use WeakMap
const childObserver = createChildObserver(onConnect, onDisconnect)
childObserver.observe(target, { childList: true })
return childObserver
}
// NOTE: If a child is disconnected then connected to the same parent in the
// same turn, then the onConnect and onDisconnect callbacks won't be called
// because the DOM tree will be back in the exact state as before (this is
// possible thanks to the logic associated with weightsPerTarget).
function createChildObserver(onConnect, onDisconnect) {
return new MutationObserver(changes => {
const weightsPerTarget = new Map
// We're just counting how many times each child node was added and
// removed from the parent we're observing.
for (let i=0, l=changes.length; i<l; i+=1) {
const change = changes[i]
if (change.type != 'childList') continue
if (!weightsPerTarget.has(change.target))
weightsPerTarget.set(change.target, new Map)
const weights = weightsPerTarget.get(change.target)
const {addedNodes} = change
for (let l=addedNodes.length, i=0; i<l; i+=1)
weights.set(addedNodes[i], (weights.get(addedNodes[i]) || 0) + 1)
const {removedNodes} = change
for (let l=removedNodes.length, i=0; i<l; i+=1)
weights.set(removedNodes[i], (weights.get(removedNodes[i]) || 0) - 1)
}
for (const [target, weights] of Array.from(weightsPerTarget)) {
for (const [node, weight] of Array.from(weights)) {
// If the number of times a child was added is greater than the
// number of times it was removed, then the net result is that
// it was added, so we call onConnect just once.
if (weight > 0 && typeof onConnect == 'function')
onConnect.call(target, node)
// If the number of times a child was added is less than the
// number of times it was removed, then the net result is that
// it was removed, so we call onConnect just once.
else if (weight < 0 && typeof onDisconnect == 'function')
onDisconnect.call(target, node)
// If the number of times a child was added is equal to the
// number of times it was removed, then it was essentially left
// in place, so we don't call anything.
}
}
})
}
const hasShadowDomV0 =
typeof Element.prototype.createShadowRoot == 'function'
&& typeof HTMLContentElement == 'function'
? true : false
const hasShadowDomV1 =
typeof Element.prototype.attachShadow == 'function'
&& typeof HTMLSlotElement == 'function'
? true : false
function getShadowRootVersion(shadowRoot) {
console.log('getShadowRootVersion')
if (!shadowRoot) return null
const slot = document.createElement('slot')
shadowRoot.appendChild(slot)
slot.appendChild(document.createElement('div'))
const assignedNodes = slot.assignedNodes({ flatten: true })
slot.remove()
console.log('hmm', assignedNodes.length, assignedNodes.length > 0 ? 'v1' : 'v0')
return assignedNodes.length > 0 ? 'v1' : 'v0'
}
function getAncestorShadowRoot(node) {
let current = node
while (current && !(current instanceof ShadowRoot)) {
current = current.parentNode
}
return current
}
// helper function to use instead of instanceof for classes that implement the
// static Symbol.hasInstance method, because the behavior of instanceof isn't
// polyfillable.
function isInstanceof(lhs, rhs) {
if (typeof rhs == 'function' && rhs[Symbol.hasInstance])
return rhs[Symbol.hasInstance](lhs)
else return lhs instanceof rhs
}
function checkIsNumberArrayString(str) {
if (!str.match(/^\s*(((\s*(-|\+)?((\.\d+)|(\d+\.\d+)|(\d+)|(\d+(\.\d+)?e(-|\+)?(\d+)))\s*,){0,2}(\s*(-|\+)?((\.\d+)|(\d+\.\d+)|(\d+)|(\d+(\.\d+)?e(-|\+)?(\d+)))))|((\s*(-|\+)?((\.\d+)|(\d+\.\d+)|(\d+)|(\d+(\.\d+)?e(-|\+)?(\d+)))\s){0,2}(\s*(-|\+)?((\.\d+)|(\d+\.\d+)|(\d+)|(\d+(\.\d+)?e(-|\+)?(\d+))))))\s*$/g))
throw new Error(`Attribute must be a comma- or space-separated sequence of up to three numbers, for example "1 2.5 3". Yours was "${str}".`)
}
function checkIsSizeArrayString(str) {
if (!str.match(/^\s*(((\s*([a-zA-Z]+)\s*,){0,2}(\s*([a-zA-Z]+)))|((\s*([a-zA-Z]+)\s*){1,3}))\s*$/g))
throw new Error(`Attribute must be a comma- or space-separated sequence of up to three strings, for example "literal proportional". Yours was "${str}".`)
}
export {
epsilon,
applyCSSLabel,
observeChildren,
getShadowRootVersion,
hasShadowDomV0,
hasShadowDomV1,
getAncestorShadowRoot,
isInstanceof,
}