Skip to content

Commit 916ead2

Browse files
committed
replace window with providedDocument.defaultView
1 parent 121349d commit 916ead2

File tree

12 files changed

+222
-148
lines changed

12 files changed

+222
-148
lines changed

packages/react/avatar/src/Avatar.tsx

+12-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useLayoutEffect } from '@radix-ui/react-use-layout-effect';
55
import { Primitive } from '@radix-ui/react-primitive';
66

77
import type { Scope } from '@radix-ui/react-context';
8+
import { useDocument } from '@radix-ui/react-document-context';
89

910
/* -------------------------------------------------------------------------------------------------
1011
* Avatar
@@ -98,13 +99,16 @@ const AvatarFallback = React.forwardRef<AvatarFallbackElement, AvatarFallbackPro
9899
const { __scopeAvatar, delayMs, ...fallbackProps } = props;
99100
const context = useAvatarContext(FALLBACK_NAME, __scopeAvatar);
100101
const [canRender, setCanRender] = React.useState(delayMs === undefined);
102+
const providedDocument = useDocument();
103+
const documentWindow = providedDocument?.defaultView;
101104

102105
React.useEffect(() => {
106+
if (!documentWindow) return;
103107
if (delayMs !== undefined) {
104-
const timerId = window.setTimeout(() => setCanRender(true), delayMs);
105-
return () => window.clearTimeout(timerId);
108+
const timerId = documentWindow.setTimeout(() => setCanRender(true), delayMs);
109+
return () => documentWindow.clearTimeout(timerId);
106110
}
107-
}, [delayMs]);
111+
}, [delayMs, documentWindow]);
108112

109113
return canRender && context.imageLoadingStatus !== 'loaded' ? (
110114
<Primitive.span {...fallbackProps} ref={forwardedRef} />
@@ -118,15 +122,17 @@ AvatarFallback.displayName = FALLBACK_NAME;
118122

119123
function useImageLoadingStatus(src?: string, referrerPolicy?: React.HTMLAttributeReferrerPolicy) {
120124
const [loadingStatus, setLoadingStatus] = React.useState<ImageLoadingStatus>('idle');
121-
125+
const providedDocument = useDocument();
126+
const documentWindow = providedDocument?.defaultView;
122127
useLayoutEffect(() => {
128+
if (!documentWindow) return;
123129
if (!src) {
124130
setLoadingStatus('error');
125131
return;
126132
}
127133

128134
let isMounted = true;
129-
const image = new window.Image();
135+
const image = new documentWindow.Image();
130136

131137
const updateStatus = (status: ImageLoadingStatus) => () => {
132138
if (!isMounted) return;
@@ -144,7 +150,7 @@ function useImageLoadingStatus(src?: string, referrerPolicy?: React.HTMLAttribut
144150
return () => {
145151
isMounted = false;
146152
};
147-
}, [src, referrerPolicy]);
153+
}, [src, referrerPolicy, documentWindow]);
148154

149155
return loadingStatus;
150156
}

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

+11-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useCallbackRef } from '@radix-ui/react-use-callback-ref';
88
import { useControllableState } from '@radix-ui/react-use-controllable-state';
99

1010
import type { Scope } from '@radix-ui/react-context';
11+
import { useDocument } from '@radix-ui/react-document-context';
1112

1213
type Direction = 'ltr' | 'rtl';
1314
type Point = { x: number; y: number };
@@ -97,10 +98,12 @@ const ContextMenuTrigger = React.forwardRef<ContextMenuTriggerElement, ContextMe
9798
const virtualRef = React.useRef({
9899
getBoundingClientRect: () => DOMRect.fromRect({ width: 0, height: 0, ...pointRef.current }),
99100
});
101+
const documentWindow = useDocument()?.defaultView;
102+
100103
const longPressTimerRef = React.useRef(0);
101104
const clearLongPress = React.useCallback(
102-
() => window.clearTimeout(longPressTimerRef.current),
103-
[]
105+
() => documentWindow?.clearTimeout(longPressTimerRef.current),
106+
[documentWindow]
104107
);
105108
const handleOpen = (event: React.MouseEvent | React.PointerEvent) => {
106109
pointRef.current = { x: event.clientX, y: event.clientY };
@@ -140,7 +143,12 @@ const ContextMenuTrigger = React.forwardRef<ContextMenuTriggerElement, ContextMe
140143
whenTouchOrPen((event) => {
141144
// clear the long press here in case there's multiple touch points
142145
clearLongPress();
143-
longPressTimerRef.current = window.setTimeout(() => handleOpen(event), 700);
146+
if (documentWindow) {
147+
longPressTimerRef.current = documentWindow?.setTimeout(
148+
() => handleOpen(event),
149+
700
150+
);
151+
}
144152
})
145153
)
146154
}

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,8 @@ function usePointerDownOutside(onPointerDownOutside?: (event: PointerDownOutside
228228

229229
React.useEffect(() => {
230230
// Only add listeners if document exists
231-
if (!providedDocument) return;
231+
const documentWindow = providedDocument?.defaultView;
232+
if (!documentWindow) return;
232233

233234
const handlePointerDown = (event: PointerEvent) => {
234235
if (event.target && !isPointerInsideReactTreeRef.current) {
@@ -282,12 +283,12 @@ function usePointerDownOutside(onPointerDownOutside?: (event: PointerDownOutside
282283
* })
283284
* });
284285
*/
285-
const timerId = window.setTimeout(() => {
286+
const timerId = documentWindow.setTimeout(() => {
286287
providedDocument.addEventListener('pointerdown', handlePointerDown);
287288
}, 0);
288289

289290
return () => {
290-
window.clearTimeout(timerId);
291+
documentWindow.clearTimeout(timerId);
291292
providedDocument?.removeEventListener('pointerdown', handlePointerDown);
292293
providedDocument?.removeEventListener('click', handleClickRef.current);
293294
};

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ 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;
6768

6869
const [open = false, setOpen] = useControllableState({
6970
prop: openProp,
@@ -73,15 +74,17 @@ const HoverCard: React.FC<HoverCardProps> = (props: ScopedProps<HoverCardProps>)
7374

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

7981
const handleClose = React.useCallback(() => {
8082
clearTimeout(openTimerRef.current);
83+
if (!documentWindow) return;
8184
if (!hasSelectionRef.current && !isPointerDownOnContentRef.current) {
82-
closeTimerRef.current = window.setTimeout(() => setOpen(false), closeDelay);
85+
closeTimerRef.current = documentWindow.setTimeout(() => setOpen(false), closeDelay);
8386
}
84-
}, [closeDelay, setOpen]);
87+
}, [closeDelay, setOpen, documentWindow]);
8588

8689
const handleDismiss = React.useCallback(() => setOpen(false), [setOpen]);
8790

packages/react/menu/src/Menu.tsx

+19-13
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,8 @@ const MenuContentImpl = React.forwardRef<MenuContentImplElement, MenuContentImpl
401401
? { as: Slot, allowPinchZoom: true }
402402
: undefined;
403403

404+
const documentWindow = providedDocument?.defaultView;
405+
404406
const handleTypeaheadSearch = (key: string) => {
405407
const search = searchRef.current + key;
406408
const items = getItems().filter((item) => !item.disabled);
@@ -413,8 +415,10 @@ const MenuContentImpl = React.forwardRef<MenuContentImplElement, MenuContentImpl
413415
// Reset `searchRef` 1 second after it was last updated
414416
(function updateSearch(value: string) {
415417
searchRef.current = value;
416-
window.clearTimeout(timerRef.current);
417-
if (value !== '') timerRef.current = window.setTimeout(() => updateSearch(''), 1000);
418+
if (!documentWindow) return;
419+
documentWindow.clearTimeout(timerRef.current);
420+
if (value !== '')
421+
timerRef.current = documentWindow.setTimeout(() => updateSearch(''), 1000);
418422
})(search);
419423

420424
if (newItem) {
@@ -427,8 +431,8 @@ const MenuContentImpl = React.forwardRef<MenuContentImplElement, MenuContentImpl
427431
};
428432

429433
React.useEffect(() => {
430-
return () => window.clearTimeout(timerRef.current);
431-
}, []);
434+
return () => documentWindow?.clearTimeout(timerRef.current);
435+
}, [documentWindow]);
432436

433437
// Make sure the whole tree has focus guards as our `MenuContent` may be
434438
// the last element in the DOM (because of the `Portal`)
@@ -540,7 +544,7 @@ const MenuContentImpl = React.forwardRef<MenuContentImplElement, MenuContentImpl
540544
onBlur={composeEventHandlers(props.onBlur, (event) => {
541545
// clear search buffer when leaving the menu
542546
if (!event.currentTarget.contains(event.target)) {
543-
window.clearTimeout(timerRef.current);
547+
documentWindow?.clearTimeout(timerRef.current);
544548
searchRef.current = '';
545549
}
546550
})}
@@ -1036,21 +1040,21 @@ const MenuSubTrigger = React.forwardRef<MenuSubTriggerElement, MenuSubTriggerPro
10361040
const openTimerRef = React.useRef<number | null>(null);
10371041
const { pointerGraceTimerRef, onPointerGraceIntentChange } = contentContext;
10381042
const scope = { __scopeMenu: props.__scopeMenu };
1039-
1043+
const documentWindow = useDocument()?.defaultView;
10401044
const clearOpenTimer = React.useCallback(() => {
1041-
if (openTimerRef.current) window.clearTimeout(openTimerRef.current);
1045+
if (openTimerRef.current) documentWindow?.clearTimeout(openTimerRef.current);
10421046
openTimerRef.current = null;
1043-
}, []);
1047+
}, [documentWindow]);
10441048

10451049
React.useEffect(() => clearOpenTimer, [clearOpenTimer]);
10461050

10471051
React.useEffect(() => {
10481052
const pointerGraceTimer = pointerGraceTimerRef.current;
10491053
return () => {
1050-
window.clearTimeout(pointerGraceTimer);
1054+
documentWindow?.clearTimeout(pointerGraceTimer);
10511055
onPointerGraceIntentChange(null);
10521056
};
1053-
}, [pointerGraceTimerRef, onPointerGraceIntentChange]);
1057+
}, [pointerGraceTimerRef, onPointerGraceIntentChange, documentWindow]);
10541058

10551059
return (
10561060
<MenuAnchor asChild {...scope}>
@@ -1082,7 +1086,8 @@ const MenuSubTrigger = React.forwardRef<MenuSubTriggerElement, MenuSubTriggerPro
10821086
if (event.defaultPrevented) return;
10831087
if (!props.disabled && !context.open && !openTimerRef.current) {
10841088
contentContext.onPointerGraceIntentChange(null);
1085-
openTimerRef.current = window.setTimeout(() => {
1089+
if (!documentWindow) return;
1090+
openTimerRef.current = documentWindow.setTimeout(() => {
10861091
context.onOpenChange(true);
10871092
clearOpenTimer();
10881093
}, 100);
@@ -1116,8 +1121,9 @@ const MenuSubTrigger = React.forwardRef<MenuSubTriggerElement, MenuSubTriggerPro
11161121
side,
11171122
});
11181123

1119-
window.clearTimeout(pointerGraceTimerRef.current);
1120-
pointerGraceTimerRef.current = window.setTimeout(
1124+
if (!documentWindow) return;
1125+
documentWindow.clearTimeout(pointerGraceTimerRef.current);
1126+
pointerGraceTimerRef.current = documentWindow.setTimeout(
11211127
() => contentContext.onPointerGraceIntentChange(null),
11221128
300
11231129
);

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

+28-24
Original file line numberDiff line numberDiff line change
@@ -118,18 +118,20 @@ const NavigationMenu = React.forwardRef<NavigationMenuElement, NavigationMenuPro
118118
const closeTimerRef = React.useRef(0);
119119
const skipDelayTimerRef = React.useRef(0);
120120
const [isOpenDelayed, setIsOpenDelayed] = React.useState(true);
121+
const documentWindow = useDocument()?.defaultView;
121122
const [value = '', setValue] = useControllableState({
122123
prop: valueProp,
123124
onChange: (value) => {
124125
const isOpen = value !== '';
125126
const hasSkipDelayDuration = skipDelayDuration > 0;
126127

127128
if (isOpen) {
128-
window.clearTimeout(skipDelayTimerRef.current);
129+
documentWindow?.clearTimeout(skipDelayTimerRef.current);
129130
if (hasSkipDelayDuration) setIsOpenDelayed(false);
130131
} else {
131-
window.clearTimeout(skipDelayTimerRef.current);
132-
skipDelayTimerRef.current = window.setTimeout(
132+
documentWindow?.clearTimeout(skipDelayTimerRef.current);
133+
if (!documentWindow) return;
134+
skipDelayTimerRef.current = documentWindow.setTimeout(
133135
() => setIsOpenDelayed(true),
134136
skipDelayDuration
135137
);
@@ -141,16 +143,17 @@ const NavigationMenu = React.forwardRef<NavigationMenuElement, NavigationMenuPro
141143
});
142144

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

148151
const handleOpen = React.useCallback(
149152
(itemValue: string) => {
150-
window.clearTimeout(closeTimerRef.current);
153+
documentWindow?.clearTimeout(closeTimerRef.current);
151154
setValue(itemValue);
152155
},
153-
[setValue]
156+
[setValue, documentWindow]
154157
);
155158

156159
const handleDelayedOpen = React.useCallback(
@@ -159,24 +162,24 @@ const NavigationMenu = React.forwardRef<NavigationMenuElement, NavigationMenuPro
159162
if (isOpenItem) {
160163
// If the item is already open (e.g. we're transitioning from the content to the trigger)
161164
// then we want to clear the close timer immediately.
162-
window.clearTimeout(closeTimerRef.current);
163-
} else {
164-
openTimerRef.current = window.setTimeout(() => {
165-
window.clearTimeout(closeTimerRef.current);
165+
documentWindow?.clearTimeout(closeTimerRef.current);
166+
} else if (documentWindow) {
167+
openTimerRef.current = documentWindow.setTimeout(() => {
168+
documentWindow.clearTimeout(closeTimerRef.current);
166169
setValue(itemValue);
167170
}, delayDuration);
168171
}
169172
},
170-
[value, setValue, delayDuration]
173+
[value, setValue, delayDuration, documentWindow]
171174
);
172175

173176
React.useEffect(() => {
174177
return () => {
175-
window.clearTimeout(openTimerRef.current);
176-
window.clearTimeout(closeTimerRef.current);
177-
window.clearTimeout(skipDelayTimerRef.current);
178+
documentWindow?.clearTimeout(openTimerRef.current);
179+
documentWindow?.clearTimeout(closeTimerRef.current);
180+
documentWindow?.clearTimeout(skipDelayTimerRef.current);
178181
};
179-
}, []);
182+
}, [documentWindow]);
180183

181184
return (
182185
<NavigationMenuProvider
@@ -187,15 +190,15 @@ const NavigationMenu = React.forwardRef<NavigationMenuElement, NavigationMenuPro
187190
orientation={orientation}
188191
rootNavigationMenu={navigationMenu}
189192
onTriggerEnter={(itemValue) => {
190-
window.clearTimeout(openTimerRef.current);
193+
documentWindow?.clearTimeout(openTimerRef.current);
191194
if (isOpenDelayed) handleDelayedOpen(itemValue);
192195
else handleOpen(itemValue);
193196
}}
194197
onTriggerLeave={() => {
195-
window.clearTimeout(openTimerRef.current);
198+
documentWindow?.clearTimeout(openTimerRef.current);
196199
startCloseTimer();
197200
}}
198-
onContentEnter={() => window.clearTimeout(closeTimerRef.current)}
201+
onContentEnter={() => documentWindow?.clearTimeout(closeTimerRef.current)}
199202
onContentLeave={startCloseTimer}
200203
onItemSelect={(itemValue) => {
201204
setValue((prevValue) => (prevValue === itemValue ? '' : itemValue));
@@ -1220,10 +1223,11 @@ function removeFromTabOrder(candidates: HTMLElement[]) {
12201223
}
12211224

12221225
function useResizeObserver(element: HTMLElement | null, onResize: () => void) {
1226+
const documentWindow = useDocument()?.defaultView;
12231227
const handleResize = useCallbackRef(onResize);
12241228
useLayoutEffect(() => {
12251229
let rAF = 0;
1226-
if (element) {
1230+
if (element && documentWindow) {
12271231
/**
12281232
* Resize Observer will throw an often benign error that says `ResizeObserver loop
12291233
* completed with undelivered notifications`. This means that ResizeObserver was not
@@ -1233,15 +1237,15 @@ function useResizeObserver(element: HTMLElement | null, onResize: () => void) {
12331237
*/
12341238
const resizeObserver = new ResizeObserver(() => {
12351239
cancelAnimationFrame(rAF);
1236-
rAF = window.requestAnimationFrame(handleResize);
1240+
rAF = documentWindow.requestAnimationFrame(handleResize);
12371241
});
12381242
resizeObserver.observe(element);
12391243
return () => {
1240-
window.cancelAnimationFrame(rAF);
1244+
documentWindow.cancelAnimationFrame(rAF);
12411245
resizeObserver.unobserve(element);
12421246
};
12431247
}
1244-
}, [element, handleResize]);
1248+
}, [element, handleResize, documentWindow]);
12451249
}
12461250

12471251
function getOpenState(open: boolean) {

packages/react/popper/src/Popper.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { useSize } from '@radix-ui/react-use-size';
2121
import type { Placement, Middleware } from '@floating-ui/react-dom';
2222
import type { Scope } from '@radix-ui/react-context';
2323
import type { Measurable } from '@radix-ui/rect';
24+
import { useDocument } from '@radix-ui/react-document-context';
2425

2526
const SIDE_OPTIONS = ['top', 'right', 'bottom', 'left'] as const;
2627
const ALIGN_OPTIONS = ['start', 'center', 'end'] as const;
@@ -224,10 +225,12 @@ const PopperContent = React.forwardRef<PopperContentElement, PopperContentProps>
224225
const arrowY = middlewareData.arrow?.y;
225226
const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0;
226227

228+
const documentWindow = useDocument()?.defaultView;
227229
const [contentZIndex, setContentZIndex] = React.useState<string>();
228230
useLayoutEffect(() => {
229-
if (content) setContentZIndex(window.getComputedStyle(content).zIndex);
230-
}, [content]);
231+
if (content && documentWindow)
232+
setContentZIndex(documentWindow.getComputedStyle(content).zIndex);
233+
}, [content, documentWindow]);
231234

232235
return (
233236
<div

0 commit comments

Comments
 (0)