Files
packager/conditionals.js
T
Thomas Weber 4514e2d465 Improve conditional engine
- better accessibility
- less unnecessary updates
2020-10-10 19:23:16 -05:00

118 lines
3.0 KiB
JavaScript

/*
Copyright (c) 2020 Thomas Weber
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
window.Conditionals = (function() {
/** @type {Conditional[]} */
const conditionals = [];
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion)');
class Conditional {
constructor() {
this._lastVisible = null;
}
isVisible() {
return false;
}
onchange(visible) {
}
update() {
const visible = this.isVisible();
if (visible === this._lastVisible) {
return;
}
this._lastVisible = visible;
this.onchange(visible);
}
}
class Regular extends Conditional {
constructor(el) {
super();
this.el = el;
}
onchange(visible) {
this.el.hidden = !visible;
}
}
class Animated extends Conditional {
constructor(el) {
super();
this.el = el;
this.el.style.overflow = 'hidden';
this.el.style.transition = '.2s';
this.height = getComputedStyle(el).height;
}
onchange(visible) {
// Because the element isn't actually hidden or removed from the DOM, we have to do some extra work to make sure that
// inputs and such won't upset accessibility tools or tab index
if (visible) {
this.el.style.height = this.height;
this.el.removeAttribute('aria-hidden');
for (const input of this.el.querySelectorAll('input')) {
input.disabled = input._disabled;
input.tabIndex = input._tabIndex;
}
} else {
this.el.style.height = '0';
this.el.setAttribute('aria-hidden', 'true');
for (const input of this.el.querySelectorAll('input')) {
input._disabled = input.disabled;
input._tabIndex = input.tabIndex;
input.tabIndex = -1;
input.disabled = true;
}
}
}
}
const setupConditionals = () => {
for (const el of document.querySelectorAll('[if]')) {
const isVisible = new Function('return ' + el.getAttribute('if') + ';');
let conditional;
if (el.classList.contains('fold') && !prefersReducedMotion.matches) {
conditional = new Animated(el);
} else {
conditional = new Regular(el);
}
conditional.isVisible = isVisible;
conditionals.push(conditional);
}
updateConditionals();
};
const updateConditionals = () => {
for (const i of conditionals) {
i.update();
}
};
return {
setup: setupConditionals,
update: updateConditionals,
};
})();