@react-aria/toast 3.0.0-beta.12 → 3.0.0-beta.14
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/ar-AE.main.js +4 -1
- package/dist/ar-AE.main.js.map +1 -1
- package/dist/ar-AE.mjs +4 -1
- package/dist/ar-AE.module.js +4 -1
- package/dist/ar-AE.module.js.map +1 -1
- package/dist/bg-BG.main.js +4 -1
- package/dist/bg-BG.main.js.map +1 -1
- package/dist/bg-BG.mjs +4 -1
- package/dist/bg-BG.module.js +4 -1
- package/dist/bg-BG.module.js.map +1 -1
- package/dist/cs-CZ.main.js +4 -1
- package/dist/cs-CZ.main.js.map +1 -1
- package/dist/cs-CZ.mjs +4 -1
- package/dist/cs-CZ.module.js +4 -1
- package/dist/cs-CZ.module.js.map +1 -1
- package/dist/da-DK.main.js +4 -1
- package/dist/da-DK.main.js.map +1 -1
- package/dist/da-DK.mjs +4 -1
- package/dist/da-DK.module.js +4 -1
- package/dist/da-DK.module.js.map +1 -1
- package/dist/de-DE.main.js +4 -1
- package/dist/de-DE.main.js.map +1 -1
- package/dist/de-DE.mjs +4 -1
- package/dist/de-DE.module.js +4 -1
- package/dist/de-DE.module.js.map +1 -1
- package/dist/el-GR.main.js +4 -1
- package/dist/el-GR.main.js.map +1 -1
- package/dist/el-GR.mjs +4 -1
- package/dist/el-GR.module.js +4 -1
- package/dist/el-GR.module.js.map +1 -1
- package/dist/en-US.main.js +4 -1
- package/dist/en-US.main.js.map +1 -1
- package/dist/en-US.mjs +4 -1
- package/dist/en-US.module.js +4 -1
- package/dist/en-US.module.js.map +1 -1
- package/dist/es-ES.main.js +4 -1
- package/dist/es-ES.main.js.map +1 -1
- package/dist/es-ES.mjs +4 -1
- package/dist/es-ES.module.js +4 -1
- package/dist/es-ES.module.js.map +1 -1
- package/dist/et-EE.main.js +4 -1
- package/dist/et-EE.main.js.map +1 -1
- package/dist/et-EE.mjs +4 -1
- package/dist/et-EE.module.js +4 -1
- package/dist/et-EE.module.js.map +1 -1
- package/dist/fi-FI.main.js +4 -1
- package/dist/fi-FI.main.js.map +1 -1
- package/dist/fi-FI.mjs +4 -1
- package/dist/fi-FI.module.js +4 -1
- package/dist/fi-FI.module.js.map +1 -1
- package/dist/fr-FR.main.js +4 -1
- package/dist/fr-FR.main.js.map +1 -1
- package/dist/fr-FR.mjs +4 -1
- package/dist/fr-FR.module.js +4 -1
- package/dist/fr-FR.module.js.map +1 -1
- package/dist/he-IL.main.js +4 -1
- package/dist/he-IL.main.js.map +1 -1
- package/dist/he-IL.mjs +4 -1
- package/dist/he-IL.module.js +4 -1
- package/dist/he-IL.module.js.map +1 -1
- package/dist/hr-HR.main.js +4 -1
- package/dist/hr-HR.main.js.map +1 -1
- package/dist/hr-HR.mjs +4 -1
- package/dist/hr-HR.module.js +4 -1
- package/dist/hr-HR.module.js.map +1 -1
- package/dist/hu-HU.main.js +4 -1
- package/dist/hu-HU.main.js.map +1 -1
- package/dist/hu-HU.mjs +4 -1
- package/dist/hu-HU.module.js +4 -1
- package/dist/hu-HU.module.js.map +1 -1
- package/dist/it-IT.main.js +4 -1
- package/dist/it-IT.main.js.map +1 -1
- package/dist/it-IT.mjs +4 -1
- package/dist/it-IT.module.js +4 -1
- package/dist/it-IT.module.js.map +1 -1
- package/dist/ja-JP.main.js +4 -1
- package/dist/ja-JP.main.js.map +1 -1
- package/dist/ja-JP.mjs +4 -1
- package/dist/ja-JP.module.js +4 -1
- package/dist/ja-JP.module.js.map +1 -1
- package/dist/ko-KR.main.js +4 -1
- package/dist/ko-KR.main.js.map +1 -1
- package/dist/ko-KR.mjs +4 -1
- package/dist/ko-KR.module.js +4 -1
- package/dist/ko-KR.module.js.map +1 -1
- package/dist/lt-LT.main.js +4 -1
- package/dist/lt-LT.main.js.map +1 -1
- package/dist/lt-LT.mjs +4 -1
- package/dist/lt-LT.module.js +4 -1
- package/dist/lt-LT.module.js.map +1 -1
- package/dist/lv-LV.main.js +4 -1
- package/dist/lv-LV.main.js.map +1 -1
- package/dist/lv-LV.mjs +4 -1
- package/dist/lv-LV.module.js +4 -1
- package/dist/lv-LV.module.js.map +1 -1
- package/dist/nb-NO.main.js +4 -1
- package/dist/nb-NO.main.js.map +1 -1
- package/dist/nb-NO.mjs +4 -1
- package/dist/nb-NO.module.js +4 -1
- package/dist/nb-NO.module.js.map +1 -1
- package/dist/nl-NL.main.js +4 -1
- package/dist/nl-NL.main.js.map +1 -1
- package/dist/nl-NL.mjs +4 -1
- package/dist/nl-NL.module.js +4 -1
- package/dist/nl-NL.module.js.map +1 -1
- package/dist/pl-PL.main.js +6 -1
- package/dist/pl-PL.main.js.map +1 -1
- package/dist/pl-PL.mjs +6 -1
- package/dist/pl-PL.module.js +6 -1
- package/dist/pl-PL.module.js.map +1 -1
- package/dist/pt-BR.main.js +4 -1
- package/dist/pt-BR.main.js.map +1 -1
- package/dist/pt-BR.mjs +4 -1
- package/dist/pt-BR.module.js +4 -1
- package/dist/pt-BR.module.js.map +1 -1
- package/dist/pt-PT.main.js +4 -1
- package/dist/pt-PT.main.js.map +1 -1
- package/dist/pt-PT.mjs +4 -1
- package/dist/pt-PT.module.js +4 -1
- package/dist/pt-PT.module.js.map +1 -1
- package/dist/ro-RO.main.js +4 -1
- package/dist/ro-RO.main.js.map +1 -1
- package/dist/ro-RO.mjs +4 -1
- package/dist/ro-RO.module.js +4 -1
- package/dist/ro-RO.module.js.map +1 -1
- package/dist/ru-RU.main.js +4 -1
- package/dist/ru-RU.main.js.map +1 -1
- package/dist/ru-RU.mjs +4 -1
- package/dist/ru-RU.module.js +4 -1
- package/dist/ru-RU.module.js.map +1 -1
- package/dist/sk-SK.main.js +5 -1
- package/dist/sk-SK.main.js.map +1 -1
- package/dist/sk-SK.mjs +5 -1
- package/dist/sk-SK.module.js +5 -1
- package/dist/sk-SK.module.js.map +1 -1
- package/dist/sl-SI.main.js +6 -1
- package/dist/sl-SI.main.js.map +1 -1
- package/dist/sl-SI.mjs +6 -1
- package/dist/sl-SI.module.js +6 -1
- package/dist/sl-SI.module.js.map +1 -1
- package/dist/sr-SP.main.js +4 -1
- package/dist/sr-SP.main.js.map +1 -1
- package/dist/sr-SP.mjs +4 -1
- package/dist/sr-SP.module.js +4 -1
- package/dist/sr-SP.module.js.map +1 -1
- package/dist/sv-SE.main.js +4 -1
- package/dist/sv-SE.main.js.map +1 -1
- package/dist/sv-SE.mjs +4 -1
- package/dist/sv-SE.module.js +4 -1
- package/dist/sv-SE.module.js.map +1 -1
- package/dist/tr-TR.main.js +4 -1
- package/dist/tr-TR.main.js.map +1 -1
- package/dist/tr-TR.mjs +4 -1
- package/dist/tr-TR.module.js +4 -1
- package/dist/tr-TR.module.js.map +1 -1
- package/dist/types.d.ts +6 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/uk-UA.main.js +4 -1
- package/dist/uk-UA.main.js.map +1 -1
- package/dist/uk-UA.mjs +4 -1
- package/dist/uk-UA.module.js +4 -1
- package/dist/uk-UA.module.js.map +1 -1
- package/dist/useToast.main.js +14 -22
- package/dist/useToast.main.js.map +1 -1
- package/dist/useToast.mjs +16 -24
- package/dist/useToast.module.js +16 -24
- package/dist/useToast.module.js.map +1 -1
- package/dist/useToastRegion.main.js +85 -6
- package/dist/useToastRegion.main.js.map +1 -1
- package/dist/useToastRegion.mjs +86 -7
- package/dist/useToastRegion.module.js +86 -7
- package/dist/useToastRegion.module.js.map +1 -1
- package/dist/zh-CN.main.js +4 -1
- package/dist/zh-CN.main.js.map +1 -1
- package/dist/zh-CN.mjs +4 -1
- package/dist/zh-CN.module.js +4 -1
- package/dist/zh-CN.module.js.map +1 -1
- package/dist/zh-TW.main.js +4 -1
- package/dist/zh-TW.main.js.map +1 -1
- package/dist/zh-TW.mjs +4 -1
- package/dist/zh-TW.module.js +4 -1
- package/dist/zh-TW.module.js.map +1 -1
- package/package.json +10 -10
- package/src/useToast.ts +24 -30
- package/src/useToastRegion.ts +98 -10
package/dist/zh-TW.module.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
var $52e8b1b00094e5a3$exports = {};
|
|
2
2
|
$52e8b1b00094e5a3$exports = {
|
|
3
3
|
"close": `\u{95DC}\u{9589}`,
|
|
4
|
-
"notifications":
|
|
4
|
+
"notifications": (args, formatter)=>`${formatter.plural(args.count, {
|
|
5
|
+
one: ()=>`${formatter.number(args.count)} \u{500B}\u{901A}\u{77E5}`,
|
|
6
|
+
other: ()=>`${formatter.number(args.count)} \u{500B}\u{901A}\u{77E5}`
|
|
7
|
+
})}\u{3002}`
|
|
5
8
|
};
|
|
6
9
|
|
|
7
10
|
|
package/dist/zh-TW.module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":";AAAA,4BAAiB;IAAG,SAAS,CAAC,gBAAE,CAAC;IAC/B,iBAAiB,CAAC,
|
|
1
|
+
{"mappings":";AAAA,4BAAiB;IAAG,SAAS,CAAC,gBAAE,CAAC;IAC/B,iBAAiB,CAAC,MAAM,YAAc,CAAC,EAAE,UAAU,MAAM,CAAC,KAAK,KAAK,EAAE;YAAC,KAAK,IAAM,CAAC,EAAE,UAAU,MAAM,CAAC,KAAK,KAAK,EAAE,yBAAI,CAAC;YAAE,OAAO,IAAM,CAAC,EAAE,UAAU,MAAM,CAAC,KAAK,KAAK,EAAE,yBAAI,CAAC;QAAA,GAAG,QAAC,CAAC;AAClL","sources":["packages/@react-aria/toast/intl/zh-TW.json"],"sourcesContent":["{\n \"close\": \"關閉\",\n \"notifications\": \"{count, plural, one {# 個通知} other {# 個通知}}。\"\n}\n"],"names":[],"version":3,"file":"zh-TW.module.js.map"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/toast",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.14",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -22,20 +22,20 @@
|
|
|
22
22
|
"url": "https://github.com/adobe/react-spectrum"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@react-aria/i18n": "^3.
|
|
26
|
-
"@react-aria/interactions": "^3.
|
|
27
|
-
"@react-aria/landmark": "3.0.0-beta.
|
|
28
|
-
"@react-aria/utils": "^3.
|
|
29
|
-
"@react-stately/toast": "3.0.0-beta.
|
|
30
|
-
"@react-types/button": "^3.9.
|
|
31
|
-
"@react-types/shared": "^3.
|
|
25
|
+
"@react-aria/i18n": "^3.12.1",
|
|
26
|
+
"@react-aria/interactions": "^3.22.1",
|
|
27
|
+
"@react-aria/landmark": "3.0.0-beta.14",
|
|
28
|
+
"@react-aria/utils": "^3.25.1",
|
|
29
|
+
"@react-stately/toast": "3.0.0-beta.5",
|
|
30
|
+
"@react-types/button": "^3.9.6",
|
|
31
|
+
"@react-types/shared": "^3.24.1",
|
|
32
32
|
"@swc/helpers": "^0.5.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
|
35
|
+
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "f0aa6aacee60af265dc8994b9274ccf072a850aa"
|
|
41
41
|
}
|
package/src/useToast.ts
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {AriaButtonProps} from '@react-types/button';
|
|
14
|
-
import {AriaLabelingProps, DOMAttributes, FocusableElement} from '@react-types/shared';
|
|
14
|
+
import {AriaLabelingProps, DOMAttributes, FocusableElement, RefObject} from '@react-types/shared';
|
|
15
15
|
// @ts-ignore
|
|
16
16
|
import intlMessages from '../intl/*.json';
|
|
17
17
|
import {QueuedToast, ToastState} from '@react-stately/toast';
|
|
18
|
-
import
|
|
19
|
-
import {useId,
|
|
18
|
+
import React, {useEffect} from 'react';
|
|
19
|
+
import {useId, useSlotId} from '@react-aria/utils';
|
|
20
20
|
import {useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
21
21
|
|
|
22
22
|
export interface AriaToastProps<T> extends AriaLabelingProps {
|
|
@@ -25,8 +25,10 @@ export interface AriaToastProps<T> extends AriaLabelingProps {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface ToastAria {
|
|
28
|
-
/** Props for the toast container element. */
|
|
28
|
+
/** Props for the toast container, non-modal dialog element. */
|
|
29
29
|
toastProps: DOMAttributes,
|
|
30
|
+
/** Props for the toast content alert message. */
|
|
31
|
+
contentProps: DOMAttributes,
|
|
30
32
|
/** Props for the toast title element. */
|
|
31
33
|
titleProps: DOMAttributes,
|
|
32
34
|
/** Props for the toast description element, if any. */
|
|
@@ -39,7 +41,8 @@ export interface ToastAria {
|
|
|
39
41
|
* Provides the behavior and accessibility implementation for a toast component.
|
|
40
42
|
* Toasts display brief, temporary notifications of actions, errors, or other events in an application.
|
|
41
43
|
*/
|
|
42
|
-
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
45
|
+
export function useToast<T>(props: AriaToastProps<T>, state: ToastState<T>, ref: RefObject<FocusableElement | null>): ToastAria {
|
|
43
46
|
let {
|
|
44
47
|
key,
|
|
45
48
|
timer,
|
|
@@ -58,30 +61,12 @@ export function useToast<T>(props: AriaToastProps<T>, state: ToastState<T>, ref:
|
|
|
58
61
|
};
|
|
59
62
|
}, [timer, timeout]);
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
// If there are no more toasts, the container will be unmounted
|
|
63
|
-
// and will restore focus to wherever focus was before the user
|
|
64
|
-
// focused the toast region.
|
|
65
|
-
let focusOnUnmount = useRef(null);
|
|
66
|
-
useLayoutEffect(() => {
|
|
67
|
-
let container = ref.current.closest('[role=region]') as HTMLElement;
|
|
68
|
-
return () => {
|
|
69
|
-
if (container && container.contains(document.activeElement)) {
|
|
70
|
-
// Focus must be delayed for focus ring to appear, but we can't wait
|
|
71
|
-
// until useEffect cleanup to check if focus was inside the container.
|
|
72
|
-
focusOnUnmount.current = container;
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
}, [ref]);
|
|
76
|
-
|
|
77
|
-
// eslint-disable-next-line
|
|
64
|
+
let [isEntered, setIsEntered] = React.useState(false);
|
|
78
65
|
useEffect(() => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
};
|
|
84
|
-
}, [ref]);
|
|
66
|
+
if (animation === 'entering' || animation === 'queued') {
|
|
67
|
+
setIsEntered(true);
|
|
68
|
+
}
|
|
69
|
+
}, [animation]);
|
|
85
70
|
|
|
86
71
|
let titleId = useId();
|
|
87
72
|
let descriptionId = useSlotId();
|
|
@@ -89,13 +74,22 @@ export function useToast<T>(props: AriaToastProps<T>, state: ToastState<T>, ref:
|
|
|
89
74
|
|
|
90
75
|
return {
|
|
91
76
|
toastProps: {
|
|
92
|
-
role: '
|
|
77
|
+
role: 'alertdialog',
|
|
78
|
+
'aria-modal': 'false',
|
|
93
79
|
'aria-label': props['aria-label'],
|
|
94
80
|
'aria-labelledby': props['aria-labelledby'] || titleId,
|
|
95
81
|
'aria-describedby': props['aria-describedby'] || descriptionId,
|
|
96
82
|
'aria-details': props['aria-details'],
|
|
97
83
|
// Hide toasts that are animating out so VoiceOver doesn't announce them.
|
|
98
|
-
'aria-hidden': animation === 'exiting' ? 'true' : undefined
|
|
84
|
+
'aria-hidden': animation === 'exiting' ? 'true' : undefined,
|
|
85
|
+
tabIndex: 0
|
|
86
|
+
},
|
|
87
|
+
contentProps: {
|
|
88
|
+
role: 'alert',
|
|
89
|
+
'aria-atomic': 'true',
|
|
90
|
+
style: {
|
|
91
|
+
visibility: isEntered || animation === null ? 'visible' : 'hidden'
|
|
92
|
+
}
|
|
99
93
|
},
|
|
100
94
|
titleProps: {
|
|
101
95
|
id: titleId
|
package/src/useToastRegion.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {AriaLabelingProps, DOMAttributes} from '@react-types/shared';
|
|
2
|
-
import {focusWithoutScrolling, mergeProps} from '@react-aria/utils';
|
|
1
|
+
import {AriaLabelingProps, DOMAttributes, RefObject} from '@react-types/shared';
|
|
2
|
+
import {focusWithoutScrolling, mergeProps, useLayoutEffect} from '@react-aria/utils';
|
|
3
3
|
import {getInteractionModality, useFocusWithin, useHover} from '@react-aria/interactions';
|
|
4
4
|
// @ts-ignore
|
|
5
5
|
import intlMessages from '../intl/*.json';
|
|
6
|
-
import {RefObject, useEffect, useRef} from 'react';
|
|
7
6
|
import {ToastState} from '@react-stately/toast';
|
|
7
|
+
import {useEffect, useRef} from 'react';
|
|
8
8
|
import {useLandmark} from '@react-aria/landmark';
|
|
9
9
|
import {useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
10
10
|
|
|
@@ -25,11 +25,11 @@ export interface ToastRegionAria {
|
|
|
25
25
|
* Provides the behavior and accessibility implementation for a toast region containing one or more toasts.
|
|
26
26
|
* Toasts display brief, temporary notifications of actions, errors, or other events in an application.
|
|
27
27
|
*/
|
|
28
|
-
export function useToastRegion<T>(props: AriaToastRegionProps, state: ToastState<T>, ref: RefObject<HTMLElement>): ToastRegionAria {
|
|
28
|
+
export function useToastRegion<T>(props: AriaToastRegionProps, state: ToastState<T>, ref: RefObject<HTMLElement | null>): ToastRegionAria {
|
|
29
29
|
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/toast');
|
|
30
30
|
let {landmarkProps} = useLandmark({
|
|
31
31
|
role: 'region',
|
|
32
|
-
'aria-label': props['aria-label'] || stringFormatter.format('notifications')
|
|
32
|
+
'aria-label': props['aria-label'] || stringFormatter.format('notifications', {count: state.visibleToasts.length})
|
|
33
33
|
}, ref);
|
|
34
34
|
|
|
35
35
|
let {hoverProps} = useHover({
|
|
@@ -37,6 +37,72 @@ export function useToastRegion<T>(props: AriaToastRegionProps, state: ToastState
|
|
|
37
37
|
onHoverEnd: state.resumeAll
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
+
// Manage focus within the toast region.
|
|
41
|
+
// If a focused containing toast is removed, move focus to the next toast, or the previous toast if there is no next toast.
|
|
42
|
+
// We might be making an assumption with how this works if someone implements the priority queue differently, or
|
|
43
|
+
// if they only show one toast at a time.
|
|
44
|
+
let toasts = useRef([]);
|
|
45
|
+
let prevVisibleToasts = useRef(state.visibleToasts);
|
|
46
|
+
let focusedToast = useRef(null);
|
|
47
|
+
useLayoutEffect(() => {
|
|
48
|
+
// If no toast has focus, then don't do anything.
|
|
49
|
+
if (focusedToast.current === -1 || state.visibleToasts.length === 0) {
|
|
50
|
+
toasts.current = [];
|
|
51
|
+
prevVisibleToasts.current = state.visibleToasts;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
toasts.current = [...ref.current.querySelectorAll('[role="alertdialog"]')];
|
|
55
|
+
// If the visible toasts haven't changed, we don't need to do anything.
|
|
56
|
+
if (prevVisibleToasts.current.length === state.visibleToasts.length
|
|
57
|
+
&& state.visibleToasts.every((t, i) => t.key === prevVisibleToasts.current[i].key)) {
|
|
58
|
+
prevVisibleToasts.current = state.visibleToasts;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Get a list of all toasts by index and add info if they are removed.
|
|
62
|
+
let allToasts = prevVisibleToasts.current
|
|
63
|
+
.map((t, i) => ({
|
|
64
|
+
...t,
|
|
65
|
+
i,
|
|
66
|
+
isRemoved: !state.visibleToasts.some(t2 => t.key === t2.key)
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
let removedFocusedToastIndex = allToasts.findIndex(t => t.i === focusedToast.current);
|
|
70
|
+
|
|
71
|
+
// If the focused toast was removed, focus the next or previous toast.
|
|
72
|
+
if (removedFocusedToastIndex > -1) {
|
|
73
|
+
let i = 0;
|
|
74
|
+
let nextToast;
|
|
75
|
+
let prevToast;
|
|
76
|
+
while (i <= removedFocusedToastIndex) {
|
|
77
|
+
if (!allToasts[i].isRemoved) {
|
|
78
|
+
prevToast = Math.max(0, i - 1);
|
|
79
|
+
}
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
while (i < allToasts.length) {
|
|
83
|
+
if (!allToasts[i].isRemoved) {
|
|
84
|
+
nextToast = i - 1;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// in the case where it's one toast at a time, both will be undefined, but we know the index must be 0
|
|
91
|
+
if (prevToast === undefined && nextToast === undefined) {
|
|
92
|
+
prevToast = 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// prioritize going to newer toasts
|
|
96
|
+
if (prevToast >= 0 && prevToast < toasts.current.length) {
|
|
97
|
+
focusWithoutScrolling(toasts.current[prevToast]);
|
|
98
|
+
} else if (nextToast >= 0 && nextToast < toasts.current.length) {
|
|
99
|
+
focusWithoutScrolling(toasts.current[nextToast]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
prevVisibleToasts.current = state.visibleToasts;
|
|
104
|
+
}, [state.visibleToasts, ref]);
|
|
105
|
+
|
|
40
106
|
let lastFocused = useRef(null);
|
|
41
107
|
let {focusWithinProps} = useFocusWithin({
|
|
42
108
|
onFocusWithin: (e) => {
|
|
@@ -49,10 +115,22 @@ export function useToastRegion<T>(props: AriaToastRegionProps, state: ToastState
|
|
|
49
115
|
}
|
|
50
116
|
});
|
|
51
117
|
|
|
52
|
-
// When the
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
118
|
+
// When the number of visible toasts becomes 0 or the region unmounts,
|
|
119
|
+
// restore focus to the last element that had focus before the user moved focus
|
|
120
|
+
// into the region. FocusScope restore focus doesn't update whenever the focus
|
|
121
|
+
// moves in, it only happens once, so we correct it.
|
|
122
|
+
// Because we're in a hook, we can't control if the user unmounts or not.
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (state.visibleToasts.length === 0 && lastFocused.current && document.body.contains(lastFocused.current)) {
|
|
125
|
+
if (getInteractionModality() === 'pointer') {
|
|
126
|
+
focusWithoutScrolling(lastFocused.current);
|
|
127
|
+
} else {
|
|
128
|
+
lastFocused.current.focus();
|
|
129
|
+
}
|
|
130
|
+
lastFocused.current = null;
|
|
131
|
+
}
|
|
132
|
+
}, [ref, state.visibleToasts.length]);
|
|
133
|
+
|
|
56
134
|
useEffect(() => {
|
|
57
135
|
return () => {
|
|
58
136
|
if (lastFocused.current && document.body.contains(lastFocused.current)) {
|
|
@@ -61,6 +139,7 @@ export function useToastRegion<T>(props: AriaToastRegionProps, state: ToastState
|
|
|
61
139
|
} else {
|
|
62
140
|
lastFocused.current.focus();
|
|
63
141
|
}
|
|
142
|
+
lastFocused.current = null;
|
|
64
143
|
}
|
|
65
144
|
};
|
|
66
145
|
}, [ref]);
|
|
@@ -73,7 +152,16 @@ export function useToastRegion<T>(props: AriaToastRegionProps, state: ToastState
|
|
|
73
152
|
// - allows focus even outside a containing focus scope
|
|
74
153
|
// - doesn’t dismiss overlays when clicking on it, even though it is outside
|
|
75
154
|
// @ts-ignore
|
|
76
|
-
'data-react-aria-top-layer': true
|
|
155
|
+
'data-react-aria-top-layer': true,
|
|
156
|
+
// listen to focus events separate from focuswithin because that will only fire once
|
|
157
|
+
// and we need to follow all focus changes
|
|
158
|
+
onFocus: (e) => {
|
|
159
|
+
let target = e.target.closest('[role="alertdialog"]');
|
|
160
|
+
focusedToast.current = toasts.current.findIndex(t => t === target);
|
|
161
|
+
},
|
|
162
|
+
onBlur: () => {
|
|
163
|
+
focusedToast.current = -1;
|
|
164
|
+
}
|
|
77
165
|
})
|
|
78
166
|
};
|
|
79
167
|
}
|