Skip to content

Commit fa7ce9c

Browse files
committed
add wrapUpdate option to ToastQueue
1 parent 71c7938 commit fa7ce9c

File tree

3 files changed

+63
-67
lines changed

3 files changed

+63
-67
lines changed

packages/@react-spectrum/toast/src/Toast.tsx

+1-18
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {classNames, useDOMRef, useStyleProps} from '@react-spectrum/utils';
1616
import CrossMedium from '@spectrum-icons/ui/CrossMedium';
1717
import {DOMProps, DOMRef} from '@react-types/shared';
1818
import {filterDOMProps, mergeProps} from '@react-aria/utils';
19-
import {flushSync} from 'react-dom';
2019
import InfoMedium from '@spectrum-icons/ui/InfoMedium';
2120
// @ts-ignore
2221
import intlMessages from '../intl/*.json';
@@ -88,22 +87,6 @@ export const Toast = React.forwardRef(function Toast(props: SpectrumToastProps,
8887
}
8988
};
9089

91-
let wrappedCloseButtonProps = {
92-
...closeButtonProps,
93-
onPress: (e) => {
94-
if ('startViewTransition' in document) {
95-
// @ts-expect-error
96-
document.startViewTransition(() => {
97-
flushSync(() => {
98-
closeButtonProps.onPress?.(e);
99-
});
100-
});
101-
} else {
102-
closeButtonProps.onPress?.(e);
103-
}
104-
}
105-
};
106-
10790
return (
10891
<div
10992
{...styleProps}
@@ -146,7 +129,7 @@ export const Toast = React.forwardRef(function Toast(props: SpectrumToastProps,
146129
</div>
147130
</div>
148131
<div className={classNames(styles, 'spectrum-Toast-buttons')}>
149-
<ClearButton {...wrappedCloseButtonProps} variant="overBackground">
132+
<ClearButton {...closeButtonProps} variant="overBackground">
150133
<CrossMedium />
151134
</ClearButton>
152135
</div>

packages/@react-spectrum/toast/src/ToastContainer.tsx

+19-22
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,29 @@ export interface SpectrumToastOptions extends Omit<ToastOptions, 'priority'>, DO
3939

4040
type CloseFunction = () => void;
4141

42+
function wrapInViewTransition<R>(fn: () => R): R {
43+
if ('startViewTransition' in document) {
44+
let result: R;
45+
// @ts-expect-error
46+
document.startViewTransition(() => {
47+
flushSync(() => {
48+
result = fn();
49+
});
50+
});
51+
// @ts-ignore
52+
return result;
53+
} else {
54+
return fn();
55+
}
56+
}
57+
4258
// There is a single global toast queue instance for the whole app, initialized lazily.
4359
let globalToastQueue: ToastQueue<SpectrumToastValue> | null = null;
4460
function getGlobalToastQueue() {
4561
if (!globalToastQueue) {
4662
globalToastQueue = new ToastQueue({
47-
maxVisibleToasts: Infinity
63+
maxVisibleToasts: Infinity,
64+
wrapUpdate: wrapInViewTransition
4865
});
4966
}
5067

@@ -180,27 +197,7 @@ function addToast(children: string, variant: SpectrumToastValue['variant'], opti
180197
// It is debatable whether non-actionable toasts would also fail.
181198
let timeout = options.timeout && !options.onAction ? Math.max(options.timeout, 5000) : undefined;
182199
let queue = getGlobalToastQueue();
183-
let key: string;
184-
let add = () => queue.add(value, {timeout, onClose: options.onClose});
185-
if ('startViewTransition' in document) {
186-
// @ts-expect-error
187-
document.startViewTransition(() => {
188-
flushSync(() => {
189-
key = add();
190-
});
191-
});
192-
} else {
193-
key = add();
194-
}
195-
196-
if ('startViewTransition' in document) {
197-
// @ts-expect-error
198-
return () => document.startViewTransition(() => {
199-
flushSync(() => {
200-
queue.close(key);
201-
});
202-
});
203-
}
200+
let key = queue.add(value, {timeout, onClose: options.onClose});
204201
return () => queue.close(key);
205202
}
206203

packages/@react-stately/toast/src/useToastState.ts

+43-27
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import {useSyncExternalStore} from 'use-sync-external-store/shim/index.js';
1616

1717
export interface ToastStateProps {
1818
/** The maximum number of toasts to display at a time. */
19-
maxVisibleToasts?: number
19+
maxVisibleToasts?: number,
20+
/** Function to wrap updates in (i.e. document.startViewTransition()). */
21+
wrapUpdate?: <R>(fn: () => R) => R
2022
}
2123

2224
export interface ToastOptions {
@@ -86,11 +88,21 @@ export class ToastQueue<T> {
8688
private queue: QueuedToast<T>[] = [];
8789
private subscriptions: Set<() => void> = new Set();
8890
private maxVisibleToasts: number;
91+
private wrapUpdate?: <R>(fn: () => R) => R;
8992
/** The currently visible toasts. */
9093
visibleToasts: QueuedToast<T>[] = [];
9194

9295
constructor(options?: ToastStateProps) {
9396
this.maxVisibleToasts = options?.maxVisibleToasts ?? 1;
97+
this.wrapUpdate = options?.wrapUpdate;
98+
}
99+
100+
private runWithWrapUpdate<R>(fn: () => R): R {
101+
if (this.wrapUpdate) {
102+
return this.wrapUpdate(fn);
103+
} else {
104+
return fn();
105+
}
94106
}
95107

96108
/** Subscribes to updates to the visible toasts. */
@@ -101,42 +113,46 @@ export class ToastQueue<T> {
101113

102114
/** Adds a new toast to the queue. */
103115
add(content: T, options: ToastOptions = {}) {
104-
let toastKey = Math.random().toString(36);
105-
let toast: QueuedToast<T> = {
106-
...options,
107-
content,
108-
key: toastKey,
109-
timer: options.timeout ? new Timer(() => this.close(toastKey), options.timeout) : undefined
110-
};
111-
112-
let low = 0;
113-
let high = this.queue.length;
114-
while (low < high) {
115-
let mid = Math.floor((low + high) / 2);
116-
if ((toast.priority || 0) > (this.queue[mid].priority || 0)) {
117-
high = mid;
118-
} else {
119-
low = mid + 1;
116+
return this.runWithWrapUpdate(() => {
117+
let toastKey = Math.random().toString(36);
118+
let toast: QueuedToast<T> = {
119+
...options,
120+
content,
121+
key: toastKey,
122+
timer: options.timeout ? new Timer(() => this.close(toastKey), options.timeout) : undefined
123+
};
124+
125+
let low = 0;
126+
let high = this.queue.length;
127+
while (low < high) {
128+
let mid = Math.floor((low + high) / 2);
129+
if ((toast.priority || 0) > (this.queue[mid].priority || 0)) {
130+
high = mid;
131+
} else {
132+
low = mid + 1;
133+
}
120134
}
121-
}
122135

123-
this.queue.splice(low, 0, toast);
136+
this.queue.splice(low, 0, toast);
124137

125-
this.updateVisibleToasts();
126-
return toastKey;
138+
this.updateVisibleToasts();
139+
return toastKey;
140+
});
127141
}
128142

129143
/**
130144
* Closes a toast.
131145
*/
132146
close(key: string) {
133-
let index = this.queue.findIndex(t => t.key === key);
134-
if (index >= 0) {
135-
this.queue[index].onClose?.();
136-
this.queue.splice(index, 1);
137-
}
147+
return this.runWithWrapUpdate(() => {
148+
let index = this.queue.findIndex(t => t.key === key);
149+
if (index >= 0) {
150+
this.queue[index].onClose?.();
151+
this.queue.splice(index, 1);
152+
}
138153

139-
this.updateVisibleToasts();
154+
this.updateVisibleToasts();
155+
});
140156
}
141157

142158
private updateVisibleToasts() {

0 commit comments

Comments
 (0)