@mui/utils 6.1.5 → 6.1.6
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/CHANGELOG.md +54 -2
- package/esm/getReactNodeRef/getReactNodeRef.js +23 -0
- package/esm/getReactNodeRef/index.js +1 -0
- package/esm/index.js +2 -0
- package/esm/useIsFocusVisible/index.js +2 -0
- package/esm/useIsFocusVisible/useIsFocusVisible.js +163 -0
- package/getReactNodeRef/getReactNodeRef.d.ts +11 -0
- package/getReactNodeRef/getReactNodeRef.js +29 -0
- package/getReactNodeRef/index.d.ts +1 -0
- package/getReactNodeRef/index.js +13 -0
- package/getReactNodeRef/package.json +6 -0
- package/index.d.ts +2 -0
- package/index.js +17 -1
- package/modern/getReactNodeRef/getReactNodeRef.js +23 -0
- package/modern/getReactNodeRef/index.js +1 -0
- package/modern/index.js +3 -1
- package/modern/useIsFocusVisible/index.js +2 -0
- package/modern/useIsFocusVisible/useIsFocusVisible.js +163 -0
- package/package.json +3 -3
- package/useIsFocusVisible/index.d.ts +2 -0
- package/useIsFocusVisible/index.js +25 -0
- package/useIsFocusVisible/package.json +6 -0
- package/useIsFocusVisible/useIsFocusVisible.d.ts +9 -0
- package/useIsFocusVisible/useIsFocusVisible.js +170 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# [Versions](https://mui.com/versions/)
|
|
2
2
|
|
|
3
|
+
## 6.1.6
|
|
4
|
+
|
|
5
|
+
<!-- generated comparing v6.1.5..master -->
|
|
6
|
+
|
|
7
|
+
_Oct 30, 2024_
|
|
8
|
+
|
|
9
|
+
A big thanks to the 13 contributors who made this release possible.
|
|
10
|
+
|
|
11
|
+
### `@mui/material@6.1.6`
|
|
12
|
+
|
|
13
|
+
- [Autocomplete] Add missing `onMouseDown` type to AutocompleteRenderInputParams (#44183) @sai6855
|
|
14
|
+
- [Avatar] Fix AvatarGroup spacing (#44208) @aarongarciah
|
|
15
|
+
- [AvatarGroup] Fix spacing CSS variable (#44202) @navedqb
|
|
16
|
+
- [Divider] Fix CSS specificity order (#44204) @o-alexandrov
|
|
17
|
+
- [Slider] Fix value prop type warning (#44131) @joshkel
|
|
18
|
+
- Replace `useThemeProps` with `useDefaultProps` (#44193) @siriwatknp
|
|
19
|
+
|
|
20
|
+
### `@mui/material-nextjs@6.1.6`
|
|
21
|
+
|
|
22
|
+
- Support Next 15.0.0 (#42428) @nphmuller
|
|
23
|
+
|
|
24
|
+
### `@mui/lab@6.0.0-beta.14`
|
|
25
|
+
|
|
26
|
+
- [Tabs] Fix type of TabPanel component (#44207) @blackcow1987
|
|
27
|
+
|
|
28
|
+
### `@mui/codemod@6.1.6`
|
|
29
|
+
|
|
30
|
+
- Fix system props default import specifier (#44170) @siriwatknp
|
|
31
|
+
|
|
32
|
+
### `@mui/utils@6.1.6`
|
|
33
|
+
|
|
34
|
+
- Bring back useIsFocusVisible (#44256) @aarongarciah
|
|
35
|
+
- Bring back getReactNodeRef (#44248) @aarongarciah
|
|
36
|
+
|
|
37
|
+
### Docs
|
|
38
|
+
|
|
39
|
+
- [material-ui][Avatar] Add AvatarGroup spacing demo (#44209) @aarongarciah
|
|
40
|
+
- Fix a typo in CONTRIBUTING.md (#44200) @prakhargupta1
|
|
41
|
+
- Mark the Hidden component as deprecated in the sidenav (#44068) @jimmycallin
|
|
42
|
+
- Use () when referencing functions (#44184) @oliviertassinari
|
|
43
|
+
- Follow types description convention (#44187) @oliviertassinari
|
|
44
|
+
|
|
45
|
+
### Core
|
|
46
|
+
|
|
47
|
+
- Lock file maintenance (#43947)
|
|
48
|
+
- Run @mui/icon-material src:icons (#44097) @oliviertassinari
|
|
49
|
+
- [test][material-ui] Add tests for Pigment Grid and Stack (#44132) @DiegoAndai
|
|
50
|
+
- [test] Distinguish private with public tests API (#44188) @oliviertassinari
|
|
51
|
+
- [docs-infra] Add recursively the relative modules in the demos (#44150) @mnajdova
|
|
52
|
+
|
|
53
|
+
All contributors of this release in alphabetical order: @aarongarciah, @blackcow1987, @DiegoAndai, @jimmycallin, @joshkel, @mnajdova, @navedqb, @nphmuller, @o-alexandrov, @oliviertassinari, @prakhargupta1, @sai6855, @siriwatknp
|
|
54
|
+
|
|
3
55
|
## 6.1.5
|
|
4
56
|
|
|
5
57
|
<!-- generated comparing v6.1.4..master -->
|
|
@@ -358,7 +410,7 @@ A big thanks to the 11 contributors who made this release possible.
|
|
|
358
410
|
|
|
359
411
|
### `@mui/material@6.0.2`
|
|
360
412
|
|
|
361
|
-
- Fix `createTheme` with just color schemes (#43518) @siriwatknp
|
|
413
|
+
- Fix `createTheme()` with just color schemes (#43518) @siriwatknp
|
|
362
414
|
- [Menu,Popover] Fix Backdrop props descriptions (#43503) @Michael-Hutchinson
|
|
363
415
|
- [MenuList] Do not react to an event with modifier key pressed (#43505) @MateuszGroth
|
|
364
416
|
|
|
@@ -1100,7 +1152,7 @@ A big thanks to the 18 contributors who made this release possible.
|
|
|
1100
1152
|
|
|
1101
1153
|
### Docs
|
|
1102
1154
|
|
|
1103
|
-
- Add `theme.applyStyles` and migrate docs (#42498) @siriwatknp
|
|
1155
|
+
- Add `theme.applyStyles()` and migrate docs (#42498) @siriwatknp
|
|
1104
1156
|
- Fix dashboard template console error (#42594) @oliviertassinari
|
|
1105
1157
|
- Migrate system props to `sx` prop (#42475) @siriwatknp
|
|
1106
1158
|
- [material-ui]Fix duplicated sentence (#42521) @alexfauquette
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the ref of a React node handling differences between React 19 and older versions.
|
|
5
|
+
* It will return null if the node is not a valid React element.
|
|
6
|
+
*
|
|
7
|
+
* @param element React.ReactNode
|
|
8
|
+
* @returns React.Ref<any> | null
|
|
9
|
+
*
|
|
10
|
+
* @deprecated Use getReactElementRef instead
|
|
11
|
+
*/
|
|
12
|
+
export default function getReactNodeRef(element) {
|
|
13
|
+
if (!element || ! /*#__PURE__*/React.isValidElement(element)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 'ref' is passed as prop in React 19, whereas 'ref' is directly attached to children in older versions
|
|
18
|
+
return element.props.propertyIsEnumerable('ref') ? element.props.ref :
|
|
19
|
+
// @ts-expect-error element.ref is not included in the ReactElement type
|
|
20
|
+
// We cannot check for it, but isValidElement is true at this point
|
|
21
|
+
// https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/70189
|
|
22
|
+
element.ref;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./getReactNodeRef.js";
|
package/esm/index.js
CHANGED
|
@@ -27,6 +27,7 @@ export { default as unstable_useForkRef } from "./useForkRef/index.js";
|
|
|
27
27
|
export { default as unstable_useLazyRef } from "./useLazyRef/index.js";
|
|
28
28
|
export { default as unstable_useTimeout, Timeout as unstable_Timeout } from "./useTimeout/index.js";
|
|
29
29
|
export { default as unstable_useOnMount } from "./useOnMount/index.js";
|
|
30
|
+
export { default as unstable_useIsFocusVisible } from "./useIsFocusVisible/index.js";
|
|
30
31
|
export { default as unstable_isFocusVisible } from "./isFocusVisible/index.js";
|
|
31
32
|
export { default as unstable_getScrollbarSize } from "./getScrollbarSize/index.js";
|
|
32
33
|
export { default as usePreviousProps } from "./usePreviousProps/index.js";
|
|
@@ -44,5 +45,6 @@ export { default as clamp } from "./clamp/index.js";
|
|
|
44
45
|
export { default as unstable_useSlotProps } from "./useSlotProps/index.js";
|
|
45
46
|
export { default as unstable_resolveComponentProps } from "./resolveComponentProps/index.js";
|
|
46
47
|
export { default as unstable_extractEventHandlers } from "./extractEventHandlers/index.js";
|
|
48
|
+
export { default as unstable_getReactNodeRef } from "./getReactNodeRef/index.js";
|
|
47
49
|
export { default as unstable_getReactElementRef } from "./getReactElementRef/index.js";
|
|
48
50
|
export * from "./types.js";
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// based on https://github.com/WICG/focus-visible/blob/v4.1.5/src/focus-visible.js
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { Timeout } from "../useTimeout/useTimeout.js";
|
|
6
|
+
let hadKeyboardEvent = true;
|
|
7
|
+
let hadFocusVisibleRecently = false;
|
|
8
|
+
const hadFocusVisibleRecentlyTimeout = new Timeout();
|
|
9
|
+
const inputTypesWhitelist = {
|
|
10
|
+
text: true,
|
|
11
|
+
search: true,
|
|
12
|
+
url: true,
|
|
13
|
+
tel: true,
|
|
14
|
+
email: true,
|
|
15
|
+
password: true,
|
|
16
|
+
number: true,
|
|
17
|
+
date: true,
|
|
18
|
+
month: true,
|
|
19
|
+
week: true,
|
|
20
|
+
time: true,
|
|
21
|
+
datetime: true,
|
|
22
|
+
'datetime-local': true
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Computes whether the given element should automatically trigger the
|
|
27
|
+
* `focus-visible` class being added, i.e. whether it should always match
|
|
28
|
+
* `:focus-visible` when focused.
|
|
29
|
+
* @param {Element} node
|
|
30
|
+
* @returns {boolean}
|
|
31
|
+
*/
|
|
32
|
+
function focusTriggersKeyboardModality(node) {
|
|
33
|
+
const {
|
|
34
|
+
type,
|
|
35
|
+
tagName
|
|
36
|
+
} = node;
|
|
37
|
+
if (tagName === 'INPUT' && inputTypesWhitelist[type] && !node.readOnly) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (tagName === 'TEXTAREA' && !node.readOnly) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (node.isContentEditable) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Keep track of our keyboard modality state with `hadKeyboardEvent`.
|
|
51
|
+
* If the most recent user interaction was via the keyboard;
|
|
52
|
+
* and the key press did not include a meta, alt/option, or control key;
|
|
53
|
+
* then the modality is keyboard. Otherwise, the modality is not keyboard.
|
|
54
|
+
* @param {KeyboardEvent} event
|
|
55
|
+
*/
|
|
56
|
+
function handleKeyDown(event) {
|
|
57
|
+
if (event.metaKey || event.altKey || event.ctrlKey) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
hadKeyboardEvent = true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* If at any point a user clicks with a pointing device, ensure that we change
|
|
65
|
+
* the modality away from keyboard.
|
|
66
|
+
* This avoids the situation where a user presses a key on an already focused
|
|
67
|
+
* element, and then clicks on a different element, focusing it with a
|
|
68
|
+
* pointing device, while we still think we're in keyboard modality.
|
|
69
|
+
*/
|
|
70
|
+
function handlePointerDown() {
|
|
71
|
+
hadKeyboardEvent = false;
|
|
72
|
+
}
|
|
73
|
+
function handleVisibilityChange() {
|
|
74
|
+
if (this.visibilityState === 'hidden') {
|
|
75
|
+
// If the tab becomes active again, the browser will handle calling focus
|
|
76
|
+
// on the element (Safari actually calls it twice).
|
|
77
|
+
// If this tab change caused a blur on an element with focus-visible,
|
|
78
|
+
// re-apply the class when the user switches back to the tab.
|
|
79
|
+
if (hadFocusVisibleRecently) {
|
|
80
|
+
hadKeyboardEvent = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function prepare(doc) {
|
|
85
|
+
doc.addEventListener('keydown', handleKeyDown, true);
|
|
86
|
+
doc.addEventListener('mousedown', handlePointerDown, true);
|
|
87
|
+
doc.addEventListener('pointerdown', handlePointerDown, true);
|
|
88
|
+
doc.addEventListener('touchstart', handlePointerDown, true);
|
|
89
|
+
doc.addEventListener('visibilitychange', handleVisibilityChange, true);
|
|
90
|
+
}
|
|
91
|
+
export function teardown(doc) {
|
|
92
|
+
doc.removeEventListener('keydown', handleKeyDown, true);
|
|
93
|
+
doc.removeEventListener('mousedown', handlePointerDown, true);
|
|
94
|
+
doc.removeEventListener('pointerdown', handlePointerDown, true);
|
|
95
|
+
doc.removeEventListener('touchstart', handlePointerDown, true);
|
|
96
|
+
doc.removeEventListener('visibilitychange', handleVisibilityChange, true);
|
|
97
|
+
}
|
|
98
|
+
function isFocusVisible(event) {
|
|
99
|
+
const {
|
|
100
|
+
target
|
|
101
|
+
} = event;
|
|
102
|
+
try {
|
|
103
|
+
return target.matches(':focus-visible');
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// Browsers not implementing :focus-visible will throw a SyntaxError.
|
|
106
|
+
// We use our own heuristic for those browsers.
|
|
107
|
+
// Rethrow might be better if it's not the expected error but do we really
|
|
108
|
+
// want to crash if focus-visible malfunctioned?
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// No need for validFocusTarget check. The user does that by attaching it to
|
|
112
|
+
// focusable events only.
|
|
113
|
+
return hadKeyboardEvent || focusTriggersKeyboardModality(target);
|
|
114
|
+
}
|
|
115
|
+
export default function useIsFocusVisible() {
|
|
116
|
+
const ref = React.useCallback(node => {
|
|
117
|
+
if (node != null) {
|
|
118
|
+
prepare(node.ownerDocument);
|
|
119
|
+
}
|
|
120
|
+
}, []);
|
|
121
|
+
const isFocusVisibleRef = React.useRef(false);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Should be called if a blur event is fired
|
|
125
|
+
*/
|
|
126
|
+
function handleBlurVisible() {
|
|
127
|
+
// checking against potential state variable does not suffice if we focus and blur synchronously.
|
|
128
|
+
// React wouldn't have time to trigger a re-render so `focusVisible` would be stale.
|
|
129
|
+
// Ideally we would adjust `isFocusVisible(event)` to look at `relatedTarget` for blur events.
|
|
130
|
+
// This doesn't work in IE11 due to https://github.com/facebook/react/issues/3751
|
|
131
|
+
// TODO: check again if React releases their internal changes to focus event handling (https://github.com/facebook/react/pull/19186).
|
|
132
|
+
if (isFocusVisibleRef.current) {
|
|
133
|
+
// To detect a tab/window switch, we look for a blur event followed
|
|
134
|
+
// rapidly by a visibility change.
|
|
135
|
+
// If we don't see a visibility change within 100ms, it's probably a
|
|
136
|
+
// regular focus change.
|
|
137
|
+
hadFocusVisibleRecently = true;
|
|
138
|
+
hadFocusVisibleRecentlyTimeout.start(100, () => {
|
|
139
|
+
hadFocusVisibleRecently = false;
|
|
140
|
+
});
|
|
141
|
+
isFocusVisibleRef.current = false;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Should be called if a blur event is fired
|
|
149
|
+
*/
|
|
150
|
+
function handleFocusVisible(event) {
|
|
151
|
+
if (isFocusVisible(event)) {
|
|
152
|
+
isFocusVisibleRef.current = true;
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
isFocusVisibleRef,
|
|
159
|
+
onFocus: handleFocusVisible,
|
|
160
|
+
onBlur: handleBlurVisible,
|
|
161
|
+
ref
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the ref of a React node handling differences between React 19 and older versions.
|
|
4
|
+
* It will return null if the node is not a valid React element.
|
|
5
|
+
*
|
|
6
|
+
* @param element React.ReactNode
|
|
7
|
+
* @returns React.Ref<any> | null
|
|
8
|
+
*
|
|
9
|
+
* @deprecated Use getReactElementRef instead
|
|
10
|
+
*/
|
|
11
|
+
export default function getReactNodeRef(element: React.ReactNode): React.Ref<any> | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.default = getReactNodeRef;
|
|
8
|
+
var React = _interopRequireWildcard(require("react"));
|
|
9
|
+
/**
|
|
10
|
+
* Returns the ref of a React node handling differences between React 19 and older versions.
|
|
11
|
+
* It will return null if the node is not a valid React element.
|
|
12
|
+
*
|
|
13
|
+
* @param element React.ReactNode
|
|
14
|
+
* @returns React.Ref<any> | null
|
|
15
|
+
*
|
|
16
|
+
* @deprecated Use getReactElementRef instead
|
|
17
|
+
*/
|
|
18
|
+
function getReactNodeRef(element) {
|
|
19
|
+
if (!element || ! /*#__PURE__*/React.isValidElement(element)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 'ref' is passed as prop in React 19, whereas 'ref' is directly attached to children in older versions
|
|
24
|
+
return element.props.propertyIsEnumerable('ref') ? element.props.ref :
|
|
25
|
+
// @ts-expect-error element.ref is not included in the ReactElement type
|
|
26
|
+
// We cannot check for it, but isValidElement is true at this point
|
|
27
|
+
// https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/70189
|
|
28
|
+
element.ref;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './getReactNodeRef';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
Object.defineProperty(exports, "default", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function () {
|
|
10
|
+
return _getReactNodeRef.default;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
var _getReactNodeRef = _interopRequireDefault(require("./getReactNodeRef"));
|
package/index.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export { default as unstable_useForkRef } from './useForkRef';
|
|
|
27
27
|
export { default as unstable_useLazyRef } from './useLazyRef';
|
|
28
28
|
export { default as unstable_useTimeout, Timeout as unstable_Timeout } from './useTimeout';
|
|
29
29
|
export { default as unstable_useOnMount } from './useOnMount';
|
|
30
|
+
export { default as unstable_useIsFocusVisible } from './useIsFocusVisible';
|
|
30
31
|
export { default as unstable_isFocusVisible } from './isFocusVisible';
|
|
31
32
|
export { default as unstable_getScrollbarSize } from './getScrollbarSize';
|
|
32
33
|
export { default as usePreviousProps } from './usePreviousProps';
|
|
@@ -45,5 +46,6 @@ export { default as unstable_useSlotProps } from './useSlotProps';
|
|
|
45
46
|
export type { UseSlotPropsParameters, UseSlotPropsResult } from './useSlotProps';
|
|
46
47
|
export { default as unstable_resolveComponentProps } from './resolveComponentProps';
|
|
47
48
|
export { default as unstable_extractEventHandlers } from './extractEventHandlers';
|
|
49
|
+
export { default as unstable_getReactNodeRef } from './getReactNodeRef';
|
|
48
50
|
export { default as unstable_getReactElementRef } from './getReactElementRef';
|
|
49
51
|
export * from './types';
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @mui/utils v6.1.
|
|
2
|
+
* @mui/utils v6.1.6
|
|
3
3
|
*
|
|
4
4
|
* @license MIT
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
@@ -43,6 +43,7 @@ var _exportNames = {
|
|
|
43
43
|
unstable_useTimeout: true,
|
|
44
44
|
unstable_Timeout: true,
|
|
45
45
|
unstable_useOnMount: true,
|
|
46
|
+
unstable_useIsFocusVisible: true,
|
|
46
47
|
unstable_isFocusVisible: true,
|
|
47
48
|
unstable_getScrollbarSize: true,
|
|
48
49
|
usePreviousProps: true,
|
|
@@ -59,6 +60,7 @@ var _exportNames = {
|
|
|
59
60
|
unstable_useSlotProps: true,
|
|
60
61
|
unstable_resolveComponentProps: true,
|
|
61
62
|
unstable_extractEventHandlers: true,
|
|
63
|
+
unstable_getReactNodeRef: true,
|
|
62
64
|
unstable_getReactElementRef: true
|
|
63
65
|
};
|
|
64
66
|
Object.defineProperty(exports, "HTMLElementType", {
|
|
@@ -217,6 +219,12 @@ Object.defineProperty(exports, "unstable_getReactElementRef", {
|
|
|
217
219
|
return _getReactElementRef.default;
|
|
218
220
|
}
|
|
219
221
|
});
|
|
222
|
+
Object.defineProperty(exports, "unstable_getReactNodeRef", {
|
|
223
|
+
enumerable: true,
|
|
224
|
+
get: function () {
|
|
225
|
+
return _getReactNodeRef.default;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
220
228
|
Object.defineProperty(exports, "unstable_getScrollbarSize", {
|
|
221
229
|
enumerable: true,
|
|
222
230
|
get: function () {
|
|
@@ -307,6 +315,12 @@ Object.defineProperty(exports, "unstable_useId", {
|
|
|
307
315
|
return _useId.default;
|
|
308
316
|
}
|
|
309
317
|
});
|
|
318
|
+
Object.defineProperty(exports, "unstable_useIsFocusVisible", {
|
|
319
|
+
enumerable: true,
|
|
320
|
+
get: function () {
|
|
321
|
+
return _useIsFocusVisible.default;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
310
324
|
Object.defineProperty(exports, "unstable_useLazyRef", {
|
|
311
325
|
enumerable: true,
|
|
312
326
|
get: function () {
|
|
@@ -371,6 +385,7 @@ var _useForkRef = _interopRequireDefault(require("./useForkRef"));
|
|
|
371
385
|
var _useLazyRef = _interopRequireDefault(require("./useLazyRef"));
|
|
372
386
|
var _useTimeout = _interopRequireWildcard(require("./useTimeout"));
|
|
373
387
|
var _useOnMount = _interopRequireDefault(require("./useOnMount"));
|
|
388
|
+
var _useIsFocusVisible = _interopRequireDefault(require("./useIsFocusVisible"));
|
|
374
389
|
var _isFocusVisible = _interopRequireDefault(require("./isFocusVisible"));
|
|
375
390
|
var _getScrollbarSize = _interopRequireDefault(require("./getScrollbarSize"));
|
|
376
391
|
var _usePreviousProps = _interopRequireDefault(require("./usePreviousProps"));
|
|
@@ -397,6 +412,7 @@ var _clamp = _interopRequireDefault(require("./clamp"));
|
|
|
397
412
|
var _useSlotProps = _interopRequireDefault(require("./useSlotProps"));
|
|
398
413
|
var _resolveComponentProps = _interopRequireDefault(require("./resolveComponentProps"));
|
|
399
414
|
var _extractEventHandlers = _interopRequireDefault(require("./extractEventHandlers"));
|
|
415
|
+
var _getReactNodeRef = _interopRequireDefault(require("./getReactNodeRef"));
|
|
400
416
|
var _getReactElementRef = _interopRequireDefault(require("./getReactElementRef"));
|
|
401
417
|
var _types = require("./types");
|
|
402
418
|
Object.keys(_types).forEach(function (key) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the ref of a React node handling differences between React 19 and older versions.
|
|
5
|
+
* It will return null if the node is not a valid React element.
|
|
6
|
+
*
|
|
7
|
+
* @param element React.ReactNode
|
|
8
|
+
* @returns React.Ref<any> | null
|
|
9
|
+
*
|
|
10
|
+
* @deprecated Use getReactElementRef instead
|
|
11
|
+
*/
|
|
12
|
+
export default function getReactNodeRef(element) {
|
|
13
|
+
if (!element || ! /*#__PURE__*/React.isValidElement(element)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 'ref' is passed as prop in React 19, whereas 'ref' is directly attached to children in older versions
|
|
18
|
+
return element.props.propertyIsEnumerable('ref') ? element.props.ref :
|
|
19
|
+
// @ts-expect-error element.ref is not included in the ReactElement type
|
|
20
|
+
// We cannot check for it, but isValidElement is true at this point
|
|
21
|
+
// https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/70189
|
|
22
|
+
element.ref;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./getReactNodeRef.js";
|
package/modern/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @mui/utils v6.1.
|
|
2
|
+
* @mui/utils v6.1.6
|
|
3
3
|
*
|
|
4
4
|
* @license MIT
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
@@ -34,6 +34,7 @@ export { default as unstable_useForkRef } from "./useForkRef/index.js";
|
|
|
34
34
|
export { default as unstable_useLazyRef } from "./useLazyRef/index.js";
|
|
35
35
|
export { default as unstable_useTimeout, Timeout as unstable_Timeout } from "./useTimeout/index.js";
|
|
36
36
|
export { default as unstable_useOnMount } from "./useOnMount/index.js";
|
|
37
|
+
export { default as unstable_useIsFocusVisible } from "./useIsFocusVisible/index.js";
|
|
37
38
|
export { default as unstable_isFocusVisible } from "./isFocusVisible/index.js";
|
|
38
39
|
export { default as unstable_getScrollbarSize } from "./getScrollbarSize/index.js";
|
|
39
40
|
export { default as usePreviousProps } from "./usePreviousProps/index.js";
|
|
@@ -51,5 +52,6 @@ export { default as clamp } from "./clamp/index.js";
|
|
|
51
52
|
export { default as unstable_useSlotProps } from "./useSlotProps/index.js";
|
|
52
53
|
export { default as unstable_resolveComponentProps } from "./resolveComponentProps/index.js";
|
|
53
54
|
export { default as unstable_extractEventHandlers } from "./extractEventHandlers/index.js";
|
|
55
|
+
export { default as unstable_getReactNodeRef } from "./getReactNodeRef/index.js";
|
|
54
56
|
export { default as unstable_getReactElementRef } from "./getReactElementRef/index.js";
|
|
55
57
|
export * from "./types.js";
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// based on https://github.com/WICG/focus-visible/blob/v4.1.5/src/focus-visible.js
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { Timeout } from "../useTimeout/useTimeout.js";
|
|
6
|
+
let hadKeyboardEvent = true;
|
|
7
|
+
let hadFocusVisibleRecently = false;
|
|
8
|
+
const hadFocusVisibleRecentlyTimeout = new Timeout();
|
|
9
|
+
const inputTypesWhitelist = {
|
|
10
|
+
text: true,
|
|
11
|
+
search: true,
|
|
12
|
+
url: true,
|
|
13
|
+
tel: true,
|
|
14
|
+
email: true,
|
|
15
|
+
password: true,
|
|
16
|
+
number: true,
|
|
17
|
+
date: true,
|
|
18
|
+
month: true,
|
|
19
|
+
week: true,
|
|
20
|
+
time: true,
|
|
21
|
+
datetime: true,
|
|
22
|
+
'datetime-local': true
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Computes whether the given element should automatically trigger the
|
|
27
|
+
* `focus-visible` class being added, i.e. whether it should always match
|
|
28
|
+
* `:focus-visible` when focused.
|
|
29
|
+
* @param {Element} node
|
|
30
|
+
* @returns {boolean}
|
|
31
|
+
*/
|
|
32
|
+
function focusTriggersKeyboardModality(node) {
|
|
33
|
+
const {
|
|
34
|
+
type,
|
|
35
|
+
tagName
|
|
36
|
+
} = node;
|
|
37
|
+
if (tagName === 'INPUT' && inputTypesWhitelist[type] && !node.readOnly) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (tagName === 'TEXTAREA' && !node.readOnly) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (node.isContentEditable) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Keep track of our keyboard modality state with `hadKeyboardEvent`.
|
|
51
|
+
* If the most recent user interaction was via the keyboard;
|
|
52
|
+
* and the key press did not include a meta, alt/option, or control key;
|
|
53
|
+
* then the modality is keyboard. Otherwise, the modality is not keyboard.
|
|
54
|
+
* @param {KeyboardEvent} event
|
|
55
|
+
*/
|
|
56
|
+
function handleKeyDown(event) {
|
|
57
|
+
if (event.metaKey || event.altKey || event.ctrlKey) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
hadKeyboardEvent = true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* If at any point a user clicks with a pointing device, ensure that we change
|
|
65
|
+
* the modality away from keyboard.
|
|
66
|
+
* This avoids the situation where a user presses a key on an already focused
|
|
67
|
+
* element, and then clicks on a different element, focusing it with a
|
|
68
|
+
* pointing device, while we still think we're in keyboard modality.
|
|
69
|
+
*/
|
|
70
|
+
function handlePointerDown() {
|
|
71
|
+
hadKeyboardEvent = false;
|
|
72
|
+
}
|
|
73
|
+
function handleVisibilityChange() {
|
|
74
|
+
if (this.visibilityState === 'hidden') {
|
|
75
|
+
// If the tab becomes active again, the browser will handle calling focus
|
|
76
|
+
// on the element (Safari actually calls it twice).
|
|
77
|
+
// If this tab change caused a blur on an element with focus-visible,
|
|
78
|
+
// re-apply the class when the user switches back to the tab.
|
|
79
|
+
if (hadFocusVisibleRecently) {
|
|
80
|
+
hadKeyboardEvent = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function prepare(doc) {
|
|
85
|
+
doc.addEventListener('keydown', handleKeyDown, true);
|
|
86
|
+
doc.addEventListener('mousedown', handlePointerDown, true);
|
|
87
|
+
doc.addEventListener('pointerdown', handlePointerDown, true);
|
|
88
|
+
doc.addEventListener('touchstart', handlePointerDown, true);
|
|
89
|
+
doc.addEventListener('visibilitychange', handleVisibilityChange, true);
|
|
90
|
+
}
|
|
91
|
+
export function teardown(doc) {
|
|
92
|
+
doc.removeEventListener('keydown', handleKeyDown, true);
|
|
93
|
+
doc.removeEventListener('mousedown', handlePointerDown, true);
|
|
94
|
+
doc.removeEventListener('pointerdown', handlePointerDown, true);
|
|
95
|
+
doc.removeEventListener('touchstart', handlePointerDown, true);
|
|
96
|
+
doc.removeEventListener('visibilitychange', handleVisibilityChange, true);
|
|
97
|
+
}
|
|
98
|
+
function isFocusVisible(event) {
|
|
99
|
+
const {
|
|
100
|
+
target
|
|
101
|
+
} = event;
|
|
102
|
+
try {
|
|
103
|
+
return target.matches(':focus-visible');
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// Browsers not implementing :focus-visible will throw a SyntaxError.
|
|
106
|
+
// We use our own heuristic for those browsers.
|
|
107
|
+
// Rethrow might be better if it's not the expected error but do we really
|
|
108
|
+
// want to crash if focus-visible malfunctioned?
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// No need for validFocusTarget check. The user does that by attaching it to
|
|
112
|
+
// focusable events only.
|
|
113
|
+
return hadKeyboardEvent || focusTriggersKeyboardModality(target);
|
|
114
|
+
}
|
|
115
|
+
export default function useIsFocusVisible() {
|
|
116
|
+
const ref = React.useCallback(node => {
|
|
117
|
+
if (node != null) {
|
|
118
|
+
prepare(node.ownerDocument);
|
|
119
|
+
}
|
|
120
|
+
}, []);
|
|
121
|
+
const isFocusVisibleRef = React.useRef(false);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Should be called if a blur event is fired
|
|
125
|
+
*/
|
|
126
|
+
function handleBlurVisible() {
|
|
127
|
+
// checking against potential state variable does not suffice if we focus and blur synchronously.
|
|
128
|
+
// React wouldn't have time to trigger a re-render so `focusVisible` would be stale.
|
|
129
|
+
// Ideally we would adjust `isFocusVisible(event)` to look at `relatedTarget` for blur events.
|
|
130
|
+
// This doesn't work in IE11 due to https://github.com/facebook/react/issues/3751
|
|
131
|
+
// TODO: check again if React releases their internal changes to focus event handling (https://github.com/facebook/react/pull/19186).
|
|
132
|
+
if (isFocusVisibleRef.current) {
|
|
133
|
+
// To detect a tab/window switch, we look for a blur event followed
|
|
134
|
+
// rapidly by a visibility change.
|
|
135
|
+
// If we don't see a visibility change within 100ms, it's probably a
|
|
136
|
+
// regular focus change.
|
|
137
|
+
hadFocusVisibleRecently = true;
|
|
138
|
+
hadFocusVisibleRecentlyTimeout.start(100, () => {
|
|
139
|
+
hadFocusVisibleRecently = false;
|
|
140
|
+
});
|
|
141
|
+
isFocusVisibleRef.current = false;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Should be called if a blur event is fired
|
|
149
|
+
*/
|
|
150
|
+
function handleFocusVisible(event) {
|
|
151
|
+
if (isFocusVisible(event)) {
|
|
152
|
+
isFocusVisibleRef.current = true;
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
isFocusVisibleRef,
|
|
159
|
+
onFocus: handleFocusVisible,
|
|
160
|
+
onBlur: handleBlurVisible,
|
|
161
|
+
ref
|
|
162
|
+
};
|
|
163
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/utils",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "MUI Team",
|
|
6
6
|
"description": "Utility functions for React components.",
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"url": "https://opencollective.com/mui-org"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@babel/runtime": "^7.
|
|
29
|
+
"@babel/runtime": "^7.26.0",
|
|
30
30
|
"@types/prop-types": "^15.7.13",
|
|
31
31
|
"clsx": "^2.1.1",
|
|
32
32
|
"prop-types": "^15.8.1",
|
|
33
33
|
"react-is": "^18.3.1",
|
|
34
|
-
"@mui/types": "^7.2.
|
|
34
|
+
"@mui/types": "^7.2.19"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
var _exportNames = {};
|
|
8
|
+
Object.defineProperty(exports, "default", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
get: function () {
|
|
11
|
+
return _useIsFocusVisible.default;
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
var _useIsFocusVisible = _interopRequireWildcard(require("./useIsFocusVisible"));
|
|
15
|
+
Object.keys(_useIsFocusVisible).forEach(function (key) {
|
|
16
|
+
if (key === "default" || key === "__esModule") return;
|
|
17
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
18
|
+
if (key in exports && exports[key] === _useIsFocusVisible[key]) return;
|
|
19
|
+
Object.defineProperty(exports, key, {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () {
|
|
22
|
+
return _useIsFocusVisible[key];
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export declare function teardown(doc: Document): void;
|
|
3
|
+
export interface UseIsFocusVisibleResult {
|
|
4
|
+
isFocusVisibleRef: React.MutableRefObject<boolean>;
|
|
5
|
+
onBlur: (event: React.FocusEvent<any>) => void;
|
|
6
|
+
onFocus: (event: React.FocusEvent<any>) => void;
|
|
7
|
+
ref: React.RefCallback<Element>;
|
|
8
|
+
}
|
|
9
|
+
export default function useIsFocusVisible(): UseIsFocusVisibleResult;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
// based on https://github.com/WICG/focus-visible/blob/v4.1.5/src/focus-visible.js
|
|
5
|
+
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
exports.default = useIsFocusVisible;
|
|
10
|
+
exports.teardown = teardown;
|
|
11
|
+
var React = _interopRequireWildcard(require("react"));
|
|
12
|
+
var _useTimeout = require("../useTimeout/useTimeout");
|
|
13
|
+
let hadKeyboardEvent = true;
|
|
14
|
+
let hadFocusVisibleRecently = false;
|
|
15
|
+
const hadFocusVisibleRecentlyTimeout = new _useTimeout.Timeout();
|
|
16
|
+
const inputTypesWhitelist = {
|
|
17
|
+
text: true,
|
|
18
|
+
search: true,
|
|
19
|
+
url: true,
|
|
20
|
+
tel: true,
|
|
21
|
+
email: true,
|
|
22
|
+
password: true,
|
|
23
|
+
number: true,
|
|
24
|
+
date: true,
|
|
25
|
+
month: true,
|
|
26
|
+
week: true,
|
|
27
|
+
time: true,
|
|
28
|
+
datetime: true,
|
|
29
|
+
'datetime-local': true
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Computes whether the given element should automatically trigger the
|
|
34
|
+
* `focus-visible` class being added, i.e. whether it should always match
|
|
35
|
+
* `:focus-visible` when focused.
|
|
36
|
+
* @param {Element} node
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
function focusTriggersKeyboardModality(node) {
|
|
40
|
+
const {
|
|
41
|
+
type,
|
|
42
|
+
tagName
|
|
43
|
+
} = node;
|
|
44
|
+
if (tagName === 'INPUT' && inputTypesWhitelist[type] && !node.readOnly) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (tagName === 'TEXTAREA' && !node.readOnly) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (node.isContentEditable) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Keep track of our keyboard modality state with `hadKeyboardEvent`.
|
|
58
|
+
* If the most recent user interaction was via the keyboard;
|
|
59
|
+
* and the key press did not include a meta, alt/option, or control key;
|
|
60
|
+
* then the modality is keyboard. Otherwise, the modality is not keyboard.
|
|
61
|
+
* @param {KeyboardEvent} event
|
|
62
|
+
*/
|
|
63
|
+
function handleKeyDown(event) {
|
|
64
|
+
if (event.metaKey || event.altKey || event.ctrlKey) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
hadKeyboardEvent = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* If at any point a user clicks with a pointing device, ensure that we change
|
|
72
|
+
* the modality away from keyboard.
|
|
73
|
+
* This avoids the situation where a user presses a key on an already focused
|
|
74
|
+
* element, and then clicks on a different element, focusing it with a
|
|
75
|
+
* pointing device, while we still think we're in keyboard modality.
|
|
76
|
+
*/
|
|
77
|
+
function handlePointerDown() {
|
|
78
|
+
hadKeyboardEvent = false;
|
|
79
|
+
}
|
|
80
|
+
function handleVisibilityChange() {
|
|
81
|
+
if (this.visibilityState === 'hidden') {
|
|
82
|
+
// If the tab becomes active again, the browser will handle calling focus
|
|
83
|
+
// on the element (Safari actually calls it twice).
|
|
84
|
+
// If this tab change caused a blur on an element with focus-visible,
|
|
85
|
+
// re-apply the class when the user switches back to the tab.
|
|
86
|
+
if (hadFocusVisibleRecently) {
|
|
87
|
+
hadKeyboardEvent = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function prepare(doc) {
|
|
92
|
+
doc.addEventListener('keydown', handleKeyDown, true);
|
|
93
|
+
doc.addEventListener('mousedown', handlePointerDown, true);
|
|
94
|
+
doc.addEventListener('pointerdown', handlePointerDown, true);
|
|
95
|
+
doc.addEventListener('touchstart', handlePointerDown, true);
|
|
96
|
+
doc.addEventListener('visibilitychange', handleVisibilityChange, true);
|
|
97
|
+
}
|
|
98
|
+
function teardown(doc) {
|
|
99
|
+
doc.removeEventListener('keydown', handleKeyDown, true);
|
|
100
|
+
doc.removeEventListener('mousedown', handlePointerDown, true);
|
|
101
|
+
doc.removeEventListener('pointerdown', handlePointerDown, true);
|
|
102
|
+
doc.removeEventListener('touchstart', handlePointerDown, true);
|
|
103
|
+
doc.removeEventListener('visibilitychange', handleVisibilityChange, true);
|
|
104
|
+
}
|
|
105
|
+
function isFocusVisible(event) {
|
|
106
|
+
const {
|
|
107
|
+
target
|
|
108
|
+
} = event;
|
|
109
|
+
try {
|
|
110
|
+
return target.matches(':focus-visible');
|
|
111
|
+
} catch (error) {
|
|
112
|
+
// Browsers not implementing :focus-visible will throw a SyntaxError.
|
|
113
|
+
// We use our own heuristic for those browsers.
|
|
114
|
+
// Rethrow might be better if it's not the expected error but do we really
|
|
115
|
+
// want to crash if focus-visible malfunctioned?
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// No need for validFocusTarget check. The user does that by attaching it to
|
|
119
|
+
// focusable events only.
|
|
120
|
+
return hadKeyboardEvent || focusTriggersKeyboardModality(target);
|
|
121
|
+
}
|
|
122
|
+
function useIsFocusVisible() {
|
|
123
|
+
const ref = React.useCallback(node => {
|
|
124
|
+
if (node != null) {
|
|
125
|
+
prepare(node.ownerDocument);
|
|
126
|
+
}
|
|
127
|
+
}, []);
|
|
128
|
+
const isFocusVisibleRef = React.useRef(false);
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Should be called if a blur event is fired
|
|
132
|
+
*/
|
|
133
|
+
function handleBlurVisible() {
|
|
134
|
+
// checking against potential state variable does not suffice if we focus and blur synchronously.
|
|
135
|
+
// React wouldn't have time to trigger a re-render so `focusVisible` would be stale.
|
|
136
|
+
// Ideally we would adjust `isFocusVisible(event)` to look at `relatedTarget` for blur events.
|
|
137
|
+
// This doesn't work in IE11 due to https://github.com/facebook/react/issues/3751
|
|
138
|
+
// TODO: check again if React releases their internal changes to focus event handling (https://github.com/facebook/react/pull/19186).
|
|
139
|
+
if (isFocusVisibleRef.current) {
|
|
140
|
+
// To detect a tab/window switch, we look for a blur event followed
|
|
141
|
+
// rapidly by a visibility change.
|
|
142
|
+
// If we don't see a visibility change within 100ms, it's probably a
|
|
143
|
+
// regular focus change.
|
|
144
|
+
hadFocusVisibleRecently = true;
|
|
145
|
+
hadFocusVisibleRecentlyTimeout.start(100, () => {
|
|
146
|
+
hadFocusVisibleRecently = false;
|
|
147
|
+
});
|
|
148
|
+
isFocusVisibleRef.current = false;
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Should be called if a blur event is fired
|
|
156
|
+
*/
|
|
157
|
+
function handleFocusVisible(event) {
|
|
158
|
+
if (isFocusVisible(event)) {
|
|
159
|
+
isFocusVisibleRef.current = true;
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
isFocusVisibleRef,
|
|
166
|
+
onFocus: handleFocusVisible,
|
|
167
|
+
onBlur: handleBlurVisible,
|
|
168
|
+
ref
|
|
169
|
+
};
|
|
170
|
+
}
|