1 |
|
2 | import * as React from 'react';
|
3 | import * as ReactDOM from 'react-dom';
|
4 | let hadKeyboardEvent = true;
|
5 | let hadFocusVisibleRecently = false;
|
6 | let hadFocusVisibleRecentlyTimeout = null;
|
7 | const inputTypesWhitelist = {
|
8 | text: true,
|
9 | search: true,
|
10 | url: true,
|
11 | tel: true,
|
12 | email: true,
|
13 | password: true,
|
14 | number: true,
|
15 | date: true,
|
16 | month: true,
|
17 | week: true,
|
18 | time: true,
|
19 | datetime: true,
|
20 | 'datetime-local': true
|
21 | };
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | function focusTriggersKeyboardModality(node) {
|
31 | const {
|
32 | type,
|
33 | tagName
|
34 | } = node;
|
35 |
|
36 | if (tagName === 'INPUT' && inputTypesWhitelist[type] && !node.readOnly) {
|
37 | return true;
|
38 | }
|
39 |
|
40 | if (tagName === 'TEXTAREA' && !node.readOnly) {
|
41 | return true;
|
42 | }
|
43 |
|
44 | if (node.isContentEditable) {
|
45 | return true;
|
46 | }
|
47 |
|
48 | return false;
|
49 | }
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | function handleKeyDown(event) {
|
60 | if (event.metaKey || event.altKey || event.ctrlKey) {
|
61 | return;
|
62 | }
|
63 |
|
64 | hadKeyboardEvent = true;
|
65 | }
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | function handlePointerDown() {
|
76 | hadKeyboardEvent = false;
|
77 | }
|
78 |
|
79 | function handleVisibilityChange() {
|
80 | if (this.visibilityState === 'hidden') {
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | if (hadFocusVisibleRecently) {
|
86 | hadKeyboardEvent = true;
|
87 | }
|
88 | }
|
89 | }
|
90 |
|
91 | function prepare(doc) {
|
92 | doc.addEventListener('keydown', handleKeyDown, true);
|
93 | doc.addEventListener('mousedown', handlePointerDown, true);
|
94 | doc.addEventListener('pointerdown', handlePointerDown, true);
|
95 | doc.addEventListener('touchstart', handlePointerDown, true);
|
96 | doc.addEventListener('visibilitychange', handleVisibilityChange, true);
|
97 | }
|
98 |
|
99 | export function teardown(doc) {
|
100 | doc.removeEventListener('keydown', handleKeyDown, true);
|
101 | doc.removeEventListener('mousedown', handlePointerDown, true);
|
102 | doc.removeEventListener('pointerdown', handlePointerDown, true);
|
103 | doc.removeEventListener('touchstart', handlePointerDown, true);
|
104 | doc.removeEventListener('visibilitychange', handleVisibilityChange, true);
|
105 | }
|
106 |
|
107 | function isFocusVisible(event) {
|
108 | const {
|
109 | target
|
110 | } = event;
|
111 |
|
112 | try {
|
113 | return target.matches(':focus-visible');
|
114 | } catch (error) {}
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 | return hadKeyboardEvent || focusTriggersKeyboardModality(target);
|
123 | }
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 | function handleBlurVisible() {
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | hadFocusVisibleRecently = true;
|
135 | window.clearTimeout(hadFocusVisibleRecentlyTimeout);
|
136 | hadFocusVisibleRecentlyTimeout = window.setTimeout(() => {
|
137 | hadFocusVisibleRecently = false;
|
138 | }, 100);
|
139 | }
|
140 |
|
141 | export default function useIsFocusVisible() {
|
142 | const ref = React.useCallback(instance => {
|
143 | const node = ReactDOM.findDOMNode(instance);
|
144 |
|
145 | if (node != null) {
|
146 | prepare(node.ownerDocument);
|
147 | }
|
148 | }, []);
|
149 |
|
150 | if (process.env.NODE_ENV !== 'production') {
|
151 |
|
152 | React.useDebugValue(isFocusVisible);
|
153 | }
|
154 |
|
155 | return {
|
156 | isFocusVisible,
|
157 | onBlurVisible: handleBlurVisible,
|
158 | ref
|
159 | };
|
160 | } |
\ | No newline at end of file |