Cat-Printer/www/accessibility.js

125 lines
4.2 KiB
JavaScript

`
No rights reserved.
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
`;
'use strict';
function isHidden(element) {
let parents = [element];
while (parents[0].parentElement)
parents.unshift(parents[0].parentElement);
return parents.some(e => {
let rect = e.getBoundingClientRect();
return (
e.classList.contains('hidden') ||
e.classList.contains('hard-hidden') ||
e.style.display == 'none' ||
rect.width == 0 || rect.height == 0 ||
// rect.x < 0 || rect.y < 0 ||
e.style.visibility == 'none' ||
e.style.opacity == '0'
);
});
}
function toLocaleKey(key) {
if (typeof i18n === 'undefined') return key;
const qwerty = '1234567890qwertyuiopasdfghjklzxcvbnm';
let keys, index;
if (key.length !== 1 ||
(keys = i18n('KeyboardLayout')) === 'KeyboardLayout' ||
(index = qwerty.indexOf(key)) === -1
) return key;
return keys[index];
}
function keyToLetter(key) {
const map = {
' ': 'SPACE',
',': 'COMMA',
'.': "DOT"
};
return map[key] || key;
}
function keyFromCode(code) {
const map = {
9: 'Tab'
};
return map[code] || String.fromCharCode(code);
}
function initKeyboardShortcuts() {
const layer = document.getElementById('keyboard-shortcuts-layer');
const dialog = document.getElementById('dialog');
const keys = 'qwertyuiopasdfghjklzxcvbnm';
let focusing = false, started = false;
let shortcuts = {};
let focus, inputs;
const mark_keys = () => {
document.querySelectorAll(':focus').forEach(e => e.isSameNode(focus) || e.blur());
let index, key;
if (dialog.classList.contains('hidden'))
inputs = Array.from(document.querySelectorAll('*[data-key]'));
else inputs = Array.from(document.querySelectorAll('#dialog *[data-key]'));
/** @type {{ [key: string]: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement }} */
let keys2 = keys.split('');
shortcuts = {};
if (focusing) shortcuts = { 'ESC': focus };
else
for (let input of inputs) {
if (isHidden(input)) continue;
key = input.getAttribute('data-key');
if ((index = keys2.indexOf(key)) !== -1) keys2.splice(index, 1);
key = toLocaleKey(key || keys2.shift());
shortcuts[key] = input;
}
// Array.from(layer.children).forEach(e => e.remove());
for (let i = layer.children.length; i <= inputs.length; i++) {
let span = document.createElement('span');
layer.appendChild(span);
}
index = 0;
for (key in shortcuts) {
let span = layer.children[index++];
let input = shortcuts[key];
let position = input.getBoundingClientRect();
let text = i18n(keyToLetter(key.toUpperCase()));
if (span.innerText !== text) span.innerText = text;
span.style.top = (position.y || position.top) + 'px';
span.style.left = (position.x || position.left) + 'px';
span.style.display = '';
}
for (let i = index; i < layer.children.length; i++) {
layer.children[i].style.display = 'none';
}
}
const start = () => setInterval(mark_keys, 1000);
const types_to_click = ['submit', 'file', 'checkbox', 'radio', 'A'];
document.body.addEventListener('keyup', (event) => {
let key = event.key || keyFromCode(event.keyCode);
if (!started) {
if (key !== 'Tab') return;
mark_keys();
start();
started = true;
}
requestAnimationFrame(mark_keys)
let input = shortcuts[key];
if (input) {
if (types_to_click.includes(input.type || input.tagName))
input.dispatchEvent(new MouseEvent(event.shiftKey ? 'contextmenu' : 'click'));
else {
input.focus();
focusing = true;
}
focus = input;
} else if ((key === 'Escape' || !event.isTrusted) && focus) {
focus.blur();
focusing = !focusing;
}
});
}