dedup code for modals in popup
This commit is contained in:
parent
4d198f56e2
commit
5bb1b5ef35
31
js/dom.js
31
js/dom.js
|
@ -387,9 +387,10 @@ function focusAccessibility() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Switches to the next/previous keyboard-focusable element
|
||||
* Switches to the next/previous keyboard-focusable element.
|
||||
* Doesn't check `visibility` or `display` via getComputedStyle for simplicity.
|
||||
* @param {HTMLElement} rootElement
|
||||
* @param {Number} step - for exmaple 1 or -1
|
||||
* @param {Number} step - for exmaple 1 or -1 (or 0 to focus the first focusable el in the box)
|
||||
* @returns {HTMLElement|false|undefined} -
|
||||
* HTMLElement: focus changed,
|
||||
* false: focus unchanged,
|
||||
|
@ -397,16 +398,15 @@ function focusAccessibility() {
|
|||
*/
|
||||
function moveFocus(rootElement, step) {
|
||||
const elements = [...rootElement.getElementsByTagName('*')];
|
||||
const activeIndex = Math.max(0, elements.indexOf(document.activeElement));
|
||||
const activeEl = document.activeElement;
|
||||
const activeIndex = step ? Math.max(step < 0 ? 0 : -1, elements.indexOf(activeEl)) : -1;
|
||||
const num = elements.length;
|
||||
const {activeElement} = document;
|
||||
if (!step) step = 1;
|
||||
for (let i = 1; i < num; i++) {
|
||||
const elementIndex = (activeIndex + i * step + num) % num;
|
||||
// we don't use positive tabindex so we stop at any valid value
|
||||
const el = elements[elementIndex];
|
||||
const el = elements[(activeIndex + i * step + num) % num];
|
||||
if (!el.disabled && el.tabIndex >= 0) {
|
||||
el.focus();
|
||||
return activeElement !== el && el;
|
||||
return activeEl !== el && el;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -462,3 +462,18 @@ function setupLivePrefs(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* exported getEventKeyName */
|
||||
/**
|
||||
* @param {KeyboardEvent} e
|
||||
* @param {boolean} [letterAsCode] - use locale-independent KeyA..KeyZ for single-letter chars
|
||||
*/
|
||||
function getEventKeyName(e, letterAsCode) {
|
||||
const mods =
|
||||
(e.shiftKey ? 'Shift-' : '') +
|
||||
(e.ctrlKey ? 'Ctrl-' : '') +
|
||||
(e.altKey ? 'Alt-' : '') +
|
||||
(e.metaKey ? 'Meta-' : '');
|
||||
return (mods === e.key + '-' ? '' : mods) +
|
||||
(e.key.length === 1 && letterAsCode ? e.code : e.key);
|
||||
}
|
||||
|
|
|
@ -25,22 +25,22 @@
|
|||
</label>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a href="#" class="configure" i18n-title="configureStyle" tabindex="0">
|
||||
<a href="#" class="configure" i18n-title="configureStyle">
|
||||
<svg class="svg-icon config"><use xlink:href="#svg-icon-config"></use></svg>
|
||||
</a>
|
||||
<a class="style-edit-link" href="edit.html?id=" i18n-title="editStyleLabel" tabindex="0">
|
||||
<a class="style-edit-link" href="edit.html?id=" i18n-title="editStyleLabel">
|
||||
<svg class="svg-icon edit" viewBox="0 0 14 16">
|
||||
<path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" class="menu-button" i18n-title="popupMenuButtonTooltip" tabindex="0">
|
||||
<a href="#" class="menu-button" i18n-title="popupMenuButtonTooltip">
|
||||
<svg class="svg-icon menu-button-icon" viewBox="0 0 3 16">
|
||||
<path fill-rule="evenodd" d="M0 2.5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 0 0-3 0zm0 5a1.5 1.5 0 1 0 3 0 1.5 1.5 0 0 0-3 0zM1.5 14a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu" tabindex="-1">
|
||||
<div class="menu">
|
||||
<div class="menu-items-wrapper">
|
||||
<b class="menu-title"></b>
|
||||
<label class="menu-item exclude-by-domain button">
|
||||
|
|
|
@ -729,11 +729,6 @@ body.blocked .actions > .main-controls {
|
|||
display: flex;
|
||||
}
|
||||
|
||||
#confirm[data-display=true] + #installed .menu[data-display=true] {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#confirm > div {
|
||||
width: 80%;
|
||||
max-height: 80%;
|
||||
|
|
130
popup/popup.js
130
popup/popup.js
|
@ -10,9 +10,11 @@
|
|||
configDialog
|
||||
FIREFOX
|
||||
getActiveTab
|
||||
getEventKeyName
|
||||
getStyleDataMerged
|
||||
hotkeys
|
||||
initializing
|
||||
moveFocus
|
||||
msg
|
||||
onDOMready
|
||||
prefs
|
||||
|
@ -30,6 +32,7 @@ let installed;
|
|||
const handleEvent = {};
|
||||
|
||||
const ENTRY_ID_PREFIX_RAW = 'style-';
|
||||
const MODAL_SHOWN = 'data-display'; // attribute name
|
||||
|
||||
$.entry = styleOrId => $(`#${ENTRY_ID_PREFIX_RAW}${styleOrId.id || styleOrId}`);
|
||||
|
||||
|
@ -135,6 +138,19 @@ async function initPopup(frames) {
|
|||
|
||||
$('#popup-wiki-button').onclick = handleEvent.openURLandHide;
|
||||
|
||||
$('#confirm').onclick = function (e) {
|
||||
const {id} = this.dataset;
|
||||
switch (e.target.dataset.cmd) {
|
||||
case 'ok':
|
||||
hideModal(this, {animate: true});
|
||||
API.deleteStyle(Number(id));
|
||||
break;
|
||||
case 'cancel':
|
||||
showModal($('.menu', $.entry(id)), '.menu-close');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (!prefs.get('popup.stylesFirst')) {
|
||||
document.body.insertBefore(
|
||||
$('body > .actions'),
|
||||
|
@ -468,88 +484,20 @@ Object.assign(handleEvent, {
|
|||
toggleMenu(event) {
|
||||
const entry = handleEvent.getClickedStyleElement(event);
|
||||
const menu = $('.menu', entry);
|
||||
const menuActive = $('.menu[data-display=true]');
|
||||
if (menuActive) {
|
||||
// fade-out style menu
|
||||
animateElement(menu, 'lights-on')
|
||||
.then(() => (menu.dataset.display = false));
|
||||
window.onkeydown = null;
|
||||
if (menu.hasAttribute(MODAL_SHOWN)) {
|
||||
hideModal(menu, {animate: true});
|
||||
} else {
|
||||
$('.menu-title', entry).textContent = $('.style-name', entry).textContent;
|
||||
menu.dataset.display = true;
|
||||
menu.style.cssText = '';
|
||||
window.onkeydown = event => {
|
||||
const close = $('.menu-close', entry);
|
||||
const checkbox = $('.exclude-by-domain-checkbox', entry);
|
||||
if (document.activeElement === close && (event.key === 'Tab') && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
checkbox.focus();
|
||||
}
|
||||
if (document.activeElement === checkbox && (event.key === 'Tab') && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
close.focus();
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
close.click();
|
||||
}
|
||||
};
|
||||
showModal(menu, '.menu-close');
|
||||
}
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
delete(event) {
|
||||
const entry = handleEvent.getClickedStyleElement(event);
|
||||
const id = entry.styleId;
|
||||
const box = $('#confirm');
|
||||
const menu = $('.menu', entry);
|
||||
const cancel = $('[data-cmd="cancel"]', box);
|
||||
const affirm = $('[data-cmd="ok"]', box);
|
||||
box.dataset.display = true;
|
||||
box.style.cssText = '';
|
||||
box.dataset.id = entry.styleId;
|
||||
$('b', box).textContent = $('.style-name', entry).textContent;
|
||||
affirm.focus();
|
||||
affirm.onclick = () => confirm(true);
|
||||
cancel.onclick = () => confirm(false);
|
||||
window.onkeydown = event => {
|
||||
const close = $('.menu-close', entry);
|
||||
const checkbox = $('.exclude-by-domain-checkbox', entry);
|
||||
const confirmActive = $('#confirm[data-display="true"]');
|
||||
const {key} = event;
|
||||
if (document.activeElement === cancel && (key === 'Tab')) {
|
||||
event.preventDefault();
|
||||
affirm.focus();
|
||||
}
|
||||
if (document.activeElement === close && (key === 'Tab') && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
checkbox.focus();
|
||||
}
|
||||
if (document.activeElement === checkbox && (key === 'Tab') && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
close.focus();
|
||||
}
|
||||
if (key === 'Escape') {
|
||||
event.preventDefault();
|
||||
if (confirmActive) {
|
||||
box.dataset.display = false;
|
||||
menu.focus();
|
||||
} else {
|
||||
close.click();
|
||||
}
|
||||
}
|
||||
};
|
||||
function confirm(ok) {
|
||||
if (ok) {
|
||||
// fade-out deletion confirmation dialog
|
||||
animateElement(box, 'lights-on')
|
||||
.then(() => (box.dataset.display = false));
|
||||
window.onkeydown = null;
|
||||
API.deleteStyle(id);
|
||||
} else {
|
||||
box.dataset.display = false;
|
||||
menu.focus();
|
||||
}
|
||||
}
|
||||
showModal(box, '[data-cmd=cancel]');
|
||||
},
|
||||
|
||||
configure(event) {
|
||||
|
@ -672,3 +620,39 @@ function blockPopup(isBlocked = true) {
|
|||
t.template.noStyles.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function showModal(box, cancelButtonSelector) {
|
||||
const oldBox = $(`[${MODAL_SHOWN}]`);
|
||||
if (oldBox) box.style.animationName = 'none';
|
||||
// '' would be fine but 'true' is backward-compatible with the existing userstyles
|
||||
box.setAttribute(MODAL_SHOWN, 'true');
|
||||
box._onkeydown = e => {
|
||||
const key = getEventKeyName(e);
|
||||
switch (key) {
|
||||
case 'Tab':
|
||||
case 'Shift-Tab':
|
||||
e.preventDefault();
|
||||
moveFocus(box, e.shiftKey ? -1 : 1);
|
||||
break;
|
||||
case 'Escape': {
|
||||
e.preventDefault();
|
||||
window.onkeydown = null;
|
||||
$(cancelButtonSelector, box).click();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
window.on('keydown', box._onkeydown);
|
||||
moveFocus(box, 0);
|
||||
hideModal(oldBox);
|
||||
}
|
||||
|
||||
async function hideModal(box, {animate} = {}) {
|
||||
window.off('keydown', box._onkeydown);
|
||||
box._onkeydown = null;
|
||||
if (animate) {
|
||||
box.style.animationName = '';
|
||||
await animateElement(box, 'lights-on');
|
||||
}
|
||||
box.removeAttribute(MODAL_SHOWN);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user