@zag-js/focus-visible 0.70.0 → 0.72.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 +22 -55
- package/dist/index.mjs +10 -20
- 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,49 +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
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
isTextInput = isTextInput || e?.target instanceof IHTMLInputElement && !nonTextInputTypes.has(e?.target?.type) || e?.target instanceof IHTMLTextAreaElement || e?.target instanceof IHTMLElement && e?.target.isContentEditable;
|
|
46
|
-
return !(isTextInput && modality === "keyboard" && e instanceof IKeyboardEvent && !Reflect.has(FOCUS_VISIBLE_INPUT_KEYS, e.key));
|
|
15
|
+
const target = e ? domQuery.getEventTarget(e) : null;
|
|
16
|
+
const win = domQuery.getWindow(target);
|
|
17
|
+
isTextInput = isTextInput || target instanceof win.HTMLInputElement && !nonTextInputTypes.has(target?.type) || target instanceof win.HTMLTextAreaElement || target instanceof win.HTMLElement && target.isContentEditable;
|
|
18
|
+
return !(isTextInput && modality === "keyboard" && e instanceof win.KeyboardEvent && !Reflect.has(FOCUS_VISIBLE_INPUT_KEYS, e.key));
|
|
47
19
|
}
|
|
48
20
|
var currentModality = null;
|
|
49
21
|
var changeHandlers = /* @__PURE__ */ new Set();
|
|
@@ -80,7 +52,8 @@ function handleClickEvent(e) {
|
|
|
80
52
|
}
|
|
81
53
|
}
|
|
82
54
|
function handleFocusEvent(e) {
|
|
83
|
-
|
|
55
|
+
const target = domQuery.getEventTarget(e);
|
|
56
|
+
if (target === domQuery.getWindow(target) || target === domQuery.getDocument(target)) {
|
|
84
57
|
return;
|
|
85
58
|
}
|
|
86
59
|
if (!hasEventBeforeFocus && !hasBlurredWindowRecently) {
|
|
@@ -95,11 +68,11 @@ function handleWindowBlur() {
|
|
|
95
68
|
hasBlurredWindowRecently = true;
|
|
96
69
|
}
|
|
97
70
|
function setupGlobalFocusEvents(root) {
|
|
98
|
-
if (typeof window === "undefined" || listenerMap.get(
|
|
71
|
+
if (typeof window === "undefined" || listenerMap.get(domQuery.getWindow(root))) {
|
|
99
72
|
return;
|
|
100
73
|
}
|
|
101
|
-
const win =
|
|
102
|
-
const doc =
|
|
74
|
+
const win = domQuery.getWindow(root);
|
|
75
|
+
const doc = domQuery.getDocument(root);
|
|
103
76
|
let focus = win.HTMLElement.prototype.focus;
|
|
104
77
|
win.HTMLElement.prototype.focus = function() {
|
|
105
78
|
currentModality = "virtual";
|
|
@@ -131,11 +104,8 @@ function setupGlobalFocusEvents(root) {
|
|
|
131
104
|
listenerMap.set(win, { focus });
|
|
132
105
|
}
|
|
133
106
|
var tearDownWindowFocusTracking = (root, loadListener) => {
|
|
134
|
-
const win =
|
|
135
|
-
const doc =
|
|
136
|
-
if (loadListener) {
|
|
137
|
-
doc.removeEventListener("DOMContentLoaded", loadListener);
|
|
138
|
-
}
|
|
107
|
+
const win = domQuery.getWindow(root);
|
|
108
|
+
const doc = domQuery.getDocument(root);
|
|
139
109
|
if (!listenerMap.has(win)) {
|
|
140
110
|
return;
|
|
141
111
|
}
|
|
@@ -189,13 +159,10 @@ function trackFocusVisible(props = {}) {
|
|
|
189
159
|
changeHandlers.delete(handler);
|
|
190
160
|
};
|
|
191
161
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
trackInteractionModality
|
|
200
|
-
});
|
|
201
|
-
//# sourceMappingURL=index.js.map
|
|
162
|
+
|
|
163
|
+
exports.getInteractionModality = getInteractionModality;
|
|
164
|
+
exports.isFocusVisible = isFocusVisible;
|
|
165
|
+
exports.listenerMap = listenerMap;
|
|
166
|
+
exports.setInteractionModality = setInteractionModality;
|
|
167
|
+
exports.trackFocusVisible = trackFocusVisible;
|
|
168
|
+
exports.trackInteractionModality = trackInteractionModality;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { getWindow, getDocument, getEventTarget, 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;
|
|
@@ -9,12 +10,10 @@ function isValidKey(e) {
|
|
|
9
10
|
}
|
|
10
11
|
var nonTextInputTypes = /* @__PURE__ */ new Set(["checkbox", "radio", "range", "color", "file", "image", "button", "submit", "reset"]);
|
|
11
12
|
function isKeyboardFocusEvent(isTextInput, modality, e) {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
isTextInput = isTextInput || e?.target instanceof IHTMLInputElement && !nonTextInputTypes.has(e?.target?.type) || e?.target instanceof IHTMLTextAreaElement || e?.target instanceof IHTMLElement && e?.target.isContentEditable;
|
|
17
|
-
return !(isTextInput && modality === "keyboard" && e instanceof IKeyboardEvent && !Reflect.has(FOCUS_VISIBLE_INPUT_KEYS, e.key));
|
|
13
|
+
const target = e ? getEventTarget(e) : null;
|
|
14
|
+
const win = getWindow(target);
|
|
15
|
+
isTextInput = isTextInput || target instanceof win.HTMLInputElement && !nonTextInputTypes.has(target?.type) || target instanceof win.HTMLTextAreaElement || target instanceof win.HTMLElement && target.isContentEditable;
|
|
16
|
+
return !(isTextInput && modality === "keyboard" && e instanceof win.KeyboardEvent && !Reflect.has(FOCUS_VISIBLE_INPUT_KEYS, e.key));
|
|
18
17
|
}
|
|
19
18
|
var currentModality = null;
|
|
20
19
|
var changeHandlers = /* @__PURE__ */ new Set();
|
|
@@ -51,7 +50,8 @@ function handleClickEvent(e) {
|
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
52
|
function handleFocusEvent(e) {
|
|
54
|
-
|
|
53
|
+
const target = getEventTarget(e);
|
|
54
|
+
if (target === getWindow(target) || target === getDocument(target)) {
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
if (!hasEventBeforeFocus && !hasBlurredWindowRecently) {
|
|
@@ -104,9 +104,6 @@ function setupGlobalFocusEvents(root) {
|
|
|
104
104
|
var tearDownWindowFocusTracking = (root, loadListener) => {
|
|
105
105
|
const win = getWindow(root);
|
|
106
106
|
const doc = getDocument(root);
|
|
107
|
-
if (loadListener) {
|
|
108
|
-
doc.removeEventListener("DOMContentLoaded", loadListener);
|
|
109
|
-
}
|
|
110
107
|
if (!listenerMap.has(win)) {
|
|
111
108
|
return;
|
|
112
109
|
}
|
|
@@ -160,12 +157,5 @@ function trackFocusVisible(props = {}) {
|
|
|
160
157
|
changeHandlers.delete(handler);
|
|
161
158
|
};
|
|
162
159
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
isFocusVisible,
|
|
166
|
-
listenerMap,
|
|
167
|
-
setInteractionModality,
|
|
168
|
-
trackFocusVisible,
|
|
169
|
-
trackInteractionModality
|
|
170
|
-
};
|
|
171
|
-
//# sourceMappingURL=index.mjs.map
|
|
160
|
+
|
|
161
|
+
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.72.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.72.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
|
-
}
|