stylus/dom.js

126 lines
3.7 KiB
JavaScript
Raw Normal View History

2017-03-25 05:54:58 +00:00
'use strict';
2017-04-21 17:35:22 +00:00
if (!navigator.userAgent.includes('Windows')) {
document.documentElement.classList.add('non-windows');
}
2017-04-08 10:19:44 +00:00
// polyfill for old browsers to enable [...results] and for-of
for (const type of [NodeList, NamedNodeMap, HTMLCollection, HTMLAllCollection]) {
if (!type.prototype[Symbol.iterator]) {
type.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
}
}
2017-05-16 22:09:31 +00:00
// add favicon in Firefox
if (/Firefox/.test(navigator.userAgent)) {
for (const size of [128, 38, 32, 19, 16]) {
document.head.appendChild($element({
tag: 'link',
rel: 'icon',
href: `/images/icon/${size}.png`,
sizes: size + 'x' + size,
}));
}
}
2017-03-25 05:54:58 +00:00
function onDOMready() {
if (document.readyState != 'loading') {
return Promise.resolve();
}
return new Promise(resolve => {
document.addEventListener('DOMContentLoaded', function _() {
document.removeEventListener('DOMContentLoaded', _);
resolve();
});
});
}
function scrollElementIntoView(element) {
// align to the top/bottom of the visible area if wasn't visible
const bounds = element.getBoundingClientRect();
if (bounds.top < 0 || bounds.top > innerHeight - bounds.height) {
element.scrollIntoView(bounds.top < 0);
}
}
function animateElement(element, {className, removeExtraClasses = [], remove = false}) {
2017-03-25 05:54:58 +00:00
return new Promise(resolve => {
element.addEventListener('animationend', function _() {
element.removeEventListener('animationend', _);
element.classList.remove(
className,
...removeExtraClasses // In Firefox, `resolve()` might be called one frame later. This is helpful to clean-up on the same frame
);
2017-03-25 05:54:58 +00:00
// TODO: investigate why animation restarts if the elements is removed in .then()
if (remove) {
element.remove();
}
resolve();
});
element.classList.add(className);
});
}
function enforceInputRange(element) {
const min = Number(element.min);
const max = Number(element.max);
const doNotify = () => element.dispatchEvent(new Event('change', {bubbles: true}));
const onChange = ({type}) => {
if (type == 'input' && element.checkValidity()) {
doNotify();
} else if (type == 'change' && !element.checkValidity()) {
element.value = Math.max(min, Math.min(max, Number(element.value)));
doNotify();
}
};
element.addEventListener('change', onChange);
element.addEventListener('input', onChange);
}
2017-03-25 05:54:58 +00:00
function $(selector, base = document) {
// we have ids with . like #manage.onlyEnabled which looks like #id.class
2017-03-25 05:54:58 +00:00
// so since getElementById is superfast we'll try it anyway
const byId = selector.startsWith('#') && document.getElementById(selector.slice(1));
return byId || base.querySelector(selector);
}
function $$(selector, base = document) {
return [...base.querySelectorAll(selector)];
}
function $element(opt) {
// tag: string, default 'div', may include namespace like 'ns#tag'
// appendChild: element or an array of elements
// dataset: object
// any DOM property: assigned as is
const [ns, tag] = opt.tag && opt.tag.includes('#')
? opt.tag.split('#')
: [null, opt.tag];
const element = ns
? document.createElementNS(ns == 'SVG' || ns == 'svg' ? 'http://www.w3.org/2000/svg' : ns, tag)
: document.createElement(tag || 'div');
(opt.appendChild instanceof Array ? opt.appendChild : [opt.appendChild])
.forEach(child => child && element.appendChild(child));
delete opt.appendChild;
delete opt.tag;
if (opt.dataset) {
Object.assign(element.dataset, opt.dataset);
delete opt.dataset;
}
if (ns) {
for (const attr in opt) {
element.setAttributeNS(null, attr, opt[attr]);
}
} else {
Object.assign(element, opt);
}
return element;
}