Skip to content

Commit 131569d

Browse files
committed
use globals for timers
1 parent 24b989e commit 131569d

File tree

15 files changed

+139
-170
lines changed

15 files changed

+139
-170
lines changed

packages/react/avatar/src/avatar.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,16 +100,12 @@ const AvatarFallback = React.forwardRef<AvatarFallbackElement, AvatarFallbackPro
100100
const { __scopeAvatar, delayMs, ...fallbackProps } = props;
101101
const context = useAvatarContext(FALLBACK_NAME, __scopeAvatar);
102102
const [canRender, setCanRender] = React.useState(delayMs === undefined);
103-
const providedDocument = useDocument();
104-
const documentWindow = providedDocument?.defaultView;
105-
106103
React.useEffect(() => {
107-
if (!documentWindow) return;
108104
if (delayMs !== undefined) {
109-
const timerId = documentWindow.setTimeout(() => setCanRender(true), delayMs);
110-
return () => documentWindow.clearTimeout(timerId);
105+
const timerId = globalThis.window.setTimeout(() => setCanRender(true), delayMs);
106+
return () => globalThis.window.clearTimeout(timerId);
111107
}
112-
}, [delayMs, documentWindow]);
108+
}, [delayMs]);
113109

114110
return canRender && context.imageLoadingStatus !== 'loaded' ? (
115111
<Primitive.span {...fallbackProps} ref={forwardedRef} />

packages/react/context-menu/src/context-menu.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import * as MenuPrimitive from '@radix-ui/react-menu';
66
import { createMenuScope } from '@radix-ui/react-menu';
77
import { useCallbackRef } from '@radix-ui/react-use-callback-ref';
88
import { useControllableState } from '@radix-ui/react-use-controllable-state';
9-
109
import type { Scope } from '@radix-ui/react-context';
11-
import { useDocument } from '@radix-ui/react-document-context';
1210

1311
type Direction = 'ltr' | 'rtl';
1412
type Point = { x: number; y: number };
@@ -98,12 +96,11 @@ const ContextMenuTrigger = React.forwardRef<ContextMenuTriggerElement, ContextMe
9896
const virtualRef = React.useRef({
9997
getBoundingClientRect: () => DOMRect.fromRect({ width: 0, height: 0, ...pointRef.current }),
10098
});
101-
const documentWindow = useDocument()?.defaultView;
10299

103100
const longPressTimerRef = React.useRef(0);
104101
const clearLongPress = React.useCallback(
105-
() => documentWindow?.clearTimeout(longPressTimerRef.current),
106-
[documentWindow]
102+
() => globalThis.window.clearTimeout(longPressTimerRef.current),
103+
[]
107104
);
108105
const handleOpen = (event: React.MouseEvent | React.PointerEvent) => {
109106
pointRef.current = { x: event.clientX, y: event.clientY };
@@ -143,12 +140,10 @@ const ContextMenuTrigger = React.forwardRef<ContextMenuTriggerElement, ContextMe
143140
whenTouchOrPen((event) => {
144141
// clear the long press here in case there's multiple touch points
145142
clearLongPress();
146-
if (documentWindow) {
147-
longPressTimerRef.current = documentWindow?.setTimeout(
148-
() => handleOpen(event),
149-
700
150-
);
151-
}
143+
longPressTimerRef.current = globalThis.window.setTimeout(
144+
() => handleOpen(event),
145+
700
146+
);
152147
})
153148
)
154149
}

packages/react/dismissable-layer/src/dismissable-layer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,12 +283,12 @@ function usePointerDownOutside(onPointerDownOutside?: (event: PointerDownOutside
283283
* })
284284
* });
285285
*/
286-
const timerId = documentWindow.setTimeout(() => {
286+
const timerId = globalThis.window.setTimeout(() => {
287287
providedDocument.addEventListener('pointerdown', handlePointerDown);
288288
}, 0);
289289

290290
return () => {
291-
documentWindow.clearTimeout(timerId);
291+
globalThis.window.clearTimeout(timerId);
292292
providedDocument?.removeEventListener('pointerdown', handlePointerDown);
293293
providedDocument?.removeEventListener('click', handleClickRef.current);
294294
};

packages/react/focus-scope/src/focus-scope.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ const FocusScope = React.forwardRef<FocusScopeElement, FocusScopeProps>((props,
166166
// We hit a react bug (fixed in v17) with focusing in unmount.
167167
// We need to delay the focus a little to get around it for now.
168168
// See: https://github.com/facebook/react/issues/17894
169-
setTimeout(() => {
169+
globalThis.window.setTimeout(() => {
170170
const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
171171
container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
172172
container.dispatchEvent(unmountEvent);

packages/react/hover-card/src/hover-card.tsx

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ const HoverCard: React.FC<HoverCardProps> = (props: ScopedProps<HoverCardProps>)
6464
const closeTimerRef = React.useRef(0);
6565
const hasSelectionRef = React.useRef(false);
6666
const isPointerDownOnContentRef = React.useRef(false);
67-
const documentWindow = useDocument()?.defaultView;
6867

6968
const [open, setOpen] = useControllableState({
7069
prop: openProp,
@@ -74,26 +73,24 @@ const HoverCard: React.FC<HoverCardProps> = (props: ScopedProps<HoverCardProps>)
7473
});
7574

7675
const handleOpen = React.useCallback(() => {
77-
clearTimeout(closeTimerRef.current);
78-
if (!documentWindow) return;
79-
openTimerRef.current = documentWindow.setTimeout(() => setOpen(true), openDelay);
80-
}, [openDelay, setOpen, documentWindow]);
76+
globalThis.window.clearTimeout(closeTimerRef.current);
77+
openTimerRef.current = globalThis.window.setTimeout(() => setOpen(true), openDelay);
78+
}, [openDelay, setOpen]);
8179

8280
const handleClose = React.useCallback(() => {
83-
clearTimeout(openTimerRef.current);
84-
if (!documentWindow) return;
81+
globalThis.window.clearTimeout(openTimerRef.current);
8582
if (!hasSelectionRef.current && !isPointerDownOnContentRef.current) {
86-
closeTimerRef.current = documentWindow.setTimeout(() => setOpen(false), closeDelay);
83+
closeTimerRef.current = globalThis.window.setTimeout(() => setOpen(false), closeDelay);
8784
}
88-
}, [closeDelay, setOpen, documentWindow]);
85+
}, [closeDelay, setOpen]);
8986

9087
const handleDismiss = React.useCallback(() => setOpen(false), [setOpen]);
9188

9289
// cleanup any queued state updates on unmount
9390
React.useEffect(() => {
9491
return () => {
95-
clearTimeout(openTimerRef.current);
96-
clearTimeout(closeTimerRef.current);
92+
globalThis.window.clearTimeout(openTimerRef.current);
93+
globalThis.window.clearTimeout(closeTimerRef.current);
9794
};
9895
}, []);
9996

@@ -302,7 +299,7 @@ const HoverCardContentImpl = React.forwardRef<
302299
context.isPointerDownOnContentRef.current = false;
303300

304301
// Delay a frame to ensure we always access the latest selection
305-
setTimeout(() => {
302+
globalThis.window.setTimeout(() => {
306303
const hasSelection = providedDocument.getSelection()?.toString() !== '';
307304
if (hasSelection) context.hasSelectionRef.current = true;
308305
});

packages/react/menu/src/menu.tsx

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,6 @@ const MenuContentImpl = React.forwardRef<MenuContentImplElement, MenuContentImpl
403403
? { as: Slot, allowPinchZoom: true }
404404
: undefined;
405405

406-
const documentWindow = providedDocument?.defaultView;
407-
408406
const handleTypeaheadSearch = (key: string) => {
409407
const search = searchRef.current + key;
410408
const items = getItems().filter((item) => !item.disabled);
@@ -417,24 +415,23 @@ const MenuContentImpl = React.forwardRef<MenuContentImplElement, MenuContentImpl
417415
// Reset `searchRef` 1 second after it was last updated
418416
(function updateSearch(value: string) {
419417
searchRef.current = value;
420-
if (!documentWindow) return;
421-
documentWindow.clearTimeout(timerRef.current);
418+
globalThis.window.clearTimeout(timerRef.current);
422419
if (value !== '')
423-
timerRef.current = documentWindow.setTimeout(() => updateSearch(''), 1000);
420+
timerRef.current = globalThis.window.setTimeout(() => updateSearch(''), 1000);
424421
})(search);
425422

426423
if (newItem) {
427424
/**
428425
* Imperative focus during keydown is risky so we prevent React's batching updates
429426
* to avoid potential bugs. See: https://github.com/facebook/react/issues/20332
430427
*/
431-
setTimeout(() => (newItem as HTMLElement).focus());
428+
globalThis.window.setTimeout(() => (newItem as HTMLElement).focus());
432429
}
433430
};
434431

435432
React.useEffect(() => {
436-
return () => documentWindow?.clearTimeout(timerRef.current);
437-
}, [documentWindow]);
433+
return () => globalThis.window.clearTimeout(timerRef.current);
434+
}, []);
438435

439436
// Make sure the whole tree has focus guards as our `MenuContent` may be
440437
// the last element in the DOM (because of the `Portal`)
@@ -546,7 +543,7 @@ const MenuContentImpl = React.forwardRef<MenuContentImplElement, MenuContentImpl
546543
onBlur={composeEventHandlers(props.onBlur, (event) => {
547544
// clear search buffer when leaving the menu
548545
if (!event.currentTarget.contains(event.target)) {
549-
documentWindow?.clearTimeout(timerRef.current);
546+
globalThis.window.clearTimeout(timerRef.current);
550547
searchRef.current = '';
551548
}
552549
})}
@@ -1042,21 +1039,20 @@ const MenuSubTrigger = React.forwardRef<MenuSubTriggerElement, MenuSubTriggerPro
10421039
const openTimerRef = React.useRef<number | null>(null);
10431040
const { pointerGraceTimerRef, onPointerGraceIntentChange } = contentContext;
10441041
const scope = { __scopeMenu: props.__scopeMenu };
1045-
const documentWindow = useDocument()?.defaultView;
10461042
const clearOpenTimer = React.useCallback(() => {
1047-
if (openTimerRef.current) documentWindow?.clearTimeout(openTimerRef.current);
1043+
if (openTimerRef.current) globalThis.window.clearTimeout(openTimerRef.current);
10481044
openTimerRef.current = null;
1049-
}, [documentWindow]);
1045+
}, []);
10501046

10511047
React.useEffect(() => clearOpenTimer, [clearOpenTimer]);
10521048

10531049
React.useEffect(() => {
10541050
const pointerGraceTimer = pointerGraceTimerRef.current;
10551051
return () => {
1056-
documentWindow?.clearTimeout(pointerGraceTimer);
1052+
globalThis.window.clearTimeout(pointerGraceTimer);
10571053
onPointerGraceIntentChange(null);
10581054
};
1059-
}, [pointerGraceTimerRef, onPointerGraceIntentChange, documentWindow]);
1055+
}, [pointerGraceTimerRef, onPointerGraceIntentChange]);
10601056

10611057
return (
10621058
<MenuAnchor asChild {...scope}>
@@ -1088,8 +1084,7 @@ const MenuSubTrigger = React.forwardRef<MenuSubTriggerElement, MenuSubTriggerPro
10881084
if (event.defaultPrevented) return;
10891085
if (!props.disabled && !context.open && !openTimerRef.current) {
10901086
contentContext.onPointerGraceIntentChange(null);
1091-
if (!documentWindow) return;
1092-
openTimerRef.current = documentWindow.setTimeout(() => {
1087+
openTimerRef.current = globalThis.window.setTimeout(() => {
10931088
context.onOpenChange(true);
10941089
clearOpenTimer();
10951090
}, 100);
@@ -1123,9 +1118,8 @@ const MenuSubTrigger = React.forwardRef<MenuSubTriggerElement, MenuSubTriggerPro
11231118
side,
11241119
});
11251120

1126-
if (!documentWindow) return;
1127-
documentWindow.clearTimeout(pointerGraceTimerRef.current);
1128-
pointerGraceTimerRef.current = documentWindow.setTimeout(
1121+
globalThis.window.clearTimeout(pointerGraceTimerRef.current);
1122+
pointerGraceTimerRef.current = globalThis.window.setTimeout(
11291123
() => contentContext.onPointerGraceIntentChange(null),
11301124
300
11311125
);

packages/react/navigation-menu/src/navigation-menu.tsx

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,18 @@ const NavigationMenu = React.forwardRef<NavigationMenuElement, NavigationMenuPro
116116
const closeTimerRef = React.useRef(0);
117117
const skipDelayTimerRef = React.useRef(0);
118118
const [isOpenDelayed, setIsOpenDelayed] = React.useState(true);
119-
const documentWindow = useDocument()?.defaultView;
120119
const [value, setValue] = useControllableState({
121120
prop: valueProp,
122121
onChange: (value) => {
123122
const isOpen = value !== '';
124123
const hasSkipDelayDuration = skipDelayDuration > 0;
125124

126125
if (isOpen) {
127-
documentWindow?.clearTimeout(skipDelayTimerRef.current);
126+
globalThis.window.clearTimeout(skipDelayTimerRef.current);
128127
if (hasSkipDelayDuration) setIsOpenDelayed(false);
129128
} else {
130-
documentWindow?.clearTimeout(skipDelayTimerRef.current);
131-
if (!documentWindow) return;
132-
skipDelayTimerRef.current = documentWindow.setTimeout(
129+
globalThis.window.clearTimeout(skipDelayTimerRef.current);
130+
skipDelayTimerRef.current = globalThis.window.setTimeout(
133131
() => setIsOpenDelayed(true),
134132
skipDelayDuration
135133
);
@@ -142,17 +140,16 @@ const NavigationMenu = React.forwardRef<NavigationMenuElement, NavigationMenuPro
142140
});
143141

144142
const startCloseTimer = React.useCallback(() => {
145-
if (!documentWindow) return;
146-
documentWindow.clearTimeout(closeTimerRef.current);
147-
closeTimerRef.current = documentWindow.setTimeout(() => setValue(''), 150);
148-
}, [setValue, documentWindow]);
143+
globalThis.window.clearTimeout(closeTimerRef.current);
144+
closeTimerRef.current = globalThis.window.setTimeout(() => setValue(''), 150);
145+
}, [setValue]);
149146

150147
const handleOpen = React.useCallback(
151148
(itemValue: string) => {
152-
documentWindow?.clearTimeout(closeTimerRef.current);
149+
globalThis.window.clearTimeout(closeTimerRef.current);
153150
setValue(itemValue);
154151
},
155-
[setValue, documentWindow]
152+
[setValue]
156153
);
157154

158155
const handleDelayedOpen = React.useCallback(
@@ -161,24 +158,24 @@ const NavigationMenu = React.forwardRef<NavigationMenuElement, NavigationMenuPro
161158
if (isOpenItem) {
162159
// If the item is already open (e.g. we're transitioning from the content to the trigger)
163160
// then we want to clear the close timer immediately.
164-
documentWindow?.clearTimeout(closeTimerRef.current);
165-
} else if (documentWindow) {
166-
openTimerRef.current = documentWindow.setTimeout(() => {
167-
documentWindow.clearTimeout(closeTimerRef.current);
161+
globalThis.window.clearTimeout(closeTimerRef.current);
162+
} else {
163+
openTimerRef.current = globalThis.window.setTimeout(() => {
164+
globalThis.window.clearTimeout(closeTimerRef.current);
168165
setValue(itemValue);
169166
}, delayDuration);
170167
}
171168
},
172-
[value, setValue, delayDuration, documentWindow]
169+
[value, setValue, delayDuration]
173170
);
174171

175172
React.useEffect(() => {
176173
return () => {
177-
documentWindow?.clearTimeout(openTimerRef.current);
178-
documentWindow?.clearTimeout(closeTimerRef.current);
179-
documentWindow?.clearTimeout(skipDelayTimerRef.current);
174+
globalThis.window.clearTimeout(openTimerRef.current);
175+
globalThis.window.clearTimeout(closeTimerRef.current);
176+
globalThis.window.clearTimeout(skipDelayTimerRef.current);
180177
};
181-
}, [documentWindow]);
178+
}, []);
182179

183180
return (
184181
<NavigationMenuProvider
@@ -189,15 +186,15 @@ const NavigationMenu = React.forwardRef<NavigationMenuElement, NavigationMenuPro
189186
orientation={orientation}
190187
rootNavigationMenu={navigationMenu}
191188
onTriggerEnter={(itemValue) => {
192-
documentWindow?.clearTimeout(openTimerRef.current);
189+
globalThis.window.clearTimeout(openTimerRef.current);
193190
if (isOpenDelayed) handleDelayedOpen(itemValue);
194191
else handleOpen(itemValue);
195192
}}
196193
onTriggerLeave={() => {
197-
documentWindow?.clearTimeout(openTimerRef.current);
194+
globalThis.window.clearTimeout(openTimerRef.current);
198195
startCloseTimer();
199196
}}
200-
onContentEnter={() => documentWindow?.clearTimeout(closeTimerRef.current)}
197+
onContentEnter={() => globalThis.window.clearTimeout(closeTimerRef.current)}
201198
onContentLeave={startCloseTimer}
202199
onItemSelect={(itemValue) => {
203200
setValue((prevValue) => (prevValue === itemValue ? '' : itemValue));
@@ -1155,7 +1152,7 @@ const FocusGroupItem = React.forwardRef<FocusGroupItemElement, FocusGroupItemPro
11551152
* Imperative focus during keydown is risky so we prevent React's batching updates
11561153
* to avoid potential bugs. See: https://github.com/facebook/react/issues/20332
11571154
*/
1158-
setTimeout(() => {
1155+
globalThis.window.setTimeout(() => {
11591156
if (providedDocument) {
11601157
focusFirst(candidateNodes, providedDocument);
11611158
}
@@ -1223,11 +1220,10 @@ function removeFromTabOrder(candidates: HTMLElement[]) {
12231220
}
12241221

12251222
function useResizeObserver(element: HTMLElement | null, onResize: () => void) {
1226-
const documentWindow = useDocument()?.defaultView;
12271223
const handleResize = useCallbackRef(onResize);
12281224
useLayoutEffect(() => {
12291225
let rAF = 0;
1230-
if (element && documentWindow) {
1226+
if (element) {
12311227
/**
12321228
* Resize Observer will throw an often benign error that says `ResizeObserver loop
12331229
* completed with undelivered notifications`. This means that ResizeObserver was not
@@ -1237,15 +1233,15 @@ function useResizeObserver(element: HTMLElement | null, onResize: () => void) {
12371233
*/
12381234
const resizeObserver = new ResizeObserver(() => {
12391235
cancelAnimationFrame(rAF);
1240-
rAF = documentWindow.requestAnimationFrame(handleResize);
1236+
rAF = globalThis.window.requestAnimationFrame(handleResize);
12411237
});
12421238
resizeObserver.observe(element);
12431239
return () => {
1244-
documentWindow.cancelAnimationFrame(rAF);
1240+
globalThis.window.cancelAnimationFrame(rAF);
12451241
resizeObserver.unobserve(element);
12461242
};
12471243
}
1248-
}, [element, handleResize, documentWindow]);
1244+
}, [element, handleResize]);
12491245
}
12501246

12511247
function getOpenState(open: boolean) {

0 commit comments

Comments
 (0)