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 {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} -
|
* @returns {HTMLElement|false|undefined} -
|
||||||
* HTMLElement: focus changed,
|
* HTMLElement: focus changed,
|
||||||
* false: focus unchanged,
|
* false: focus unchanged,
|
||||||
|
@ -397,16 +398,15 @@ function focusAccessibility() {
|
||||||
*/
|
*/
|
||||||
function moveFocus(rootElement, step) {
|
function moveFocus(rootElement, step) {
|
||||||
const elements = [...rootElement.getElementsByTagName('*')];
|
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 num = elements.length;
|
||||||
const {activeElement} = document;
|
if (!step) step = 1;
|
||||||
for (let i = 1; i < num; i++) {
|
for (let i = 1; i < num; i++) {
|
||||||
const elementIndex = (activeIndex + i * step + num) % num;
|
const el = elements[(activeIndex + i * step + num) % num];
|
||||||
// we don't use positive tabindex so we stop at any valid value
|
|
||||||
const el = elements[elementIndex];
|
|
||||||
if (!el.disabled && el.tabIndex >= 0) {
|
if (!el.disabled && el.tabIndex >= 0) {
|
||||||
el.focus();
|
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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<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>
|
<svg class="svg-icon config"><use xlink:href="#svg-icon-config"></use></svg>
|
||||||
</a>
|
</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">
|
<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"/>
|
<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>
|
</svg>
|
||||||
</a>
|
</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">
|
<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"/>
|
<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>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu" tabindex="-1">
|
<div class="menu">
|
||||||
<div class="menu-items-wrapper">
|
<div class="menu-items-wrapper">
|
||||||
<b class="menu-title"></b>
|
<b class="menu-title"></b>
|
||||||
<label class="menu-item exclude-by-domain button">
|
<label class="menu-item exclude-by-domain button">
|
||||||
|
|
|
@ -729,11 +729,6 @@ body.blocked .actions > .main-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
#confirm[data-display=true] + #installed .menu[data-display=true] {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#confirm > div {
|
#confirm > div {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
|
|
130
popup/popup.js
130
popup/popup.js
|
@ -10,9 +10,11 @@
|
||||||
configDialog
|
configDialog
|
||||||
FIREFOX
|
FIREFOX
|
||||||
getActiveTab
|
getActiveTab
|
||||||
|
getEventKeyName
|
||||||
getStyleDataMerged
|
getStyleDataMerged
|
||||||
hotkeys
|
hotkeys
|
||||||
initializing
|
initializing
|
||||||
|
moveFocus
|
||||||
msg
|
msg
|
||||||
onDOMready
|
onDOMready
|
||||||
prefs
|
prefs
|
||||||
|
@ -30,6 +32,7 @@ let installed;
|
||||||
const handleEvent = {};
|
const handleEvent = {};
|
||||||
|
|
||||||
const ENTRY_ID_PREFIX_RAW = 'style-';
|
const ENTRY_ID_PREFIX_RAW = 'style-';
|
||||||
|
const MODAL_SHOWN = 'data-display'; // attribute name
|
||||||
|
|
||||||
$.entry = styleOrId => $(`#${ENTRY_ID_PREFIX_RAW}${styleOrId.id || styleOrId}`);
|
$.entry = styleOrId => $(`#${ENTRY_ID_PREFIX_RAW}${styleOrId.id || styleOrId}`);
|
||||||
|
|
||||||
|
@ -135,6 +138,19 @@ async function initPopup(frames) {
|
||||||
|
|
||||||
$('#popup-wiki-button').onclick = handleEvent.openURLandHide;
|
$('#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')) {
|
if (!prefs.get('popup.stylesFirst')) {
|
||||||
document.body.insertBefore(
|
document.body.insertBefore(
|
||||||
$('body > .actions'),
|
$('body > .actions'),
|
||||||
|
@ -468,88 +484,20 @@ Object.assign(handleEvent, {
|
||||||
toggleMenu(event) {
|
toggleMenu(event) {
|
||||||
const entry = handleEvent.getClickedStyleElement(event);
|
const entry = handleEvent.getClickedStyleElement(event);
|
||||||
const menu = $('.menu', entry);
|
const menu = $('.menu', entry);
|
||||||
const menuActive = $('.menu[data-display=true]');
|
if (menu.hasAttribute(MODAL_SHOWN)) {
|
||||||
if (menuActive) {
|
hideModal(menu, {animate: true});
|
||||||
// fade-out style menu
|
|
||||||
animateElement(menu, 'lights-on')
|
|
||||||
.then(() => (menu.dataset.display = false));
|
|
||||||
window.onkeydown = null;
|
|
||||||
} else {
|
} else {
|
||||||
$('.menu-title', entry).textContent = $('.style-name', entry).textContent;
|
$('.menu-title', entry).textContent = $('.style-name', entry).textContent;
|
||||||
menu.dataset.display = true;
|
showModal(menu, '.menu-close');
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
delete(event) {
|
delete(event) {
|
||||||
const entry = handleEvent.getClickedStyleElement(event);
|
const entry = handleEvent.getClickedStyleElement(event);
|
||||||
const id = entry.styleId;
|
|
||||||
const box = $('#confirm');
|
const box = $('#confirm');
|
||||||
const menu = $('.menu', entry);
|
box.dataset.id = entry.styleId;
|
||||||
const cancel = $('[data-cmd="cancel"]', box);
|
|
||||||
const affirm = $('[data-cmd="ok"]', box);
|
|
||||||
box.dataset.display = true;
|
|
||||||
box.style.cssText = '';
|
|
||||||
$('b', box).textContent = $('.style-name', entry).textContent;
|
$('b', box).textContent = $('.style-name', entry).textContent;
|
||||||
affirm.focus();
|
showModal(box, '[data-cmd=cancel]');
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
configure(event) {
|
configure(event) {
|
||||||
|
@ -672,3 +620,39 @@ function blockPopup(isBlocked = true) {
|
||||||
t.template.noStyles.remove();
|
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