[change] modality-specific focus styles

Remove the default focus ring when the keyboard is not being used. This
provides a superior UX when using touch or mouse.

Fix #310
This commit is contained in:
Nicolas Gallagher
2017-02-18 13:39:25 -08:00
parent a3ed8f05e6
commit 44ebd8f5a3
3 changed files with 103 additions and 0 deletions
+3
View File
@@ -18,6 +18,9 @@ import View from './components/View';
// modules
import createDOMElement from './modules/createDOMElement';
import modality from './modules/modality';
modality();
const ReactNativeCore = {
createDOMElement,
+3
View File
@@ -39,6 +39,7 @@ import View from './components/View';
// modules
import createDOMElement from './modules/createDOMElement';
import modality from './modules/modality';
import NativeModules from './modules/NativeModules';
// propTypes
@@ -46,6 +47,8 @@ import ColorPropType from './propTypes/ColorPropType';
import EdgeInsetsPropType from './propTypes/EdgeInsetsPropType';
import PointPropType from './propTypes/PointPropType';
modality();
const ReactNative = {
// top-level API
findNodeHandle,
+97
View File
@@ -0,0 +1,97 @@
/* global document, window */
/**
* Adapts focus styles based on the user's active input modality (i.e., how
* they are interacting with the UI right now).
*
* Focus styles are only relevant when using the keyboard to interact with the
* page. If we only show the focus ring when relevant, we can avoid user
* confusion without compromising accessibility.
*
* The script uses two heuristics to determine whether the keyboard is being used:
*
* 1. a keydown event occurred immediately before a focus event;
* 2. a focus event happened on an element which requires keyboard interaction (e.g., a text field);
*
* Based on https://github.com/WICG/modality
*/
const modality = () => {
/**
* Determine whether the keyboard is required when an element is focused
*/
const proto = window.Element.prototype;
const matcher = proto.matches || proto.mozMatchesSelector || proto.msMatchesSelector || proto.webkitMatchesSelector;
const keyboardModalityWhitelist = [
'input:not([type])',
'input[type=text]',
'input[type=number]',
'input[type=date]',
'input[type=time]',
'input[type=datetime]',
'textarea',
'[role=textbox]',
// indicates that a custom element supports the keyboard
'[supports-modality=keyboard]'
].join(',');
const focusTriggersKeyboardModality = (el) => {
if (matcher) {
return matcher.call(el, keyboardModalityWhitelist) && matcher.call(el, ':not([readonly])');
} else {
return false;
}
};
/**
* Disable the focus ring by default
*/
const id = 'modality__';
const style = `<style id="${id}">:focus { outline: none; }</style>`;
document.head.insertAdjacentHTML('afterbegin', style);
const styleElement = document.getElementById(id);
const disableFocus = () => {
if (styleElement) {
styleElement.disabled = false;
}
};
const enableFocus = () => {
if (styleElement) {
styleElement.disabled = true;
}
};
/**
* Manage the modality focus state
*/
let keyboardTimer;
let hadKeyboardEvent = false;
// track when the keyboard is in use
document.body.addEventListener('keydown', () => {
hadKeyboardEvent = true;
if (keyboardTimer) {
clearTimeout(keyboardTimer);
}
keyboardTimer = setTimeout(() => {
hadKeyboardEvent = false;
}, 100);
}, true);
// disable focus style reset when the keyboard is in use
document.body.addEventListener('focus', (e) => {
if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {
enableFocus();
}
}, true);
// enable focus style reset when keyboard is no longer in use
document.body.addEventListener('blur', () => {
if (!hadKeyboardEvent) {
disableFocus();
}
}, true);
};
export default modality;