UNPKG

4.91 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports.teardown = teardown;
9exports.default = useIsFocusVisible;
10
11var React = _interopRequireWildcard(require("react"));
12
13var ReactDOM = _interopRequireWildcard(require("react-dom"));
14
15// based on https://github.com/WICG/focus-visible/blob/v4.1.5/src/focus-visible.js
16var hadKeyboardEvent = true;
17var hadFocusVisibleRecently = false;
18var hadFocusVisibleRecentlyTimeout = null;
19var inputTypesWhitelist = {
20 text: true,
21 search: true,
22 url: true,
23 tel: true,
24 email: true,
25 password: true,
26 number: true,
27 date: true,
28 month: true,
29 week: true,
30 time: true,
31 datetime: true,
32 'datetime-local': true
33};
34/**
35 * Computes whether the given element should automatically trigger the
36 * `focus-visible` class being added, i.e. whether it should always match
37 * `:focus-visible` when focused.
38 * @param {Element} node
39 * @return {boolean}
40 */
41
42function focusTriggersKeyboardModality(node) {
43 var type = node.type,
44 tagName = node.tagName;
45
46 if (tagName === 'INPUT' && inputTypesWhitelist[type] && !node.readOnly) {
47 return true;
48 }
49
50 if (tagName === 'TEXTAREA' && !node.readOnly) {
51 return true;
52 }
53
54 if (node.isContentEditable) {
55 return true;
56 }
57
58 return false;
59}
60/**
61 * Keep track of our keyboard modality state with `hadKeyboardEvent`.
62 * If the most recent user interaction was via the keyboard;
63 * and the key press did not include a meta, alt/option, or control key;
64 * then the modality is keyboard. Otherwise, the modality is not keyboard.
65 * @param {KeyboardEvent} event
66 */
67
68
69function handleKeyDown(event) {
70 if (event.metaKey || event.altKey || event.ctrlKey) {
71 return;
72 }
73
74 hadKeyboardEvent = true;
75}
76/**
77 * If at any point a user clicks with a pointing device, ensure that we change
78 * the modality away from keyboard.
79 * This avoids the situation where a user presses a key on an already focused
80 * element, and then clicks on a different element, focusing it with a
81 * pointing device, while we still think we're in keyboard modality.
82 */
83
84
85function handlePointerDown() {
86 hadKeyboardEvent = false;
87}
88
89function handleVisibilityChange() {
90 if (this.visibilityState === 'hidden') {
91 // If the tab becomes active again, the browser will handle calling focus
92 // on the element (Safari actually calls it twice).
93 // If this tab change caused a blur on an element with focus-visible,
94 // re-apply the class when the user switches back to the tab.
95 if (hadFocusVisibleRecently) {
96 hadKeyboardEvent = true;
97 }
98 }
99}
100
101function prepare(doc) {
102 doc.addEventListener('keydown', handleKeyDown, true);
103 doc.addEventListener('mousedown', handlePointerDown, true);
104 doc.addEventListener('pointerdown', handlePointerDown, true);
105 doc.addEventListener('touchstart', handlePointerDown, true);
106 doc.addEventListener('visibilitychange', handleVisibilityChange, true);
107}
108
109function teardown(doc) {
110 doc.removeEventListener('keydown', handleKeyDown, true);
111 doc.removeEventListener('mousedown', handlePointerDown, true);
112 doc.removeEventListener('pointerdown', handlePointerDown, true);
113 doc.removeEventListener('touchstart', handlePointerDown, true);
114 doc.removeEventListener('visibilitychange', handleVisibilityChange, true);
115}
116
117function isFocusVisible(event) {
118 var target = event.target;
119
120 try {
121 return target.matches(':focus-visible');
122 } catch (error) {} // browsers not implementing :focus-visible will throw a SyntaxError
123 // we use our own heuristic for those browsers
124 // rethrow might be better if it's not the expected error but do we really
125 // want to crash if focus-visible malfunctioned?
126 // no need for validFocusTarget check. the user does that by attaching it to
127 // focusable events only
128
129
130 return hadKeyboardEvent || focusTriggersKeyboardModality(target);
131}
132/**
133 * Should be called if a blur event is fired on a focus-visible element
134 */
135
136
137function handleBlurVisible() {
138 // To detect a tab/window switch, we look for a blur event followed
139 // rapidly by a visibility change.
140 // If we don't see a visibility change within 100ms, it's probably a
141 // regular focus change.
142 hadFocusVisibleRecently = true;
143 window.clearTimeout(hadFocusVisibleRecentlyTimeout);
144 hadFocusVisibleRecentlyTimeout = window.setTimeout(function () {
145 hadFocusVisibleRecently = false;
146 }, 100);
147}
148
149function useIsFocusVisible() {
150 var ref = React.useCallback(function (instance) {
151 var node = ReactDOM.findDOMNode(instance);
152
153 if (node != null) {
154 prepare(node.ownerDocument);
155 }
156 }, []);
157
158 if (process.env.NODE_ENV !== 'production') {
159 // eslint-disable-next-line react-hooks/rules-of-hooks
160 React.useDebugValue(isFocusVisible);
161 }
162
163 return {
164 isFocusVisible: isFocusVisible,
165 onBlurVisible: handleBlurVisible,
166 ref: ref
167 };
168}
\No newline at end of file