@zag-js/focus-visible 0.70.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 CHANGED
@@ -1,47 +1,21 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
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 || !(0, import_dom_query.isMac)() && e.altKey || e.ctrlKey || e.key === "Control" || e.key === "Shift" || e.key === "Meta");
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" ? (0, import_dom_query.getWindow)(e?.target).HTMLInputElement : HTMLInputElement;
42
- const IHTMLTextAreaElement = typeof window !== "undefined" ? (0, import_dom_query.getWindow)(e?.target).HTMLTextAreaElement : HTMLTextAreaElement;
43
- const IHTMLElement = typeof window !== "undefined" ? (0, import_dom_query.getWindow)(e?.target).HTMLElement : HTMLElement;
44
- const IKeyboardEvent = typeof window !== "undefined" ? (0, import_dom_query.getWindow)(e?.target).KeyboardEvent : KeyboardEvent;
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 === (0, import_dom_query.getWindow)(e.target) || e.target === (0, import_dom_query.getDocument)(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((0, import_dom_query.getWindow)(root))) {
72
+ if (typeof window === "undefined" || listenerMap.get(domQuery.getWindow(root))) {
99
73
  return;
100
74
  }
101
- const win = (0, import_dom_query.getWindow)(root);
102
- const doc = (0, import_dom_query.getDocument)(root);
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 = (0, import_dom_query.getWindow)(root);
135
- const doc = (0, import_dom_query.getDocument)(root);
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
- // Annotate the CommonJS export names for ESM import in node:
193
- 0 && (module.exports = {
194
- getInteractionModality,
195
- isFocusVisible,
196
- listenerMap,
197
- setInteractionModality,
198
- trackFocusVisible,
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
- export {
164
- getInteractionModality,
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.70.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.70.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": "jest --config ../../../jest.config.js --rootDir tests",
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":[]}
@@ -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
- }