@zag-js/focus-visible 0.69.0 → 0.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +21 -53
- package/dist/index.mjs +4 -13
- package/package.json +4 -5
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/src/index.ts +0 -297
package/dist/index.js
CHANGED
|
@@ -1,47 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var domQuery = require('@zag-js/dom-query');
|
|
19
4
|
|
|
20
5
|
// src/index.ts
|
|
21
|
-
var src_exports = {};
|
|
22
|
-
__export(src_exports, {
|
|
23
|
-
getInteractionModality: () => getInteractionModality,
|
|
24
|
-
isFocusVisible: () => isFocusVisible,
|
|
25
|
-
listenerMap: () => listenerMap,
|
|
26
|
-
setInteractionModality: () => setInteractionModality,
|
|
27
|
-
trackFocusVisible: () => trackFocusVisible,
|
|
28
|
-
trackInteractionModality: () => trackInteractionModality
|
|
29
|
-
});
|
|
30
|
-
module.exports = __toCommonJS(src_exports);
|
|
31
|
-
var import_dom_query = require("@zag-js/dom-query");
|
|
32
6
|
function isVirtualClick(event) {
|
|
33
7
|
if (event.mozInputSource === 0 && event.isTrusted) return true;
|
|
34
8
|
return event.detail === 0 && !event.pointerType;
|
|
35
9
|
}
|
|
36
10
|
function isValidKey(e) {
|
|
37
|
-
return !(e.metaKey || !
|
|
11
|
+
return !(e.metaKey || !domQuery.isMac() && e.altKey || e.ctrlKey || e.key === "Control" || e.key === "Shift" || e.key === "Meta");
|
|
38
12
|
}
|
|
39
13
|
var nonTextInputTypes = /* @__PURE__ */ new Set(["checkbox", "radio", "range", "color", "file", "image", "button", "submit", "reset"]);
|
|
40
14
|
function isKeyboardFocusEvent(isTextInput, modality, e) {
|
|
41
|
-
const IHTMLInputElement = typeof window !== "undefined" ?
|
|
42
|
-
const IHTMLTextAreaElement = typeof window !== "undefined" ?
|
|
43
|
-
const IHTMLElement = typeof window !== "undefined" ?
|
|
44
|
-
const IKeyboardEvent = typeof window !== "undefined" ?
|
|
15
|
+
const IHTMLInputElement = typeof window !== "undefined" ? domQuery.getWindow(e?.target).HTMLInputElement : HTMLInputElement;
|
|
16
|
+
const IHTMLTextAreaElement = typeof window !== "undefined" ? domQuery.getWindow(e?.target).HTMLTextAreaElement : HTMLTextAreaElement;
|
|
17
|
+
const IHTMLElement = typeof window !== "undefined" ? domQuery.getWindow(e?.target).HTMLElement : HTMLElement;
|
|
18
|
+
const IKeyboardEvent = typeof window !== "undefined" ? domQuery.getWindow(e?.target).KeyboardEvent : KeyboardEvent;
|
|
45
19
|
isTextInput = isTextInput || e?.target instanceof IHTMLInputElement && !nonTextInputTypes.has(e?.target?.type) || e?.target instanceof IHTMLTextAreaElement || e?.target instanceof IHTMLElement && e?.target.isContentEditable;
|
|
46
20
|
return !(isTextInput && modality === "keyboard" && e instanceof IKeyboardEvent && !Reflect.has(FOCUS_VISIBLE_INPUT_KEYS, e.key));
|
|
47
21
|
}
|
|
@@ -80,7 +54,7 @@ function handleClickEvent(e) {
|
|
|
80
54
|
}
|
|
81
55
|
}
|
|
82
56
|
function handleFocusEvent(e) {
|
|
83
|
-
if (e.target ===
|
|
57
|
+
if (e.target === domQuery.getWindow(e.target) || e.target === domQuery.getDocument(e.target)) {
|
|
84
58
|
return;
|
|
85
59
|
}
|
|
86
60
|
if (!hasEventBeforeFocus && !hasBlurredWindowRecently) {
|
|
@@ -95,11 +69,11 @@ function handleWindowBlur() {
|
|
|
95
69
|
hasBlurredWindowRecently = true;
|
|
96
70
|
}
|
|
97
71
|
function setupGlobalFocusEvents(root) {
|
|
98
|
-
if (typeof window === "undefined" || listenerMap.get(
|
|
72
|
+
if (typeof window === "undefined" || listenerMap.get(domQuery.getWindow(root))) {
|
|
99
73
|
return;
|
|
100
74
|
}
|
|
101
|
-
const win =
|
|
102
|
-
const doc =
|
|
75
|
+
const win = domQuery.getWindow(root);
|
|
76
|
+
const doc = domQuery.getDocument(root);
|
|
103
77
|
let focus = win.HTMLElement.prototype.focus;
|
|
104
78
|
win.HTMLElement.prototype.focus = function() {
|
|
105
79
|
currentModality = "virtual";
|
|
@@ -131,11 +105,8 @@ function setupGlobalFocusEvents(root) {
|
|
|
131
105
|
listenerMap.set(win, { focus });
|
|
132
106
|
}
|
|
133
107
|
var tearDownWindowFocusTracking = (root, loadListener) => {
|
|
134
|
-
const win =
|
|
135
|
-
const doc =
|
|
136
|
-
if (loadListener) {
|
|
137
|
-
doc.removeEventListener("DOMContentLoaded", loadListener);
|
|
138
|
-
}
|
|
108
|
+
const win = domQuery.getWindow(root);
|
|
109
|
+
const doc = domQuery.getDocument(root);
|
|
139
110
|
if (!listenerMap.has(win)) {
|
|
140
111
|
return;
|
|
141
112
|
}
|
|
@@ -189,13 +160,10 @@ function trackFocusVisible(props = {}) {
|
|
|
189
160
|
changeHandlers.delete(handler);
|
|
190
161
|
};
|
|
191
162
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
trackInteractionModality
|
|
200
|
-
});
|
|
201
|
-
//# sourceMappingURL=index.js.map
|
|
163
|
+
|
|
164
|
+
exports.getInteractionModality = getInteractionModality;
|
|
165
|
+
exports.isFocusVisible = isFocusVisible;
|
|
166
|
+
exports.listenerMap = listenerMap;
|
|
167
|
+
exports.setInteractionModality = setInteractionModality;
|
|
168
|
+
exports.trackFocusVisible = trackFocusVisible;
|
|
169
|
+
exports.trackInteractionModality = trackInteractionModality;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { getWindow, getDocument, isMac } from '@zag-js/dom-query';
|
|
2
|
+
|
|
1
3
|
// src/index.ts
|
|
2
|
-
import { getDocument, getWindow, isMac } from "@zag-js/dom-query";
|
|
3
4
|
function isVirtualClick(event) {
|
|
4
5
|
if (event.mozInputSource === 0 && event.isTrusted) return true;
|
|
5
6
|
return event.detail === 0 && !event.pointerType;
|
|
@@ -104,9 +105,6 @@ function setupGlobalFocusEvents(root) {
|
|
|
104
105
|
var tearDownWindowFocusTracking = (root, loadListener) => {
|
|
105
106
|
const win = getWindow(root);
|
|
106
107
|
const doc = getDocument(root);
|
|
107
|
-
if (loadListener) {
|
|
108
|
-
doc.removeEventListener("DOMContentLoaded", loadListener);
|
|
109
|
-
}
|
|
110
108
|
if (!listenerMap.has(win)) {
|
|
111
109
|
return;
|
|
112
110
|
}
|
|
@@ -160,12 +158,5 @@ function trackFocusVisible(props = {}) {
|
|
|
160
158
|
changeHandlers.delete(handler);
|
|
161
159
|
};
|
|
162
160
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
isFocusVisible,
|
|
166
|
-
listenerMap,
|
|
167
|
-
setInteractionModality,
|
|
168
|
-
trackFocusVisible,
|
|
169
|
-
trackInteractionModality
|
|
170
|
-
};
|
|
171
|
-
//# sourceMappingURL=index.mjs.map
|
|
161
|
+
|
|
162
|
+
export { getInteractionModality, isFocusVisible, listenerMap, setInteractionModality, trackFocusVisible, trackInteractionModality };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zag-js/focus-visible",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"description": "Focus visible polyfill utility based on WICG",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"js",
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
"repository": "https://github.com/chakra-ui/zag/tree/main/packages/utilities/focus-visible",
|
|
15
15
|
"sideEffects": false,
|
|
16
16
|
"files": [
|
|
17
|
-
"dist"
|
|
18
|
-
"src"
|
|
17
|
+
"dist"
|
|
19
18
|
],
|
|
20
19
|
"publishConfig": {
|
|
21
20
|
"access": "public"
|
|
@@ -26,7 +25,7 @@
|
|
|
26
25
|
"clean-package": "../../../clean-package.config.json",
|
|
27
26
|
"main": "dist/index.js",
|
|
28
27
|
"dependencies": {
|
|
29
|
-
"@zag-js/dom-query": "0.
|
|
28
|
+
"@zag-js/dom-query": "0.71.0"
|
|
30
29
|
},
|
|
31
30
|
"devDependencies": {
|
|
32
31
|
"clean-package": "2.2.0"
|
|
@@ -43,7 +42,7 @@
|
|
|
43
42
|
},
|
|
44
43
|
"scripts": {
|
|
45
44
|
"build": "tsup",
|
|
46
|
-
"test": "
|
|
45
|
+
"test": "vitest",
|
|
47
46
|
"lint": "eslint src",
|
|
48
47
|
"test-ci": "pnpm test --ci --runInBand -u",
|
|
49
48
|
"test-watch": "pnpm test --watchAll"
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Credit: Huge props to the team at Adobe for inspiring this implementation.\n * https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/interactions/src/useFocusVisible.ts\n */\nimport { getDocument, getWindow, isMac } from \"@zag-js/dom-query\"\n\nfunction isVirtualClick(event: MouseEvent | PointerEvent): boolean {\n if ((event as any).mozInputSource === 0 && event.isTrusted) return true\n return event.detail === 0 && !(event as PointerEvent).pointerType\n}\n\nfunction isValidKey(e: KeyboardEvent) {\n return !(\n e.metaKey ||\n (!isMac() && e.altKey) ||\n e.ctrlKey ||\n e.key === \"Control\" ||\n e.key === \"Shift\" ||\n e.key === \"Meta\"\n )\n}\n\nconst nonTextInputTypes = new Set([\"checkbox\", \"radio\", \"range\", \"color\", \"file\", \"image\", \"button\", \"submit\", \"reset\"])\n\nfunction isKeyboardFocusEvent(isTextInput: boolean, modality: Modality, e: HandlerEvent) {\n const IHTMLInputElement =\n typeof window !== \"undefined\" ? getWindow(e?.target as Element).HTMLInputElement : HTMLInputElement\n const IHTMLTextAreaElement =\n typeof window !== \"undefined\" ? getWindow(e?.target as Element).HTMLTextAreaElement : HTMLTextAreaElement\n const IHTMLElement = typeof window !== \"undefined\" ? getWindow(e?.target as Element).HTMLElement : HTMLElement\n const IKeyboardEvent = typeof window !== \"undefined\" ? getWindow(e?.target as Element).KeyboardEvent : KeyboardEvent\n\n isTextInput =\n isTextInput ||\n (e?.target instanceof IHTMLInputElement && !nonTextInputTypes.has(e?.target?.type)) ||\n e?.target instanceof IHTMLTextAreaElement ||\n (e?.target instanceof IHTMLElement && e?.target.isContentEditable)\n\n return !(\n isTextInput &&\n modality === \"keyboard\" &&\n e instanceof IKeyboardEvent &&\n !Reflect.has(FOCUS_VISIBLE_INPUT_KEYS, e.key)\n )\n}\n\n/////////////////////////////////////////////////////////////////////////////////////////////\n\nexport type Modality = \"keyboard\" | \"pointer\" | \"virtual\"\n\ntype RootNode = Document | ShadowRoot | Node\n\ntype HandlerEvent = PointerEvent | MouseEvent | KeyboardEvent | FocusEvent | null\n\ntype Handler = (modality: Modality, e: HandlerEvent) => void\n\n/////////////////////////////////////////////////////////////////////////////////////////////\n\nlet currentModality: Modality | null = null\n\nlet changeHandlers = new Set<Handler>()\n\ninterface GlobalListenerData {\n focus: VoidFunction\n}\n\nexport let listenerMap = new Map<Window, GlobalListenerData>()\n\nlet hasEventBeforeFocus = false\nlet hasBlurredWindowRecently = false\n\n// Only Tab or Esc keys will make focus visible on text input elements\nconst FOCUS_VISIBLE_INPUT_KEYS = {\n Tab: true,\n Escape: true,\n}\n\nfunction triggerChangeHandlers(modality: Modality, e: HandlerEvent) {\n for (let handler of changeHandlers) {\n handler(modality, e)\n }\n}\n\nfunction handleKeyboardEvent(e: KeyboardEvent) {\n hasEventBeforeFocus = true\n if (isValidKey(e)) {\n currentModality = \"keyboard\"\n triggerChangeHandlers(\"keyboard\", e)\n }\n}\n\nfunction handlePointerEvent(e: PointerEvent | MouseEvent) {\n currentModality = \"pointer\"\n if (e.type === \"mousedown\" || e.type === \"pointerdown\") {\n hasEventBeforeFocus = true\n triggerChangeHandlers(\"pointer\", e)\n }\n}\n\nfunction handleClickEvent(e: MouseEvent) {\n if (isVirtualClick(e)) {\n hasEventBeforeFocus = true\n currentModality = \"virtual\"\n }\n}\n\nfunction handleFocusEvent(e: FocusEvent) {\n // Firefox fires two extra focus events when the user first clicks into an iframe:\n // first on the window, then on the document. We ignore these events so they don't\n // cause keyboard focus rings to appear.\n if (e.target === getWindow(e.target as Element) || e.target === getDocument(e.target as Element)) {\n return\n }\n\n // If a focus event occurs without a preceding keyboard or pointer event, switch to virtual modality.\n // This occurs, for example, when navigating a form with the next/previous buttons on iOS.\n if (!hasEventBeforeFocus && !hasBlurredWindowRecently) {\n currentModality = \"virtual\"\n triggerChangeHandlers(\"virtual\", e)\n }\n\n hasEventBeforeFocus = false\n hasBlurredWindowRecently = false\n}\n\nfunction handleWindowBlur() {\n // When the window is blurred, reset state. This is necessary when tabbing out of the window,\n // for example, since a subsequent focus event won't be fired.\n hasEventBeforeFocus = false\n hasBlurredWindowRecently = true\n}\n\n/**\n * Setup global event listeners to control when keyboard focus style should be visible.\n */\nfunction setupGlobalFocusEvents(root?: RootNode) {\n if (typeof window === \"undefined\" || listenerMap.get(getWindow(root))) {\n return\n }\n\n const win = getWindow(root)\n const doc = getDocument(root)\n\n let focus = win.HTMLElement.prototype.focus\n win.HTMLElement.prototype.focus = function () {\n // For programmatic focus, we remove the focus visible state to prevent showing focus rings\n // When `options.focusVisible` is supported in most browsers, we can remove this\n // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#focusvisible\n currentModality = \"virtual\"\n triggerChangeHandlers(\"virtual\", null)\n\n hasEventBeforeFocus = true\n focus.apply(this, arguments as unknown as [options?: FocusOptions | undefined])\n }\n\n doc.addEventListener(\"keydown\", handleKeyboardEvent, true)\n doc.addEventListener(\"keyup\", handleKeyboardEvent, true)\n doc.addEventListener(\"click\", handleClickEvent, true)\n\n win.addEventListener(\"focus\", handleFocusEvent, true)\n win.addEventListener(\"blur\", handleWindowBlur, false)\n\n if (typeof win.PointerEvent !== \"undefined\") {\n doc.addEventListener(\"pointerdown\", handlePointerEvent, true)\n doc.addEventListener(\"pointermove\", handlePointerEvent, true)\n doc.addEventListener(\"pointerup\", handlePointerEvent, true)\n } else {\n doc.addEventListener(\"mousedown\", handlePointerEvent, true)\n doc.addEventListener(\"mousemove\", handlePointerEvent, true)\n doc.addEventListener(\"mouseup\", handlePointerEvent, true)\n }\n\n // Add unmount handler\n win.addEventListener(\n \"beforeunload\",\n () => {\n tearDownWindowFocusTracking(root)\n },\n { once: true },\n )\n\n listenerMap.set(win, { focus })\n}\n\nconst tearDownWindowFocusTracking = (root?: RootNode, loadListener?: () => void) => {\n const win = getWindow(root)\n const doc = getDocument(root)\n\n if (loadListener) {\n doc.removeEventListener(\"DOMContentLoaded\", loadListener)\n }\n\n if (!listenerMap.has(win)) {\n return\n }\n\n win.HTMLElement.prototype.focus = listenerMap.get(win)!.focus\n\n doc.removeEventListener(\"keydown\", handleKeyboardEvent, true)\n doc.removeEventListener(\"keyup\", handleKeyboardEvent, true)\n doc.removeEventListener(\"click\", handleClickEvent, true)\n win.removeEventListener(\"focus\", handleFocusEvent, true)\n win.removeEventListener(\"blur\", handleWindowBlur, false)\n\n if (typeof win.PointerEvent !== \"undefined\") {\n doc.removeEventListener(\"pointerdown\", handlePointerEvent, true)\n doc.removeEventListener(\"pointermove\", handlePointerEvent, true)\n doc.removeEventListener(\"pointerup\", handlePointerEvent, true)\n } else {\n doc.removeEventListener(\"mousedown\", handlePointerEvent, true)\n doc.removeEventListener(\"mousemove\", handlePointerEvent, true)\n doc.removeEventListener(\"mouseup\", handlePointerEvent, true)\n }\n\n listenerMap.delete(win)\n}\n\n/////////////////////////////////////////////////////////////////////////////////////////////\n\nexport function getInteractionModality(): Modality | null {\n return currentModality\n}\n\nexport function setInteractionModality(modality: Modality) {\n currentModality = modality\n triggerChangeHandlers(modality, null)\n}\n\nexport interface InteractionModalityChangeDetails {\n /** The modality of the interaction that caused the focus to be visible. */\n modality: Modality | null\n}\n\nexport interface InteractionModalityProps {\n /** The root element to track focus visibility for. */\n root?: RootNode\n /** Callback to be called when the interaction modality changes. */\n onChange: (details: InteractionModalityChangeDetails) => void\n}\n\nexport function trackInteractionModality(props: InteractionModalityProps): VoidFunction {\n const { onChange, root } = props\n\n setupGlobalFocusEvents(root)\n\n onChange({ modality: currentModality })\n\n const handler = () => onChange({ modality: currentModality })\n\n changeHandlers.add(handler)\n return () => {\n changeHandlers.delete(handler)\n }\n}\n\n/////////////////////////////////////////////////////////////////////////////////////////////\n\nexport function isFocusVisible(): boolean {\n return currentModality === \"keyboard\"\n}\n\nexport interface FocusVisibleChangeDetails {\n /** Whether keyboard focus is visible globally. */\n isFocusVisible: boolean\n /** The modality of the interaction that caused the focus to be visible. */\n modality: Modality | null\n}\n\nexport interface FocusVisibleProps {\n /** The root element to track focus visibility for. */\n root?: RootNode\n /** Whether the element is a text input. */\n isTextInput?: boolean\n /** Whether the element will be auto focused. */\n autoFocus?: boolean\n /** Callback to be called when the focus visibility changes. */\n onChange?: (details: FocusVisibleChangeDetails) => void\n}\n\nexport function trackFocusVisible(props: FocusVisibleProps = {}): VoidFunction {\n const { isTextInput, autoFocus, onChange, root } = props\n\n setupGlobalFocusEvents(root)\n\n onChange?.({ isFocusVisible: autoFocus || isFocusVisible(), modality: currentModality })\n\n const handler = (modality: Modality, e: HandlerEvent) => {\n if (!isKeyboardFocusEvent(!!isTextInput, modality, e)) return\n onChange?.({ isFocusVisible: isFocusVisible(), modality })\n }\n\n changeHandlers.add(handler)\n\n return () => {\n changeHandlers.delete(handler)\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Credit: Huge props to the team at Adobe for inspiring this implementation.\n * https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/interactions/src/useFocusVisible.ts\n */\nimport { getDocument, getWindow, isMac } from \"@zag-js/dom-query\"\n\nfunction isVirtualClick(event: MouseEvent | PointerEvent): boolean {\n if ((event as any).mozInputSource === 0 && event.isTrusted) return true\n return event.detail === 0 && !(event as PointerEvent).pointerType\n}\n\nfunction isValidKey(e: KeyboardEvent) {\n return !(\n e.metaKey ||\n (!isMac() && e.altKey) ||\n e.ctrlKey ||\n e.key === \"Control\" ||\n e.key === \"Shift\" ||\n e.key === \"Meta\"\n )\n}\n\nconst nonTextInputTypes = new Set([\"checkbox\", \"radio\", \"range\", \"color\", \"file\", \"image\", \"button\", \"submit\", \"reset\"])\n\nfunction isKeyboardFocusEvent(isTextInput: boolean, modality: Modality, e: HandlerEvent) {\n const IHTMLInputElement =\n typeof window !== \"undefined\" ? getWindow(e?.target as Element).HTMLInputElement : HTMLInputElement\n const IHTMLTextAreaElement =\n typeof window !== \"undefined\" ? getWindow(e?.target as Element).HTMLTextAreaElement : HTMLTextAreaElement\n const IHTMLElement = typeof window !== \"undefined\" ? getWindow(e?.target as Element).HTMLElement : HTMLElement\n const IKeyboardEvent = typeof window !== \"undefined\" ? getWindow(e?.target as Element).KeyboardEvent : KeyboardEvent\n\n isTextInput =\n isTextInput ||\n (e?.target instanceof IHTMLInputElement && !nonTextInputTypes.has(e?.target?.type)) ||\n e?.target instanceof IHTMLTextAreaElement ||\n (e?.target instanceof IHTMLElement && e?.target.isContentEditable)\n\n return !(\n isTextInput &&\n modality === \"keyboard\" &&\n e instanceof IKeyboardEvent &&\n !Reflect.has(FOCUS_VISIBLE_INPUT_KEYS, e.key)\n )\n}\n\n/////////////////////////////////////////////////////////////////////////////////////////////\n\nexport type Modality = \"keyboard\" | \"pointer\" | \"virtual\"\n\ntype RootNode = Document | ShadowRoot | Node\n\ntype HandlerEvent = PointerEvent | MouseEvent | KeyboardEvent | FocusEvent | null\n\ntype Handler = (modality: Modality, e: HandlerEvent) => void\n\n/////////////////////////////////////////////////////////////////////////////////////////////\n\nlet currentModality: Modality | null = null\n\nlet changeHandlers = new Set<Handler>()\n\ninterface GlobalListenerData {\n focus: VoidFunction\n}\n\nexport let listenerMap = new Map<Window, GlobalListenerData>()\n\nlet hasEventBeforeFocus = false\nlet hasBlurredWindowRecently = false\n\n// Only Tab or Esc keys will make focus visible on text input elements\nconst FOCUS_VISIBLE_INPUT_KEYS = {\n Tab: true,\n Escape: true,\n}\n\nfunction triggerChangeHandlers(modality: Modality, e: HandlerEvent) {\n for (let handler of changeHandlers) {\n handler(modality, e)\n }\n}\n\nfunction handleKeyboardEvent(e: KeyboardEvent) {\n hasEventBeforeFocus = true\n if (isValidKey(e)) {\n currentModality = \"keyboard\"\n triggerChangeHandlers(\"keyboard\", e)\n }\n}\n\nfunction handlePointerEvent(e: PointerEvent | MouseEvent) {\n currentModality = \"pointer\"\n if (e.type === \"mousedown\" || e.type === \"pointerdown\") {\n hasEventBeforeFocus = true\n triggerChangeHandlers(\"pointer\", e)\n }\n}\n\nfunction handleClickEvent(e: MouseEvent) {\n if (isVirtualClick(e)) {\n hasEventBeforeFocus = true\n currentModality = \"virtual\"\n }\n}\n\nfunction handleFocusEvent(e: FocusEvent) {\n // Firefox fires two extra focus events when the user first clicks into an iframe:\n // first on the window, then on the document. We ignore these events so they don't\n // cause keyboard focus rings to appear.\n if (e.target === getWindow(e.target as Element) || e.target === getDocument(e.target as Element)) {\n return\n }\n\n // If a focus event occurs without a preceding keyboard or pointer event, switch to virtual modality.\n // This occurs, for example, when navigating a form with the next/previous buttons on iOS.\n if (!hasEventBeforeFocus && !hasBlurredWindowRecently) {\n currentModality = \"virtual\"\n triggerChangeHandlers(\"virtual\", e)\n }\n\n hasEventBeforeFocus = false\n hasBlurredWindowRecently = false\n}\n\nfunction handleWindowBlur() {\n // When the window is blurred, reset state. This is necessary when tabbing out of the window,\n // for example, since a subsequent focus event won't be fired.\n hasEventBeforeFocus = false\n hasBlurredWindowRecently = true\n}\n\n/**\n * Setup global event listeners to control when keyboard focus style should be visible.\n */\nfunction setupGlobalFocusEvents(root?: RootNode) {\n if (typeof window === \"undefined\" || listenerMap.get(getWindow(root))) {\n return\n }\n\n const win = getWindow(root)\n const doc = getDocument(root)\n\n let focus = win.HTMLElement.prototype.focus\n win.HTMLElement.prototype.focus = function () {\n // For programmatic focus, we remove the focus visible state to prevent showing focus rings\n // When `options.focusVisible` is supported in most browsers, we can remove this\n // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#focusvisible\n currentModality = \"virtual\"\n triggerChangeHandlers(\"virtual\", null)\n\n hasEventBeforeFocus = true\n focus.apply(this, arguments as unknown as [options?: FocusOptions | undefined])\n }\n\n doc.addEventListener(\"keydown\", handleKeyboardEvent, true)\n doc.addEventListener(\"keyup\", handleKeyboardEvent, true)\n doc.addEventListener(\"click\", handleClickEvent, true)\n\n win.addEventListener(\"focus\", handleFocusEvent, true)\n win.addEventListener(\"blur\", handleWindowBlur, false)\n\n if (typeof win.PointerEvent !== \"undefined\") {\n doc.addEventListener(\"pointerdown\", handlePointerEvent, true)\n doc.addEventListener(\"pointermove\", handlePointerEvent, true)\n doc.addEventListener(\"pointerup\", handlePointerEvent, true)\n } else {\n doc.addEventListener(\"mousedown\", handlePointerEvent, true)\n doc.addEventListener(\"mousemove\", handlePointerEvent, true)\n doc.addEventListener(\"mouseup\", handlePointerEvent, true)\n }\n\n // Add unmount handler\n win.addEventListener(\n \"beforeunload\",\n () => {\n tearDownWindowFocusTracking(root)\n },\n { once: true },\n )\n\n listenerMap.set(win, { focus })\n}\n\nconst tearDownWindowFocusTracking = (root?: RootNode, loadListener?: () => void) => {\n const win = getWindow(root)\n const doc = getDocument(root)\n\n if (loadListener) {\n doc.removeEventListener(\"DOMContentLoaded\", loadListener)\n }\n\n if (!listenerMap.has(win)) {\n return\n }\n\n win.HTMLElement.prototype.focus = listenerMap.get(win)!.focus\n\n doc.removeEventListener(\"keydown\", handleKeyboardEvent, true)\n doc.removeEventListener(\"keyup\", handleKeyboardEvent, true)\n doc.removeEventListener(\"click\", handleClickEvent, true)\n win.removeEventListener(\"focus\", handleFocusEvent, true)\n win.removeEventListener(\"blur\", handleWindowBlur, false)\n\n if (typeof win.PointerEvent !== \"undefined\") {\n doc.removeEventListener(\"pointerdown\", handlePointerEvent, true)\n doc.removeEventListener(\"pointermove\", handlePointerEvent, true)\n doc.removeEventListener(\"pointerup\", handlePointerEvent, true)\n } else {\n doc.removeEventListener(\"mousedown\", handlePointerEvent, true)\n doc.removeEventListener(\"mousemove\", handlePointerEvent, true)\n doc.removeEventListener(\"mouseup\", handlePointerEvent, true)\n }\n\n listenerMap.delete(win)\n}\n\n/////////////////////////////////////////////////////////////////////////////////////////////\n\nexport function getInteractionModality(): Modality | null {\n return currentModality\n}\n\nexport function setInteractionModality(modality: Modality) {\n currentModality = modality\n triggerChangeHandlers(modality, null)\n}\n\nexport interface InteractionModalityChangeDetails {\n /** The modality of the interaction that caused the focus to be visible. */\n modality: Modality | null\n}\n\nexport interface InteractionModalityProps {\n /** The root element to track focus visibility for. */\n root?: RootNode\n /** Callback to be called when the interaction modality changes. */\n onChange: (details: InteractionModalityChangeDetails) => void\n}\n\nexport function trackInteractionModality(props: InteractionModalityProps): VoidFunction {\n const { onChange, root } = props\n\n setupGlobalFocusEvents(root)\n\n onChange({ modality: currentModality })\n\n const handler = () => onChange({ modality: currentModality })\n\n changeHandlers.add(handler)\n return () => {\n changeHandlers.delete(handler)\n }\n}\n\n/////////////////////////////////////////////////////////////////////////////////////////////\n\nexport function isFocusVisible(): boolean {\n return currentModality === \"keyboard\"\n}\n\nexport interface FocusVisibleChangeDetails {\n /** Whether keyboard focus is visible globally. */\n isFocusVisible: boolean\n /** The modality of the interaction that caused the focus to be visible. */\n modality: Modality | null\n}\n\nexport interface FocusVisibleProps {\n /** The root element to track focus visibility for. */\n root?: RootNode\n /** Whether the element is a text input. */\n isTextInput?: boolean\n /** Whether the element will be auto focused. */\n autoFocus?: boolean\n /** Callback to be called when the focus visibility changes. */\n onChange?: (details: FocusVisibleChangeDetails) => void\n}\n\nexport function trackFocusVisible(props: FocusVisibleProps = {}): VoidFunction {\n const { isTextInput, autoFocus, onChange, root } = props\n\n setupGlobalFocusEvents(root)\n\n onChange?.({ isFocusVisible: autoFocus || isFocusVisible(), modality: currentModality })\n\n const handler = (modality: Modality, e: HandlerEvent) => {\n if (!isKeyboardFocusEvent(!!isTextInput, modality, e)) return\n onChange?.({ isFocusVisible: isFocusVisible(), modality })\n }\n\n changeHandlers.add(handler)\n\n return () => {\n changeHandlers.delete(handler)\n }\n}\n"],"mappings":"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","names":[]}
|
package/src/index.ts
DELETED
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Credit: Huge props to the team at Adobe for inspiring this implementation.
|
|
3
|
-
* https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/interactions/src/useFocusVisible.ts
|
|
4
|
-
*/
|
|
5
|
-
import { getDocument, getWindow, isMac } from "@zag-js/dom-query"
|
|
6
|
-
|
|
7
|
-
function isVirtualClick(event: MouseEvent | PointerEvent): boolean {
|
|
8
|
-
if ((event as any).mozInputSource === 0 && event.isTrusted) return true
|
|
9
|
-
return event.detail === 0 && !(event as PointerEvent).pointerType
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function isValidKey(e: KeyboardEvent) {
|
|
13
|
-
return !(
|
|
14
|
-
e.metaKey ||
|
|
15
|
-
(!isMac() && e.altKey) ||
|
|
16
|
-
e.ctrlKey ||
|
|
17
|
-
e.key === "Control" ||
|
|
18
|
-
e.key === "Shift" ||
|
|
19
|
-
e.key === "Meta"
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const nonTextInputTypes = new Set(["checkbox", "radio", "range", "color", "file", "image", "button", "submit", "reset"])
|
|
24
|
-
|
|
25
|
-
function isKeyboardFocusEvent(isTextInput: boolean, modality: Modality, e: HandlerEvent) {
|
|
26
|
-
const IHTMLInputElement =
|
|
27
|
-
typeof window !== "undefined" ? getWindow(e?.target as Element).HTMLInputElement : HTMLInputElement
|
|
28
|
-
const IHTMLTextAreaElement =
|
|
29
|
-
typeof window !== "undefined" ? getWindow(e?.target as Element).HTMLTextAreaElement : HTMLTextAreaElement
|
|
30
|
-
const IHTMLElement = typeof window !== "undefined" ? getWindow(e?.target as Element).HTMLElement : HTMLElement
|
|
31
|
-
const IKeyboardEvent = typeof window !== "undefined" ? getWindow(e?.target as Element).KeyboardEvent : KeyboardEvent
|
|
32
|
-
|
|
33
|
-
isTextInput =
|
|
34
|
-
isTextInput ||
|
|
35
|
-
(e?.target instanceof IHTMLInputElement && !nonTextInputTypes.has(e?.target?.type)) ||
|
|
36
|
-
e?.target instanceof IHTMLTextAreaElement ||
|
|
37
|
-
(e?.target instanceof IHTMLElement && e?.target.isContentEditable)
|
|
38
|
-
|
|
39
|
-
return !(
|
|
40
|
-
isTextInput &&
|
|
41
|
-
modality === "keyboard" &&
|
|
42
|
-
e instanceof IKeyboardEvent &&
|
|
43
|
-
!Reflect.has(FOCUS_VISIBLE_INPUT_KEYS, e.key)
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
48
|
-
|
|
49
|
-
export type Modality = "keyboard" | "pointer" | "virtual"
|
|
50
|
-
|
|
51
|
-
type RootNode = Document | ShadowRoot | Node
|
|
52
|
-
|
|
53
|
-
type HandlerEvent = PointerEvent | MouseEvent | KeyboardEvent | FocusEvent | null
|
|
54
|
-
|
|
55
|
-
type Handler = (modality: Modality, e: HandlerEvent) => void
|
|
56
|
-
|
|
57
|
-
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
58
|
-
|
|
59
|
-
let currentModality: Modality | null = null
|
|
60
|
-
|
|
61
|
-
let changeHandlers = new Set<Handler>()
|
|
62
|
-
|
|
63
|
-
interface GlobalListenerData {
|
|
64
|
-
focus: VoidFunction
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export let listenerMap = new Map<Window, GlobalListenerData>()
|
|
68
|
-
|
|
69
|
-
let hasEventBeforeFocus = false
|
|
70
|
-
let hasBlurredWindowRecently = false
|
|
71
|
-
|
|
72
|
-
// Only Tab or Esc keys will make focus visible on text input elements
|
|
73
|
-
const FOCUS_VISIBLE_INPUT_KEYS = {
|
|
74
|
-
Tab: true,
|
|
75
|
-
Escape: true,
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function triggerChangeHandlers(modality: Modality, e: HandlerEvent) {
|
|
79
|
-
for (let handler of changeHandlers) {
|
|
80
|
-
handler(modality, e)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function handleKeyboardEvent(e: KeyboardEvent) {
|
|
85
|
-
hasEventBeforeFocus = true
|
|
86
|
-
if (isValidKey(e)) {
|
|
87
|
-
currentModality = "keyboard"
|
|
88
|
-
triggerChangeHandlers("keyboard", e)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function handlePointerEvent(e: PointerEvent | MouseEvent) {
|
|
93
|
-
currentModality = "pointer"
|
|
94
|
-
if (e.type === "mousedown" || e.type === "pointerdown") {
|
|
95
|
-
hasEventBeforeFocus = true
|
|
96
|
-
triggerChangeHandlers("pointer", e)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function handleClickEvent(e: MouseEvent) {
|
|
101
|
-
if (isVirtualClick(e)) {
|
|
102
|
-
hasEventBeforeFocus = true
|
|
103
|
-
currentModality = "virtual"
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function handleFocusEvent(e: FocusEvent) {
|
|
108
|
-
// Firefox fires two extra focus events when the user first clicks into an iframe:
|
|
109
|
-
// first on the window, then on the document. We ignore these events so they don't
|
|
110
|
-
// cause keyboard focus rings to appear.
|
|
111
|
-
if (e.target === getWindow(e.target as Element) || e.target === getDocument(e.target as Element)) {
|
|
112
|
-
return
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// If a focus event occurs without a preceding keyboard or pointer event, switch to virtual modality.
|
|
116
|
-
// This occurs, for example, when navigating a form with the next/previous buttons on iOS.
|
|
117
|
-
if (!hasEventBeforeFocus && !hasBlurredWindowRecently) {
|
|
118
|
-
currentModality = "virtual"
|
|
119
|
-
triggerChangeHandlers("virtual", e)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
hasEventBeforeFocus = false
|
|
123
|
-
hasBlurredWindowRecently = false
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function handleWindowBlur() {
|
|
127
|
-
// When the window is blurred, reset state. This is necessary when tabbing out of the window,
|
|
128
|
-
// for example, since a subsequent focus event won't be fired.
|
|
129
|
-
hasEventBeforeFocus = false
|
|
130
|
-
hasBlurredWindowRecently = true
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Setup global event listeners to control when keyboard focus style should be visible.
|
|
135
|
-
*/
|
|
136
|
-
function setupGlobalFocusEvents(root?: RootNode) {
|
|
137
|
-
if (typeof window === "undefined" || listenerMap.get(getWindow(root))) {
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const win = getWindow(root)
|
|
142
|
-
const doc = getDocument(root)
|
|
143
|
-
|
|
144
|
-
let focus = win.HTMLElement.prototype.focus
|
|
145
|
-
win.HTMLElement.prototype.focus = function () {
|
|
146
|
-
// For programmatic focus, we remove the focus visible state to prevent showing focus rings
|
|
147
|
-
// When `options.focusVisible` is supported in most browsers, we can remove this
|
|
148
|
-
// @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#focusvisible
|
|
149
|
-
currentModality = "virtual"
|
|
150
|
-
triggerChangeHandlers("virtual", null)
|
|
151
|
-
|
|
152
|
-
hasEventBeforeFocus = true
|
|
153
|
-
focus.apply(this, arguments as unknown as [options?: FocusOptions | undefined])
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
doc.addEventListener("keydown", handleKeyboardEvent, true)
|
|
157
|
-
doc.addEventListener("keyup", handleKeyboardEvent, true)
|
|
158
|
-
doc.addEventListener("click", handleClickEvent, true)
|
|
159
|
-
|
|
160
|
-
win.addEventListener("focus", handleFocusEvent, true)
|
|
161
|
-
win.addEventListener("blur", handleWindowBlur, false)
|
|
162
|
-
|
|
163
|
-
if (typeof win.PointerEvent !== "undefined") {
|
|
164
|
-
doc.addEventListener("pointerdown", handlePointerEvent, true)
|
|
165
|
-
doc.addEventListener("pointermove", handlePointerEvent, true)
|
|
166
|
-
doc.addEventListener("pointerup", handlePointerEvent, true)
|
|
167
|
-
} else {
|
|
168
|
-
doc.addEventListener("mousedown", handlePointerEvent, true)
|
|
169
|
-
doc.addEventListener("mousemove", handlePointerEvent, true)
|
|
170
|
-
doc.addEventListener("mouseup", handlePointerEvent, true)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Add unmount handler
|
|
174
|
-
win.addEventListener(
|
|
175
|
-
"beforeunload",
|
|
176
|
-
() => {
|
|
177
|
-
tearDownWindowFocusTracking(root)
|
|
178
|
-
},
|
|
179
|
-
{ once: true },
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
listenerMap.set(win, { focus })
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const tearDownWindowFocusTracking = (root?: RootNode, loadListener?: () => void) => {
|
|
186
|
-
const win = getWindow(root)
|
|
187
|
-
const doc = getDocument(root)
|
|
188
|
-
|
|
189
|
-
if (loadListener) {
|
|
190
|
-
doc.removeEventListener("DOMContentLoaded", loadListener)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (!listenerMap.has(win)) {
|
|
194
|
-
return
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
win.HTMLElement.prototype.focus = listenerMap.get(win)!.focus
|
|
198
|
-
|
|
199
|
-
doc.removeEventListener("keydown", handleKeyboardEvent, true)
|
|
200
|
-
doc.removeEventListener("keyup", handleKeyboardEvent, true)
|
|
201
|
-
doc.removeEventListener("click", handleClickEvent, true)
|
|
202
|
-
win.removeEventListener("focus", handleFocusEvent, true)
|
|
203
|
-
win.removeEventListener("blur", handleWindowBlur, false)
|
|
204
|
-
|
|
205
|
-
if (typeof win.PointerEvent !== "undefined") {
|
|
206
|
-
doc.removeEventListener("pointerdown", handlePointerEvent, true)
|
|
207
|
-
doc.removeEventListener("pointermove", handlePointerEvent, true)
|
|
208
|
-
doc.removeEventListener("pointerup", handlePointerEvent, true)
|
|
209
|
-
} else {
|
|
210
|
-
doc.removeEventListener("mousedown", handlePointerEvent, true)
|
|
211
|
-
doc.removeEventListener("mousemove", handlePointerEvent, true)
|
|
212
|
-
doc.removeEventListener("mouseup", handlePointerEvent, true)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
listenerMap.delete(win)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
219
|
-
|
|
220
|
-
export function getInteractionModality(): Modality | null {
|
|
221
|
-
return currentModality
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export function setInteractionModality(modality: Modality) {
|
|
225
|
-
currentModality = modality
|
|
226
|
-
triggerChangeHandlers(modality, null)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export interface InteractionModalityChangeDetails {
|
|
230
|
-
/** The modality of the interaction that caused the focus to be visible. */
|
|
231
|
-
modality: Modality | null
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
export interface InteractionModalityProps {
|
|
235
|
-
/** The root element to track focus visibility for. */
|
|
236
|
-
root?: RootNode
|
|
237
|
-
/** Callback to be called when the interaction modality changes. */
|
|
238
|
-
onChange: (details: InteractionModalityChangeDetails) => void
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export function trackInteractionModality(props: InteractionModalityProps): VoidFunction {
|
|
242
|
-
const { onChange, root } = props
|
|
243
|
-
|
|
244
|
-
setupGlobalFocusEvents(root)
|
|
245
|
-
|
|
246
|
-
onChange({ modality: currentModality })
|
|
247
|
-
|
|
248
|
-
const handler = () => onChange({ modality: currentModality })
|
|
249
|
-
|
|
250
|
-
changeHandlers.add(handler)
|
|
251
|
-
return () => {
|
|
252
|
-
changeHandlers.delete(handler)
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
257
|
-
|
|
258
|
-
export function isFocusVisible(): boolean {
|
|
259
|
-
return currentModality === "keyboard"
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export interface FocusVisibleChangeDetails {
|
|
263
|
-
/** Whether keyboard focus is visible globally. */
|
|
264
|
-
isFocusVisible: boolean
|
|
265
|
-
/** The modality of the interaction that caused the focus to be visible. */
|
|
266
|
-
modality: Modality | null
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export interface FocusVisibleProps {
|
|
270
|
-
/** The root element to track focus visibility for. */
|
|
271
|
-
root?: RootNode
|
|
272
|
-
/** Whether the element is a text input. */
|
|
273
|
-
isTextInput?: boolean
|
|
274
|
-
/** Whether the element will be auto focused. */
|
|
275
|
-
autoFocus?: boolean
|
|
276
|
-
/** Callback to be called when the focus visibility changes. */
|
|
277
|
-
onChange?: (details: FocusVisibleChangeDetails) => void
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
export function trackFocusVisible(props: FocusVisibleProps = {}): VoidFunction {
|
|
281
|
-
const { isTextInput, autoFocus, onChange, root } = props
|
|
282
|
-
|
|
283
|
-
setupGlobalFocusEvents(root)
|
|
284
|
-
|
|
285
|
-
onChange?.({ isFocusVisible: autoFocus || isFocusVisible(), modality: currentModality })
|
|
286
|
-
|
|
287
|
-
const handler = (modality: Modality, e: HandlerEvent) => {
|
|
288
|
-
if (!isKeyboardFocusEvent(!!isTextInput, modality, e)) return
|
|
289
|
-
onChange?.({ isFocusVisible: isFocusVisible(), modality })
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
changeHandlers.add(handler)
|
|
293
|
-
|
|
294
|
-
return () => {
|
|
295
|
-
changeHandlers.delete(handler)
|
|
296
|
-
}
|
|
297
|
-
}
|