UNPKG

13.9 kBJavaScriptView Raw
1import {isMac as $28AnR$isMac, isVirtualClick as $28AnR$isVirtualClick, getOwnerWindow as $28AnR$getOwnerWindow, getOwnerDocument as $28AnR$getOwnerDocument} from "@react-aria/utils";
2import {useState as $28AnR$useState, useEffect as $28AnR$useEffect} from "react";
3import {useIsSSR as $28AnR$useIsSSR} from "@react-aria/ssr";
4
5/*
6 * Copyright 2020 Adobe. All rights reserved.
7 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License. You may obtain a copy
9 * of the License at http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software distributed under
12 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
13 * OF ANY KIND, either express or implied. See the License for the specific language
14 * governing permissions and limitations under the License.
15 */ // Portions of the code in this file are based on code from react.
16// Original licensing for the following can be found in the
17// NOTICE file in the root directory of this source tree.
18// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
19
20
21
22let $507fabe10e71c6fb$var$currentModality = null;
23let $507fabe10e71c6fb$var$changeHandlers = new Set();
24let $507fabe10e71c6fb$export$d90243b58daecda7 = new Map(); // We use a map here to support setting event listeners across multiple document objects.
25let $507fabe10e71c6fb$var$hasEventBeforeFocus = false;
26let $507fabe10e71c6fb$var$hasBlurredWindowRecently = false;
27// Only Tab or Esc keys will make focus visible on text input elements
28const $507fabe10e71c6fb$var$FOCUS_VISIBLE_INPUT_KEYS = {
29 Tab: true,
30 Escape: true
31};
32function $507fabe10e71c6fb$var$triggerChangeHandlers(modality, e) {
33 for (let handler of $507fabe10e71c6fb$var$changeHandlers)handler(modality, e);
34}
35/**
36 * Helper function to determine if a KeyboardEvent is unmodified and could make keyboard focus styles visible.
37 */ function $507fabe10e71c6fb$var$isValidKey(e) {
38 // Control and Shift keys trigger when navigating back to the tab with keyboard.
39 return !(e.metaKey || !(0, $28AnR$isMac)() && e.altKey || e.ctrlKey || e.key === 'Control' || e.key === 'Shift' || e.key === 'Meta');
40}
41function $507fabe10e71c6fb$var$handleKeyboardEvent(e) {
42 $507fabe10e71c6fb$var$hasEventBeforeFocus = true;
43 if ($507fabe10e71c6fb$var$isValidKey(e)) {
44 $507fabe10e71c6fb$var$currentModality = 'keyboard';
45 $507fabe10e71c6fb$var$triggerChangeHandlers('keyboard', e);
46 }
47}
48function $507fabe10e71c6fb$var$handlePointerEvent(e) {
49 $507fabe10e71c6fb$var$currentModality = 'pointer';
50 if (e.type === 'mousedown' || e.type === 'pointerdown') {
51 $507fabe10e71c6fb$var$hasEventBeforeFocus = true;
52 $507fabe10e71c6fb$var$triggerChangeHandlers('pointer', e);
53 }
54}
55function $507fabe10e71c6fb$var$handleClickEvent(e) {
56 if ((0, $28AnR$isVirtualClick)(e)) {
57 $507fabe10e71c6fb$var$hasEventBeforeFocus = true;
58 $507fabe10e71c6fb$var$currentModality = 'virtual';
59 }
60}
61function $507fabe10e71c6fb$var$handleFocusEvent(e) {
62 // Firefox fires two extra focus events when the user first clicks into an iframe:
63 // first on the window, then on the document. We ignore these events so they don't
64 // cause keyboard focus rings to appear.
65 if (e.target === window || e.target === document) return;
66 // If a focus event occurs without a preceding keyboard or pointer event, switch to virtual modality.
67 // This occurs, for example, when navigating a form with the next/previous buttons on iOS.
68 if (!$507fabe10e71c6fb$var$hasEventBeforeFocus && !$507fabe10e71c6fb$var$hasBlurredWindowRecently) {
69 $507fabe10e71c6fb$var$currentModality = 'virtual';
70 $507fabe10e71c6fb$var$triggerChangeHandlers('virtual', e);
71 }
72 $507fabe10e71c6fb$var$hasEventBeforeFocus = false;
73 $507fabe10e71c6fb$var$hasBlurredWindowRecently = false;
74}
75function $507fabe10e71c6fb$var$handleWindowBlur() {
76 // When the window is blurred, reset state. This is necessary when tabbing out of the window,
77 // for example, since a subsequent focus event won't be fired.
78 $507fabe10e71c6fb$var$hasEventBeforeFocus = false;
79 $507fabe10e71c6fb$var$hasBlurredWindowRecently = true;
80}
81/**
82 * Setup global event listeners to control when keyboard focus style should be visible.
83 */ function $507fabe10e71c6fb$var$setupGlobalFocusEvents(element) {
84 if (typeof window === 'undefined' || $507fabe10e71c6fb$export$d90243b58daecda7.get((0, $28AnR$getOwnerWindow)(element))) return;
85 const windowObject = (0, $28AnR$getOwnerWindow)(element);
86 const documentObject = (0, $28AnR$getOwnerDocument)(element);
87 // Programmatic focus() calls shouldn't affect the current input modality.
88 // However, we need to detect other cases when a focus event occurs without
89 // a preceding user event (e.g. screen reader focus). Overriding the focus
90 // method on HTMLElement.prototype is a bit hacky, but works.
91 let focus = windowObject.HTMLElement.prototype.focus;
92 windowObject.HTMLElement.prototype.focus = function() {
93 $507fabe10e71c6fb$var$hasEventBeforeFocus = true;
94 focus.apply(this, arguments);
95 };
96 documentObject.addEventListener('keydown', $507fabe10e71c6fb$var$handleKeyboardEvent, true);
97 documentObject.addEventListener('keyup', $507fabe10e71c6fb$var$handleKeyboardEvent, true);
98 documentObject.addEventListener('click', $507fabe10e71c6fb$var$handleClickEvent, true);
99 // Register focus events on the window so they are sure to happen
100 // before React's event listeners (registered on the document).
101 windowObject.addEventListener('focus', $507fabe10e71c6fb$var$handleFocusEvent, true);
102 windowObject.addEventListener('blur', $507fabe10e71c6fb$var$handleWindowBlur, false);
103 if (typeof PointerEvent !== 'undefined') {
104 documentObject.addEventListener('pointerdown', $507fabe10e71c6fb$var$handlePointerEvent, true);
105 documentObject.addEventListener('pointermove', $507fabe10e71c6fb$var$handlePointerEvent, true);
106 documentObject.addEventListener('pointerup', $507fabe10e71c6fb$var$handlePointerEvent, true);
107 } else {
108 documentObject.addEventListener('mousedown', $507fabe10e71c6fb$var$handlePointerEvent, true);
109 documentObject.addEventListener('mousemove', $507fabe10e71c6fb$var$handlePointerEvent, true);
110 documentObject.addEventListener('mouseup', $507fabe10e71c6fb$var$handlePointerEvent, true);
111 }
112 // Add unmount handler
113 windowObject.addEventListener('beforeunload', ()=>{
114 $507fabe10e71c6fb$var$tearDownWindowFocusTracking(element);
115 }, {
116 once: true
117 });
118 $507fabe10e71c6fb$export$d90243b58daecda7.set(windowObject, {
119 focus: focus
120 });
121}
122const $507fabe10e71c6fb$var$tearDownWindowFocusTracking = (element, loadListener)=>{
123 const windowObject = (0, $28AnR$getOwnerWindow)(element);
124 const documentObject = (0, $28AnR$getOwnerDocument)(element);
125 if (loadListener) documentObject.removeEventListener('DOMContentLoaded', loadListener);
126 if (!$507fabe10e71c6fb$export$d90243b58daecda7.has(windowObject)) return;
127 windowObject.HTMLElement.prototype.focus = $507fabe10e71c6fb$export$d90243b58daecda7.get(windowObject).focus;
128 documentObject.removeEventListener('keydown', $507fabe10e71c6fb$var$handleKeyboardEvent, true);
129 documentObject.removeEventListener('keyup', $507fabe10e71c6fb$var$handleKeyboardEvent, true);
130 documentObject.removeEventListener('click', $507fabe10e71c6fb$var$handleClickEvent, true);
131 windowObject.removeEventListener('focus', $507fabe10e71c6fb$var$handleFocusEvent, true);
132 windowObject.removeEventListener('blur', $507fabe10e71c6fb$var$handleWindowBlur, false);
133 if (typeof PointerEvent !== 'undefined') {
134 documentObject.removeEventListener('pointerdown', $507fabe10e71c6fb$var$handlePointerEvent, true);
135 documentObject.removeEventListener('pointermove', $507fabe10e71c6fb$var$handlePointerEvent, true);
136 documentObject.removeEventListener('pointerup', $507fabe10e71c6fb$var$handlePointerEvent, true);
137 } else {
138 documentObject.removeEventListener('mousedown', $507fabe10e71c6fb$var$handlePointerEvent, true);
139 documentObject.removeEventListener('mousemove', $507fabe10e71c6fb$var$handlePointerEvent, true);
140 documentObject.removeEventListener('mouseup', $507fabe10e71c6fb$var$handlePointerEvent, true);
141 }
142 $507fabe10e71c6fb$export$d90243b58daecda7.delete(windowObject);
143};
144function $507fabe10e71c6fb$export$2f1888112f558a7d(element) {
145 const documentObject = (0, $28AnR$getOwnerDocument)(element);
146 let loadListener;
147 if (documentObject.readyState !== 'loading') $507fabe10e71c6fb$var$setupGlobalFocusEvents(element);
148 else {
149 loadListener = ()=>{
150 $507fabe10e71c6fb$var$setupGlobalFocusEvents(element);
151 };
152 documentObject.addEventListener('DOMContentLoaded', loadListener);
153 }
154 return ()=>$507fabe10e71c6fb$var$tearDownWindowFocusTracking(element, loadListener);
155}
156// Server-side rendering does not have the document object defined
157// eslint-disable-next-line no-restricted-globals
158if (typeof document !== 'undefined') $507fabe10e71c6fb$export$2f1888112f558a7d();
159function $507fabe10e71c6fb$export$b9b3dfddab17db27() {
160 return $507fabe10e71c6fb$var$currentModality !== 'pointer';
161}
162function $507fabe10e71c6fb$export$630ff653c5ada6a9() {
163 return $507fabe10e71c6fb$var$currentModality;
164}
165function $507fabe10e71c6fb$export$8397ddfc504fdb9a(modality) {
166 $507fabe10e71c6fb$var$currentModality = modality;
167 $507fabe10e71c6fb$var$triggerChangeHandlers(modality, null);
168}
169function $507fabe10e71c6fb$export$98e20ec92f614cfe() {
170 $507fabe10e71c6fb$var$setupGlobalFocusEvents();
171 let [modality, setModality] = (0, $28AnR$useState)($507fabe10e71c6fb$var$currentModality);
172 (0, $28AnR$useEffect)(()=>{
173 let handler = ()=>{
174 setModality($507fabe10e71c6fb$var$currentModality);
175 };
176 $507fabe10e71c6fb$var$changeHandlers.add(handler);
177 return ()=>{
178 $507fabe10e71c6fb$var$changeHandlers.delete(handler);
179 };
180 }, []);
181 return (0, $28AnR$useIsSSR)() ? null : modality;
182}
183const $507fabe10e71c6fb$var$nonTextInputTypes = new Set([
184 'checkbox',
185 'radio',
186 'range',
187 'color',
188 'file',
189 'image',
190 'button',
191 'submit',
192 'reset'
193]);
194/**
195 * If this is attached to text input component, return if the event is a focus event (Tab/Escape keys pressed) so that
196 * focus visible style can be properly set.
197 */ function $507fabe10e71c6fb$var$isKeyboardFocusEvent(isTextInput, modality, e) {
198 var _e_target;
199 const IHTMLInputElement = typeof window !== 'undefined' ? (0, $28AnR$getOwnerWindow)(e === null || e === void 0 ? void 0 : e.target).HTMLInputElement : HTMLInputElement;
200 const IHTMLTextAreaElement = typeof window !== 'undefined' ? (0, $28AnR$getOwnerWindow)(e === null || e === void 0 ? void 0 : e.target).HTMLTextAreaElement : HTMLTextAreaElement;
201 const IHTMLElement = typeof window !== 'undefined' ? (0, $28AnR$getOwnerWindow)(e === null || e === void 0 ? void 0 : e.target).HTMLElement : HTMLElement;
202 const IKeyboardEvent = typeof window !== 'undefined' ? (0, $28AnR$getOwnerWindow)(e === null || e === void 0 ? void 0 : e.target).KeyboardEvent : KeyboardEvent;
203 isTextInput = isTextInput || (e === null || e === void 0 ? void 0 : e.target) instanceof IHTMLInputElement && !$507fabe10e71c6fb$var$nonTextInputTypes.has(e === null || e === void 0 ? void 0 : (_e_target = e.target) === null || _e_target === void 0 ? void 0 : _e_target.type) || (e === null || e === void 0 ? void 0 : e.target) instanceof IHTMLTextAreaElement || (e === null || e === void 0 ? void 0 : e.target) instanceof IHTMLElement && (e === null || e === void 0 ? void 0 : e.target.isContentEditable);
204 return !(isTextInput && modality === 'keyboard' && e instanceof IKeyboardEvent && !$507fabe10e71c6fb$var$FOCUS_VISIBLE_INPUT_KEYS[e.key]);
205}
206function $507fabe10e71c6fb$export$ffd9e5021c1fb2d6(props = {}) {
207 let { isTextInput: isTextInput, autoFocus: autoFocus } = props;
208 let [isFocusVisibleState, setFocusVisible] = (0, $28AnR$useState)(autoFocus || $507fabe10e71c6fb$export$b9b3dfddab17db27());
209 $507fabe10e71c6fb$export$ec71b4b83ac08ec3((isFocusVisible)=>{
210 setFocusVisible(isFocusVisible);
211 }, [
212 isTextInput
213 ], {
214 isTextInput: isTextInput
215 });
216 return {
217 isFocusVisible: isFocusVisibleState
218 };
219}
220function $507fabe10e71c6fb$export$ec71b4b83ac08ec3(fn, deps, opts) {
221 $507fabe10e71c6fb$var$setupGlobalFocusEvents();
222 (0, $28AnR$useEffect)(()=>{
223 let handler = (modality, e)=>{
224 if (!$507fabe10e71c6fb$var$isKeyboardFocusEvent(!!(opts === null || opts === void 0 ? void 0 : opts.isTextInput), modality, e)) return;
225 fn($507fabe10e71c6fb$export$b9b3dfddab17db27());
226 };
227 $507fabe10e71c6fb$var$changeHandlers.add(handler);
228 return ()=>{
229 $507fabe10e71c6fb$var$changeHandlers.delete(handler);
230 };
231 // eslint-disable-next-line react-hooks/exhaustive-deps
232 }, deps);
233}
234
235
236export {$507fabe10e71c6fb$export$d90243b58daecda7 as hasSetupGlobalListeners, $507fabe10e71c6fb$export$2f1888112f558a7d as addWindowFocusTracking, $507fabe10e71c6fb$export$b9b3dfddab17db27 as isFocusVisible, $507fabe10e71c6fb$export$630ff653c5ada6a9 as getInteractionModality, $507fabe10e71c6fb$export$8397ddfc504fdb9a as setInteractionModality, $507fabe10e71c6fb$export$98e20ec92f614cfe as useInteractionModality, $507fabe10e71c6fb$export$ffd9e5021c1fb2d6 as useFocusVisible, $507fabe10e71c6fb$export$ec71b4b83ac08ec3 as useFocusVisibleListener};
237//# sourceMappingURL=useFocusVisible.module.js.map