@mui/utils 6.1.5 → 6.1.7
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 +111 -5
- 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,6 +1,112 @@
|
|
|
1
1
|
# [Versions](https://mui.com/versions/)
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## v6.1.7
|
|
4
|
+
|
|
5
|
+
<!-- generated comparing v6.1.6..master -->
|
|
6
|
+
|
|
7
|
+
_Nov 13, 2024_
|
|
8
|
+
|
|
9
|
+
A big thanks to the 13 contributors who made this release possible.
|
|
10
|
+
This release includes fixes as well as documentation improvements.
|
|
11
|
+
|
|
12
|
+
### `@mui/material@6.1.7`
|
|
13
|
+
|
|
14
|
+
- Fix default props theme scoping (#44340) @siriwatknp
|
|
15
|
+
- Support theme scoping in `useMediaQuery` (#44339) @siriwatknp
|
|
16
|
+
- [Grid] Fix regression spacing prop with string value (#44376) @siriwatknp
|
|
17
|
+
|
|
18
|
+
### `@mui/styled-engine-sc@6.1.7`
|
|
19
|
+
|
|
20
|
+
- Fix missing `@types/hoist-non-react-statics` causing `styled` returns any (#44397) @megos
|
|
21
|
+
|
|
22
|
+
### Docs
|
|
23
|
+
|
|
24
|
+
- Replace 'Experimental APIs - Toolpad' with 'Toolpad (Beta)' (#44388) @prakhargupta1
|
|
25
|
+
- Fix Pigment CSS install (#44353) @oliviertassinari
|
|
26
|
+
- Fix dashboard menu warning (#44317) @siriwatknp
|
|
27
|
+
- Add runtime theme section for Material Pigment CSS (#44137) @siriwatknp
|
|
28
|
+
- Add hash to `key` to remove noise from console (#44289) @sai6855
|
|
29
|
+
- Revise Example Projects and Related Projects pages (#44191) @samuelsycamore
|
|
30
|
+
- [material-ui] Fix typo in typography theme set up for templates (#44338) @navedqb
|
|
31
|
+
- [material-ui] Add StackBlitz/CodeSandbox buttons to template cards (#44253) @zanivan
|
|
32
|
+
- [material-ui] Fix Sign-in/Sign-up templates layout (#44281) @zanivan
|
|
33
|
+
- [material-ui] Remove noise in template (#44260) @oliviertassinari
|
|
34
|
+
- [material-ui][Rating] Add uncontrolled example to Basic Rating demo (#44386) @sai6855
|
|
35
|
+
- [material-ui][TextField] Replace InputProps with slotProps.input in demo (#44288) @sai6855
|
|
36
|
+
|
|
37
|
+
### Core
|
|
38
|
+
|
|
39
|
+
- [blog] Follow media asset guidelines (#44374) @oliviertassinari
|
|
40
|
+
- [code-infra] Changes for test util to work in `vitest` (#43625) @JCQuintas
|
|
41
|
+
- Remove old marked JS options (#44375) @ZeeshanTamboli
|
|
42
|
+
- Fix webpack capitalization (#44352) @oliviertassinari
|
|
43
|
+
- Fix Next.js link 404 (710cd95) @oliviertassinari
|
|
44
|
+
- Update Gold sponsoring backlinks (#44316) @oliviertassinari
|
|
45
|
+
- Fix tools-public.mui.com redirection (9196fa5) @oliviertassinari
|
|
46
|
+
- Remove blank AlertTitle test file (#44282) @ZeeshanTamboli
|
|
47
|
+
- [docs-infra] Fix ad in RTL (#44345) @oliviertassinari
|
|
48
|
+
- [docs-infra] Enforce punctuation on descriptions (#44292) @oliviertassinari
|
|
49
|
+
- [docs-infra] Add CodeSandbox and StackBlitz to vale vocab (6db477a) @oliviertassinari
|
|
50
|
+
- [docs-infra] Fix correct spelling of VS Code (#44277) @oliviertassinari
|
|
51
|
+
- [docs-infra] Add a `rawDescriptions` option (#44390) @vladmoroz
|
|
52
|
+
- [examples] Add missing `clsx` dependency (#43526) @Janpot
|
|
53
|
+
- [infra] Fix @renovate[bot] appearing in changelog (#44275) @mnajdova
|
|
54
|
+
|
|
55
|
+
All contributors of this release in alphabetical order: @Janpot, @JCQuintas, @megos, @mnajdova, @navedqb, @oliviertassinari, @prakhargupta1, @sai6855, @samuelsycamore, @siriwatknp, @vladmoroz, @zanivan, @ZeeshanTamboli
|
|
56
|
+
|
|
57
|
+
## v6.1.6
|
|
58
|
+
|
|
59
|
+
<!-- generated comparing v6.1.5..master -->
|
|
60
|
+
|
|
61
|
+
_Oct 30, 2024_
|
|
62
|
+
|
|
63
|
+
A big thanks to the 13 contributors who made this release possible.
|
|
64
|
+
|
|
65
|
+
### `@mui/material@6.1.6`
|
|
66
|
+
|
|
67
|
+
- [Autocomplete] Add missing `onMouseDown` type to AutocompleteRenderInputParams (#44183) @sai6855
|
|
68
|
+
- [Avatar] Fix AvatarGroup spacing (#44208) @aarongarciah
|
|
69
|
+
- [AvatarGroup] Fix spacing CSS variable (#44202) @navedqb
|
|
70
|
+
- [Divider] Fix CSS specificity order (#44204) @o-alexandrov
|
|
71
|
+
- [Slider] Fix value prop type warning (#44131) @joshkel
|
|
72
|
+
- Replace `useThemeProps` with `useDefaultProps` (#44193) @siriwatknp
|
|
73
|
+
|
|
74
|
+
### `@mui/material-nextjs@6.1.6`
|
|
75
|
+
|
|
76
|
+
- Support Next 15.0.0 (#42428) @nphmuller
|
|
77
|
+
|
|
78
|
+
### `@mui/lab@6.0.0-beta.14`
|
|
79
|
+
|
|
80
|
+
- [Tabs] Fix type of TabPanel component (#44207) @blackcow1987
|
|
81
|
+
|
|
82
|
+
### `@mui/codemod@6.1.6`
|
|
83
|
+
|
|
84
|
+
- Fix system props default import specifier (#44170) @siriwatknp
|
|
85
|
+
|
|
86
|
+
### `@mui/utils@6.1.6`
|
|
87
|
+
|
|
88
|
+
- Bring back useIsFocusVisible (#44256) @aarongarciah
|
|
89
|
+
- Bring back getReactNodeRef (#44248) @aarongarciah
|
|
90
|
+
|
|
91
|
+
### Docs
|
|
92
|
+
|
|
93
|
+
- [material-ui][Avatar] Add AvatarGroup spacing demo (#44209) @aarongarciah
|
|
94
|
+
- Fix a typo in CONTRIBUTING.md (#44200) @prakhargupta1
|
|
95
|
+
- Mark the Hidden component as deprecated in the sidenav (#44068) @jimmycallin
|
|
96
|
+
- Use () when referencing functions (#44184) @oliviertassinari
|
|
97
|
+
- Follow types description convention (#44187) @oliviertassinari
|
|
98
|
+
|
|
99
|
+
### Core
|
|
100
|
+
|
|
101
|
+
- Lock file maintenance (#43947)
|
|
102
|
+
- Run @mui/icon-material src:icons (#44097) @oliviertassinari
|
|
103
|
+
- [test][material-ui] Add tests for Pigment Grid and Stack (#44132) @DiegoAndai
|
|
104
|
+
- [test] Distinguish private with public tests API (#44188) @oliviertassinari
|
|
105
|
+
- [docs-infra] Add recursively the relative modules in the demos (#44150) @mnajdova
|
|
106
|
+
|
|
107
|
+
All contributors of this release in alphabetical order: @aarongarciah, @blackcow1987, @DiegoAndai, @jimmycallin, @joshkel, @mnajdova, @navedqb, @nphmuller, @o-alexandrov, @oliviertassinari, @prakhargupta1, @sai6855, @siriwatknp
|
|
108
|
+
|
|
109
|
+
## v6.1.5
|
|
4
110
|
|
|
5
111
|
<!-- generated comparing v6.1.4..master -->
|
|
6
112
|
|
|
@@ -32,7 +138,7 @@ A big thanks to the 9 contributors who made this release possible.
|
|
|
32
138
|
- [material-ui][TextField] Dynamically modify the eye password button aria-label (#44122) @ChinoUkaegbu
|
|
33
139
|
- [icons] Run pnpm docs:mdicons:synonyms (#44098) @oliviertassinari
|
|
34
140
|
- [joy-ui] Update Overview copy to match Readme (#44136) @samuelsycamore
|
|
35
|
-
- Add CodeSandbox/
|
|
141
|
+
- Add CodeSandbox/StackBlitz to the rest of the templates (#43708) @siriwatknp
|
|
36
142
|
- Update Figma link to fix 301 (a7b7d9c) @oliviertassinari
|
|
37
143
|
- Link Toolpad from Core repo (#44111) @prakhargupta1
|
|
38
144
|
- Remove HighlightedCode max-width (#43731) @Janpot
|
|
@@ -202,7 +308,7 @@ A big thanks to the 13 contributors who made this release possible.
|
|
|
202
308
|
- Uniformity in version range @oliviertassinari
|
|
203
309
|
- Replace `toBeAriaHidden` matcher with `toBeInaccessible` in tests (#43870) @ZeeshanTamboli
|
|
204
310
|
- [docs-infra] Strengthen CSP (#43711) @oliviertassinari
|
|
205
|
-
- [docs-infra] Open
|
|
311
|
+
- [docs-infra] Open CodeSandbox demo with fontsize=12 (#43860) @siriwatknp
|
|
206
312
|
- [icons] Reduce Material Icon page size (#43911) @oliviertassinari
|
|
207
313
|
- [test] Point Istanbul to correct URL (#43935) @sai6855
|
|
208
314
|
- [test] Sync React.version parse logic with codebase (#43820) @oliviertassinari
|
|
@@ -358,7 +464,7 @@ A big thanks to the 11 contributors who made this release possible.
|
|
|
358
464
|
|
|
359
465
|
### `@mui/material@6.0.2`
|
|
360
466
|
|
|
361
|
-
- Fix `createTheme` with just color schemes (#43518) @siriwatknp
|
|
467
|
+
- Fix `createTheme()` with just color schemes (#43518) @siriwatknp
|
|
362
468
|
- [Menu,Popover] Fix Backdrop props descriptions (#43503) @Michael-Hutchinson
|
|
363
469
|
- [MenuList] Do not react to an event with modifier key pressed (#43505) @MateuszGroth
|
|
364
470
|
|
|
@@ -1100,7 +1206,7 @@ A big thanks to the 18 contributors who made this release possible.
|
|
|
1100
1206
|
|
|
1101
1207
|
### Docs
|
|
1102
1208
|
|
|
1103
|
-
- Add `theme.applyStyles` and migrate docs (#42498) @siriwatknp
|
|
1209
|
+
- Add `theme.applyStyles()` and migrate docs (#42498) @siriwatknp
|
|
1104
1210
|
- Fix dashboard template console error (#42594) @oliviertassinari
|
|
1105
1211
|
- Migrate system props to `sx` prop (#42475) @siriwatknp
|
|
1106
1212
|
- [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.7
|
|
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.7
|
|
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.7",
|
|
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
|
+
}
|