Merge remote-tracking branch 'upstream/master'

* upstream/master:
  don't autofocus external links like feedback
  fixup c57fef7b: always set lastFocusedViaClick
  notify embedder on closing colorpicker
  code cosmetics
  suppress focus outline when invoked via mouse
This commit is contained in:
dana 2018-09-06 22:17:28 -07:00
commit 731e8ad249
3 changed files with 43 additions and 20 deletions

View File

@ -313,20 +313,31 @@ function initCollapsibles({bindClickOn = 'h2'} = {}) {
} }
} }
// Makes the focus outline appear on keyboard tabbing, but not on mouse clicks.
function focusAccessibility() { function focusAccessibility() {
// Makes the focus outline appear on keyboard tabbing, but not on mouse clicks. // last event's focusedViaClick
// Since we don't want full layout recalc, we modify only the closest focusable element, focusAccessibility.lastFocusedViaClick = false;
// which we try to find in DOM for this many parentElement jumps: // tags of focusable elements;
const focusables = focusAccessibility.ELEMENTS = // to avoid a full layout recalc we modify the closest one
['a', 'button', 'input', 'textarea', 'label', 'select', 'summary']; focusAccessibility.ELEMENTS = [
'a',
'button',
'input',
'textarea',
'label',
'select',
'summary',
];
// try to find a focusable parent for this many parentElement jumps:
const GIVE_UP_DEPTH = 4; const GIVE_UP_DEPTH = 4;
addEventListener('mousedown', suppressOutlineOnClick, {passive: true}); addEventListener('mousedown', suppressOutlineOnClick, {passive: true});
addEventListener('keydown', keepOutlineOnTab, {passive: true}); addEventListener('keydown', keepOutlineOnTab, {passive: true});
function suppressOutlineOnClick({target}) { function suppressOutlineOnClick({target}) {
for (let el = target, i = 0; el && i++ < GIVE_UP_DEPTH; el = el.parentElement) { for (let el = target, i = 0; el && i++ < GIVE_UP_DEPTH; el = el.parentElement) {
if (focusables.includes(el.localName)) { if (focusAccessibility.ELEMENTS.includes(el.localName)) {
focusAccessibility.lastFocusedViaClick = true;
if (el.dataset.focusedViaClick === undefined) { if (el.dataset.focusedViaClick === undefined) {
el.dataset.focusedViaClick = ''; el.dataset.focusedViaClick = '';
} }
@ -337,13 +348,14 @@ function focusAccessibility() {
function keepOutlineOnTab(event) { function keepOutlineOnTab(event) {
if (event.which === 9) { if (event.which === 9) {
focusAccessibility.lastFocusedViaClick = false;
setTimeout(keepOutlineOnTab, 0, true); setTimeout(keepOutlineOnTab, 0, true);
return; return;
} else if (event !== true) { } else if (event !== true) {
return; return;
} }
let el = document.activeElement; let el = document.activeElement;
if (!el || !focusables.includes(el.localName)) { if (!el || !focusAccessibility.ELEMENTS.includes(el.localName)) {
return; return;
} }
if (el.dataset.focusedViaClick !== undefined) { if (el.dataset.focusedViaClick !== undefined) {
@ -360,18 +372,23 @@ function focusAccessibility() {
* Switches to the next/previous keyboard-focusable element * Switches to the next/previous keyboard-focusable element
* @param {HTMLElement} rootElement * @param {HTMLElement} rootElement
* @param {Number} step - for exmaple 1 or -1 * @param {Number} step - for exmaple 1 or -1
* @returns {HTMLElement|false|undefined} -
* HTMLElement: focus changed,
* false: focus unchanged,
* undefined: nothing to focus
*/ */
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 activeIndex = Math.max(0, elements.indexOf(document.activeElement));
const num = elements.length; const num = elements.length;
const {activeElement} = document;
for (let i = 1; i < num; i++) { for (let i = 1; i < num; i++) {
const elementIndex = (activeIndex + i * step + num) % num; const elementIndex = (activeIndex + i * step + num) % num;
// we don't use positive tabindex so we stop at any valid value // we don't use positive tabindex so we stop at any valid value
const el = elements[elementIndex]; const el = elements[elementIndex];
if (!el.disabled && el.tabIndex >= 0) { if (!el.disabled && el.tabIndex >= 0) {
el.focus(); el.focus();
return; return activeElement !== el && el;
} }
} }
} }

View File

@ -34,7 +34,12 @@ function messageBox({
document.body.appendChild(messageBox.element); document.body.appendChild(messageBox.element);
messageBox.originalFocus = document.activeElement; messageBox.originalFocus = document.activeElement;
moveFocus(messageBox.element, 1); // skip external links like feedback
while ((moveFocus(messageBox.element, 1) || {}).target === '_blank') {/*NOP*/}
// suppress focus outline when invoked via click
if (focusAccessibility.lastFocusedViaClick && document.activeElement) {
document.activeElement.dataset.focusedViaClick = '';
}
if (typeof onshow === 'function') { if (typeof onshow === 'function') {
onshow(messageBox.element); onshow(messageBox.element);

View File

@ -222,11 +222,9 @@
} }
} }
function hide({notify = true} = {}) { function hide() {
if (shown) { if (shown) {
if (notify) { colorpickerCallback('');
colorpickerCallback('');
}
unregisterEvents(); unregisterEvents();
focusNoScroll(prevFocusedElement); focusNoScroll(prevFocusedElement);
$root.remove(); $root.remove();
@ -623,7 +621,7 @@
case 27: case 27:
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
hide({notify: false}); hide();
break; break;
} }
} }
@ -643,17 +641,20 @@
//region Event utilities //region Event utilities
function colorpickerCallback(colorString = currentColorToString()) { function colorpickerCallback(colorString = currentColorToString()) {
// Esc pressed? const isCallable = typeof options.callback === 'function';
if (!colorString) { // hiding
if (!colorString && isCallable) {
options.callback(''); options.callback('');
return;
} }
if ( if (
userActivity && userActivity &&
$inputs[currentFormat].every(el => el.checkValidity()) && $inputs[currentFormat].every(el => el.checkValidity())
typeof options.callback === 'function'
) { ) {
lastOutputColor = colorString.replace(/\b0\./g, '.'); lastOutputColor = colorString.replace(/\b0\./g, '.');
options.callback(lastOutputColor); if (isCallable) {
options.callback(lastOutputColor);
}
} }
} }