[fix] ref.focus() should focus any element type

Ensure that programmatic focus can be moved to any element. Each
instance of a primitive component type (e.g., `View`, `Text`, etc.)
includes a `focus` method. However, on the web only certain elements can
receive programmatic focus by default: those that can also receive
keyboard focus, e.g., `a`, `button`, `input`, etc. All other element
types must set `tabIndex="-1"` in order to be programmatically focusable
without also being focusable via keyboard or mouse.

Fix #1099
This commit is contained in:
Nicolas Gallagher
2018-11-10 12:03:44 -08:00
parent 91c9457392
commit b6be677db9
2 changed files with 41 additions and 4 deletions
@@ -2,8 +2,8 @@
import UIManager from '..';
const createStyledNode = (style = {}) => {
const root = document.createElement('div');
const createStyledNode = (name = 'div', style = {}) => {
const root = document.createElement(name);
Object.keys(style).forEach(prop => {
root.style[prop] = style[prop];
});
@@ -18,6 +18,29 @@ const componentStub = {
};
describe('apis/UIManager', () => {
describe('focus', () => {
test('sets tabIndex="-1" on elements not programmatically focusable by default', () => {
const node = createStyledNode();
UIManager.focus(node);
expect(node.getAttribute('tabIndex')).toEqual('-1');
});
test('doesn\'t set tabIndex="-1" on elements with an existing tabIndex', () => {
const node = createStyledNode();
node.tabIndex = 0;
UIManager.focus(node);
expect(node.getAttribute('tabIndex')).toEqual('0');
});
test('doesn\'t set tabIndex="-1" on elements focusable by default', () => {
['a', 'input', 'select', 'textarea'].forEach(name => {
const node = createStyledNode(name);
UIManager.focus(node);
expect(node.getAttribute('tabIndex')).toBeNull();
});
});
});
describe('updateView', () => {
test('supports className alias for class', () => {
const node = createStyledNode();
@@ -27,7 +50,7 @@ describe('apis/UIManager', () => {
});
test('adds correct DOM styles to existing style', () => {
const node = createStyledNode({ color: 'red' });
const node = createStyledNode('div', { color: 'red' });
const props = { style: { marginTop: 0, marginBottom: 0, opacity: 0 } };
UIManager.updateView(node, props, componentStub);
expect(node.getAttribute('style')).toEqual(
@@ -36,7 +59,7 @@ describe('apis/UIManager', () => {
});
test('replaces input and textarea text', () => {
const node = createStyledNode();
const node = createStyledNode('textarea');
node.value = 'initial';
const textProp = { text: 'expected-text' };
const valueProp = { value: 'expected-value' };
@@ -37,6 +37,13 @@ const measureLayout = (node, relativeToNativeNode, callback) => {
}
};
const focusableElements = {
A: true,
INPUT: true,
SELECT: true,
TEXTAREA: true
};
const UIManager = {
blur(node) {
try {
@@ -46,6 +53,13 @@ const UIManager = {
focus(node) {
try {
const name = node.nodeName;
// A tabIndex of -1 allows element to be programmatically focused but
// prevents keyboard focus, so we don't want to set the value on elements
// that support keyboard focus by default.
if (node.getAttribute('tabIndex') == null && focusableElements[name] == null) {
node.setAttribute('tabIndex', '-1');
}
node.focus();
} catch (err) {}
},