From b42e18ee29d1618736ecb7d1d62c0f0fc328b70f Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 3 Apr 2024 17:08:36 -0700 Subject: [PATCH 01/25] wip --- .../src/configs/restricted-globals.js | 479 +++++++++--------- 1 file changed, 238 insertions(+), 241 deletions(-) diff --git a/packages/eslint-plugin/src/configs/restricted-globals.js b/packages/eslint-plugin/src/configs/restricted-globals.js index 5cc7632b4002c..513d23f1aff6f 100644 --- a/packages/eslint-plugin/src/configs/restricted-globals.js +++ b/packages/eslint-plugin/src/configs/restricted-globals.js @@ -1,243 +1,240 @@ -// These will be enabled in a follow up PR. -// https://github.com/microsoft/fluentui/issues/29964 - // Via: https://developer.mozilla.org/en-US/docs/Web/API/Window -// const windowKeys = [ -// // Instance properties -// 'caches', -// 'clientInformation', -// 'closed', -// 'console', -// 'credentialless', -// 'crypto', -// 'customElements', -// 'devicePixelRatio', -// 'frameElement', -// 'frames', -// 'fullScreen', -// 'history', -// 'indexedDB', -// 'innerHeight', -// 'innerWidth', -// 'isSecureContext', -// 'launchQueue', -// 'length', -// 'localStorage', -// 'location', -// 'locationbar', -// 'menubar', -// 'name', -// 'navigation', -// 'navigator', -// 'opener', -// 'origin', -// 'outerHeight', -// 'outerWidth', -// 'pageXOffset', -// 'pageYOffset', -// 'parent', -// 'performance', -// 'personalbar', -// 'scheduler', -// 'screen', -// 'screenX', -// 'screenY', -// 'scrollbars', -// 'scrollX', -// 'scrollY', -// 'self', -// 'sessionStorage', -// 'speechSynthesis', -// 'statusbar', -// 'toolbar', -// 'top', -// 'visualViewport', -// 'window', +const windowKeys = [ + // Instance properties + 'caches', + 'clientInformation', + 'closed', + // 'console', // `console` is already disallowed via a different rule + 'credentialless', + 'crypto', + 'customElements', + 'devicePixelRatio', + 'frameElement', + 'frames', + 'fullScreen', + 'history', + 'indexedDB', + 'innerHeight', + 'innerWidth', + 'isSecureContext', + 'launchQueue', + 'length', + 'localStorage', + 'location', + 'locationbar', + 'menubar', + 'name', + 'navigation', + 'navigator', + 'opener', + 'origin', + 'outerHeight', + 'outerWidth', + 'pageXOffset', + 'pageYOffset', + 'parent', + 'performance', + 'personalbar', + 'scheduler', + 'screen', + 'screenX', + 'screenY', + 'scrollbars', + 'scrollX', + 'scrollY', + 'self', + 'sessionStorage', + 'speechSynthesis', + 'statusbar', + 'toolbar', + 'top', + 'visualViewport', + 'window', -// // Deprecated properties -// 'defaultStatus', -// 'event', -// 'external', -// 'orientation', -// 'returnValue', -// 'sidebar', -// 'status', + // Deprecated properties + 'defaultStatus', + 'event', + 'external', + 'orientation', + 'returnValue', + 'sidebar', + 'status', -// // Instance methods -// 'addEventListener', -// 'alert', -// 'atob', -// 'blur', -// 'btoa', -// 'cancelAnimationFrame', -// 'cancelIdleCallback', -// 'clearImmediate', -// 'clearInterval', -// 'clearTimeout', -// 'close', -// 'confirm', -// 'createImageBitmap', -// 'dispatchEvent', -// 'dump', -// 'fetch', -// 'find', -// 'focus', -// 'getComputedStyle', -// 'getSelection', -// 'matchMedia', -// 'moveBy', -// 'moveTo', -// 'open', -// 'postMessage', -// 'print', -// 'prompt', -// 'queryLocalFonts', -// 'removeEventListener', -// 'reportError', -// 'requestAnimationFrame', -// 'requestIdleCallback', -// 'resizeBy', -// 'resizeTo', -// 'scroll', -// 'scrollBy', -// 'scrollTo', -// 'setImmediate', -// 'setInterval', -// 'setTimeout', -// 'showDirectoryPicker', -// 'showOpenFilePicker', -// 'showSaveFilePicker', -// 'stop', -// 'updateCommands', + // Instance methods + 'addEventListener', + 'alert', + 'atob', + 'blur', + 'btoa', + 'cancelAnimationFrame', + 'cancelIdleCallback', + 'clearImmediate', + 'clearInterval', + 'clearTimeout', + 'close', + 'confirm', + 'createImageBitmap', + 'dispatchEvent', + 'dump', + 'fetch', + 'find', + 'focus', + 'getComputedStyle', + 'getSelection', + 'matchMedia', + 'moveBy', + 'moveTo', + 'open', + 'postMessage', + 'print', + 'prompt', + 'queryLocalFonts', + 'removeEventListener', + 'reportError', + 'requestAnimationFrame', + 'requestIdleCallback', + 'resizeBy', + 'resizeTo', + 'scroll', + 'scrollBy', + 'scrollTo', + 'setImmediate', + 'setInterval', + 'setTimeout', + 'showDirectoryPicker', + 'showOpenFilePicker', + 'showSaveFilePicker', + 'stop', + 'updateCommands', -// // Deprecated methods -// 'back', -// 'captureEvents', -// 'forward', -// 'releaseEvents', -// 'showModalDialog', + // Deprecated methods + 'back', + 'captureEvents', + 'forward', + 'releaseEvents', + 'showModalDialog', -// // Events -// 'onabort', -// 'onafterprint', -// 'onanimationend', -// 'onanimationiteration', -// 'onanimationstart', -// 'onappinstalled', -// 'onauxclick', -// 'onbeforeinput', -// 'onbeforeinstallprompt', -// 'onbeforematch', -// 'onbeforeprint', -// 'onbeforetoggle', -// 'onbeforeunload', -// 'onbeforexrselect', -// 'onblur', -// 'oncancel', -// 'oncanplay', -// 'oncanplaythrough', -// 'onchange', -// 'onclick', -// 'onclose', -// 'oncontentvisibilityautostatechange', -// 'oncontextlost', -// 'oncontextmenu', -// 'oncontextrestored', -// 'oncuechange', -// 'ondblclick', -// 'ondevicemotion', -// 'ondeviceorientation', -// 'ondeviceorientationabsolute', -// 'ondrag', -// 'ondragend', -// 'ondragenter', -// 'ondragleave', -// 'ondragover', -// 'ondragstart', -// 'ondrop', -// 'ondurationchange', -// 'onemptied', -// 'onended', -// 'onerror', -// 'onfocus', -// 'onformdata', -// 'ongotpointercapture', -// 'onhashchange', -// 'oninput', -// 'oninvalid', -// 'onkeydown', -// 'onkeypress', -// 'onkeyup', -// 'onlanguagechange', -// 'onload', -// 'onloadeddata', -// 'onloadedmetadata', -// 'onloadstart', -// 'onlostpointercapture', -// 'onmessage', -// 'onmessageerror', -// 'onmousedown', -// 'onmouseenter', -// 'onmouseleave', -// 'onmousemove', -// 'onmouseout', -// 'onmouseover', -// 'onmouseup', -// 'onmousewheel', -// 'onoffline', -// 'ononline', -// 'onoverscroll', -// 'onpagehide', -// 'onpageshow', -// 'onpause', -// 'onplay', -// 'onplaying', -// 'onpointercancel', -// 'onpointerdown', -// 'onpointerenter', -// 'onpointerleave', -// 'onpointermove', -// 'onpointerout', -// 'onpointerover', -// 'onpointerrawupdate', -// 'onpointerup', -// 'onpopstate', -// 'onprogress', -// 'onratechange', -// 'onrejectionhandled', -// 'onreset', -// 'onresize', -// 'onscroll', -// 'onscrollend', -// 'onsearch', -// 'onsecuritypolicyviolation', -// 'onseeked', -// 'onseeking', -// 'onselect', -// 'onselectionchange', -// 'onselectstart', -// 'onslotchange', -// 'onstalled', -// 'onstorage', -// 'onsubmit', -// 'onsuspend', -// 'ontimeupdate', -// 'ontimezonechange', -// 'ontoggle', -// 'ontransitioncancel', -// 'ontransitionend', -// 'ontransitionrun', -// 'ontransitionstart', -// 'onunhandledrejection', -// 'onunload', -// 'onvolumechange', -// 'onwaiting', -// 'onwebkitanimationend', -// 'onwebkitanimationiteration', -// 'onwebkitanimationstart', -// 'onwebkittransitionend', -// 'onwheel', -// ]; + // Events + 'onabort', + 'onafterprint', + 'onanimationend', + 'onanimationiteration', + 'onanimationstart', + 'onappinstalled', + 'onauxclick', + 'onbeforeinput', + 'onbeforeinstallprompt', + 'onbeforematch', + 'onbeforeprint', + 'onbeforetoggle', + 'onbeforeunload', + 'onbeforexrselect', + 'onblur', + 'oncancel', + 'oncanplay', + 'oncanplaythrough', + 'onchange', + 'onclick', + 'onclose', + 'oncontentvisibilityautostatechange', + 'oncontextlost', + 'oncontextmenu', + 'oncontextrestored', + 'oncuechange', + 'ondblclick', + 'ondevicemotion', + 'ondeviceorientation', + 'ondeviceorientationabsolute', + 'ondrag', + 'ondragend', + 'ondragenter', + 'ondragleave', + 'ondragover', + 'ondragstart', + 'ondrop', + 'ondurationchange', + 'onemptied', + 'onended', + 'onerror', + 'onfocus', + 'onformdata', + 'ongotpointercapture', + 'onhashchange', + 'oninput', + 'oninvalid', + 'onkeydown', + 'onkeypress', + 'onkeyup', + 'onlanguagechange', + 'onload', + 'onloadeddata', + 'onloadedmetadata', + 'onloadstart', + 'onlostpointercapture', + 'onmessage', + 'onmessageerror', + 'onmousedown', + 'onmouseenter', + 'onmouseleave', + 'onmousemove', + 'onmouseout', + 'onmouseover', + 'onmouseup', + 'onmousewheel', + 'onoffline', + 'ononline', + 'onoverscroll', + 'onpagehide', + 'onpageshow', + 'onpause', + 'onplay', + 'onplaying', + 'onpointercancel', + 'onpointerdown', + 'onpointerenter', + 'onpointerleave', + 'onpointermove', + 'onpointerout', + 'onpointerover', + 'onpointerrawupdate', + 'onpointerup', + 'onpopstate', + 'onprogress', + 'onratechange', + 'onrejectionhandled', + 'onreset', + 'onresize', + 'onscroll', + 'onscrollend', + 'onsearch', + 'onsecuritypolicyviolation', + 'onseeked', + 'onseeking', + 'onselect', + 'onselectionchange', + 'onselectstart', + 'onslotchange', + 'onstalled', + 'onstorage', + 'onsubmit', + 'onsuspend', + 'ontimeupdate', + 'ontimezonechange', + 'ontoggle', + 'ontransitioncancel', + 'ontransitionend', + 'ontransitionrun', + 'ontransitionstart', + 'onunhandledrejection', + 'onunload', + 'onvolumechange', + 'onwaiting', + 'onwebkitanimationend', + 'onwebkitanimationiteration', + 'onwebkitanimationstart', + 'onwebkittransitionend', + 'onwheel', +]; const reactLegacy = [ 'error', @@ -261,12 +258,12 @@ const react = [ name: 'document', message: 'Get a reference to `document` from `useFluent()`.', }, - // ...windowKeys.map(key => { - // return { - // name: key, - // message: `Get a reference to \`window\` from \`useFluent()\` and access \`${key}\` from there.`, - // }; - // }), + ...windowKeys.map(key => { + return { + name: key, + message: `Get a reference to \`window\` from \`useFluent()\` and access \`${key}\` from there.`, + }; + }), ]; module.exports = { From 3a97295b8ef6310a778cb081aa74984600aa88b8 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 3 Apr 2024 17:28:44 -0700 Subject: [PATCH 02/25] use 'window' from context --- .../react-utilities/src/hooks/useAnimationFrame.ts | 10 +++++++--- .../react-utilities/src/hooks/useTimeout.ts | 8 ++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/react-components/react-utilities/src/hooks/useAnimationFrame.ts b/packages/react-components/react-utilities/src/hooks/useAnimationFrame.ts index a95e862aad739..a3866d161fced 100644 --- a/packages/react-components/react-utilities/src/hooks/useAnimationFrame.ts +++ b/packages/react-components/react-utilities/src/hooks/useAnimationFrame.ts @@ -1,5 +1,6 @@ import { canUseDOM } from '../ssr/canUseDOM'; import { useBrowserTimer } from './useBrowserTimer'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; const setAnimationFrameNoop = (callback: FrameRequestCallback) => { callback(0); @@ -18,9 +19,12 @@ const cancelAnimationFrameNoop = (handle: number) => handle; export function useAnimationFrame() { const isDOM = canUseDOM(); - // TODO: figure it out a way to not call global.requestAnimationFrame and instead infer window from some context - const setAnimationFrame = isDOM ? requestAnimationFrame : setAnimationFrameNoop; - const clearAnimationFrame = isDOM ? cancelAnimationFrame : cancelAnimationFrameNoop; + const { targetDocument } = useFluent(); + // eslint-disable-next-line no-restricted-globals + const win = targetDocument?.defaultView ?? window; + + const setAnimationFrame = isDOM ? win.requestAnimationFrame : setAnimationFrameNoop; + const clearAnimationFrame = isDOM ? win.cancelAnimationFrame : cancelAnimationFrameNoop; return useBrowserTimer(setAnimationFrame, clearAnimationFrame); } diff --git a/packages/react-components/react-utilities/src/hooks/useTimeout.ts b/packages/react-components/react-utilities/src/hooks/useTimeout.ts index 36c0813ef2107..d140ccde78bbd 100644 --- a/packages/react-components/react-utilities/src/hooks/useTimeout.ts +++ b/packages/react-components/react-utilities/src/hooks/useTimeout.ts @@ -1,4 +1,5 @@ import { useBrowserTimer } from './useBrowserTimer'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; /** * @internal @@ -9,6 +10,9 @@ import { useBrowserTimer } from './useBrowserTimer'; * @returns A pair of [setTimeout, clearTimeout] that are stable between renders. */ export function useTimeout() { - // TODO: figure it out a way to not call global.setTimeout and instead infer window from some context - return useBrowserTimer(setTimeout, clearTimeout); + const { targetDocument } = useFluent(); + // eslint-disable-next-line no-restricted-globals + const win = targetDocument?.defaultView ?? window; + + return useBrowserTimer(win.setTimeout, win.clearTimeout); } From e49b73c964f7df4a26c2f286d2bc50bd772fab36 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 4 Apr 2024 11:21:03 -0700 Subject: [PATCH 03/25] fix: update global functions to use 'window' from Fluent context --- .../react-icons-compat/src/icon.ts | 2 ++ .../src/components/Menu/useMenu.tsx | 2 ++ .../components/MenuPopover/useMenuPopover.ts | 12 +++++----- .../src/components/Popover/usePopover.ts | 5 ++++- .../stories/Tree/utils/mockFetch.ts | 1 + .../components/Virtualizer/useVirtualizer.ts | 5 ----- .../src/hooks/useIntersectionObserver.ts | 22 ++++++++++++------- 7 files changed, 30 insertions(+), 19 deletions(-) diff --git a/packages/react-components/react-icons-compat/src/icon.ts b/packages/react-components/react-icons-compat/src/icon.ts index 998e4429787b3..6bcce7fb14726 100644 --- a/packages/react-components/react-icons-compat/src/icon.ts +++ b/packages/react-components/react-icons-compat/src/icon.ts @@ -197,6 +197,7 @@ export function setIconOptions(options: Partial): void { } let _missingIcons: string[] = []; +// eslint-disable-next-line no-restricted-globals let _missingIconsTimer: ReturnType | undefined = undefined; function _warnDuplicateIcon(iconName: string): void { @@ -207,6 +208,7 @@ function _warnDuplicateIcon(iconName: string): void { if (!options.disableWarnings) { _missingIcons.push(iconName); if (_missingIconsTimer === undefined) { + // eslint-disable-next-line no-restricted-globals _missingIconsTimer = setTimeout(() => { // eslint-disable-next-line no-console console.warn( diff --git a/packages/react-components/react-menu/src/components/Menu/useMenu.tsx b/packages/react-components/react-menu/src/components/Menu/useMenu.tsx index 507b8f072ccad..50d6acf3d9674 100644 --- a/packages/react-components/react-menu/src/components/Menu/useMenu.tsx +++ b/packages/react-components/react-menu/src/components/Menu/useMenu.tsx @@ -174,6 +174,8 @@ const useMenuOpenState = ( Pick, ) => { const { targetDocument } = useFluent(); + // eslint-disable-next-line no-restricted-globals + const win = targetDocument?.defaultView ?? window; const parentSetOpen = useMenuContext_unstable(context => context.setOpen); const onOpenChange: MenuProps['onOpenChange'] = useEventCallback((e, data) => state.onOpenChange?.(e, data)); diff --git a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts index c90b12dfcd1d7..acd78fca8c56a 100644 --- a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts +++ b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts @@ -28,7 +28,9 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< const throttleDispatchTimerRef = React.useRef(0); const restoreFocusSourceAttributes = useRestoreFocusSource(); - const { dir } = useFluent(); + const { dir, targetDocument } = useFluent(); + // eslint-disable-next-line no-restricted-globals + const win = targetDocument?.defaultView ?? window; const CloseArrowKey = dir === 'ltr' ? ArrowLeft : ArrowRight; // use DOM listener since react events propagate up the react tree @@ -45,17 +47,17 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< dispatchMenuEnterEvent(popoverRef.current as HTMLElement, e); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore #16889 Node setTimeout type leaking - throttleDispatchTimerRef.current = setTimeout(() => (canDispatchCustomEventRef.current = true), 250); + throttleDispatchTimerRef.current = win.setTimeout(() => (canDispatchCustomEventRef.current = true), 250); } }); } }, - [popoverRef, throttleDispatchTimerRef], + [popoverRef, throttleDispatchTimerRef, win], ); React.useEffect(() => { - () => clearTimeout(throttleDispatchTimerRef.current); - }, []); + () => win.clearTimeout(throttleDispatchTimerRef.current); + }, [win]); const inline = useMenuContext_unstable(context => context.inline) ?? false; const mountNode = useMenuContext_unstable(context => context.mountNode); diff --git a/packages/react-components/react-popover/src/components/Popover/usePopover.ts b/packages/react-components/react-popover/src/components/Popover/usePopover.ts index 74a57079fa280..a9fc196da6e93 100644 --- a/packages/react-components/react-popover/src/components/Popover/usePopover.ts +++ b/packages/react-components/react-popover/src/components/Popover/usePopover.ts @@ -36,6 +36,10 @@ export const usePopover_unstable = (props: PopoverProps): PopoverState => { ...props, } as const; + const { targetDocument } = useFluent(); + // eslint-disable-next-line no-restricted-globals + const win = targetDocument?.defaultView ?? window; + const children = React.Children.toArray(props.children) as React.ReactElement[]; if (process.env.NODE_ENV !== 'production') { @@ -89,7 +93,6 @@ export const usePopover_unstable = (props: PopoverProps): PopoverState => { ); const positioningRefs = usePopoverRefs(initialState); - const { targetDocument } = useFluent(); useOnClickOutside({ contains: elementContains, diff --git a/packages/react-components/react-tree/stories/Tree/utils/mockFetch.ts b/packages/react-components/react-tree/stories/Tree/utils/mockFetch.ts index 7c65b4794822a..32e7c59920027 100644 --- a/packages/react-components/react-tree/stories/Tree/utils/mockFetch.ts +++ b/packages/react-components/react-tree/stories/Tree/utils/mockFetch.ts @@ -4,6 +4,7 @@ export interface Response { export const mockFetch = (type: string) => { return new Promise(resolve => { + // eslint-disable-next-line no-restricted-globals setTimeout(() => { const mockData: Response = { results: Array.from({ length: 10 }, (_, index) => ({ diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index ed1ed221babb2..d07de5b243aab 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -111,12 +111,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta initializeScrollingTimer(); }, [actualIndex, initializeScrollingTimer]); - const batchUpdateNewIndex = (index: number) => { // Local updates - updateChildRows(index); - updateCurrentItemSizes(index); - - // Set before 'setActualIndex' call // If it changes before render, or injected via context, re-render will update ref. actualIndexRef.current = index; diff --git a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts index b0450c9f69722..d11ee09c2f177 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts @@ -12,10 +12,14 @@ import { useMutationObserver } from './useMutationObserver'; * @param target target element that will have its current reading direction determined * @returns the corrected rootMargin (if it was necessary to correct) */ -export const getRTLRootMargin = (ltrRootMargin: string, target?: Element | Document | null | undefined): string => { +export const getRTLRootMargin = ( + ltrRootMargin: string, + win: Window, + target?: Element | Document | null | undefined, +): string => { if (target) { // get the computed dir for the target element - const newDir = getComputedStyle(target as Element).direction; + const newDir = win.getComputedStyle(target as Element).direction; // If we're in rtl reading direction, we might need to flip the margins on the left/right sides if (newDir === 'rtl') { @@ -57,12 +61,14 @@ export const useIntersectionObserver = ( const observer = useRef(); const [observerList, setObserverList] = useState(); const { targetDocument } = useFluent(); + // eslint-disable-next-line no-restricted-globals + const win = targetDocument?.defaultView ?? window; // set the initial init with corrected margins based on the observed root's calculated reading direction. const [observerInit, setObserverInit] = useState( options && { ...options, - rootMargin: getRTLRootMargin(options.rootMargin ?? '0px', options.root as Element), + rootMargin: getRTLRootMargin(options.rootMargin ?? '0px', win, options.root as Element), }, ); @@ -82,12 +88,12 @@ export const useIntersectionObserver = ( ) { setObserverInit({ ...observerInit, - rootMargin: getRTLRootMargin(ltrRootMargin.current, observerInit?.root), + rootMargin: getRTLRootMargin(ltrRootMargin.current, win, observerInit?.root), }); } } }, - [ltrRootMargin, observerInit, options?.root], + [ltrRootMargin, observerInit, options?.root, win], ); // Mutation observer for dir attribute changes in the document @@ -107,7 +113,7 @@ export const useIntersectionObserver = ( observer.current = new win.IntersectionObserver(callback, { ...observerInit, - rootMargin: getRTLRootMargin(ltrRootMargin.current, observerInit?.root), + rootMargin: getRTLRootMargin(ltrRootMargin.current, win, observerInit?.root), }); // If we have an instance of IO and a list with elements, observer the elements @@ -134,10 +140,10 @@ export const useIntersectionObserver = ( // Call the internal setter to update the value and ensure if our calculated direction is rtl, we flip the margin setObserverInit({ ...newInit, - rootMargin: getRTLRootMargin(ltrRootMargin.current, newInit?.root as Element), + rootMargin: getRTLRootMargin(ltrRootMargin.current, win, newInit?.root as Element), }); }, - [ltrRootMargin, setObserverInit], + [ltrRootMargin, setObserverInit, win], ); return { setObserverList, setObserverInit: setObserverInitExternal, observer }; From eb11579ecbd8dbcd68cf999344f36966d21290b6 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 4 Apr 2024 11:25:59 -0700 Subject: [PATCH 04/25] change files --- ...eslint-plugin-ea97d70f-085e-47bb-a6ac-6faba3f09cfc.json | 7 +++++++ ...ui-react-menu-85b57efe-b104-402f-ac09-c78d05848110.json | 7 +++++++ ...react-popover-83cdc4d1-3431-4143-8e7a-e9afae638791.json | 7 +++++++ ...i-react-table-ddd67de8-e1fd-4ab5-b0b5-40f161357694.json | 7 +++++++ ...i-react-toast-fbabe77b-dce1-41c3-9760-2e0786626a05.json | 7 +++++++ ...act-utilities-c872de02-afc7-4b9a-9150-71eec49dcd12.json | 7 +++++++ ...t-virtualizer-80a7f92f-f93e-41be-8cf1-c97f839de717.json | 7 +++++++ 7 files changed, 49 insertions(+) create mode 100644 change/@fluentui-eslint-plugin-ea97d70f-085e-47bb-a6ac-6faba3f09cfc.json create mode 100644 change/@fluentui-react-menu-85b57efe-b104-402f-ac09-c78d05848110.json create mode 100644 change/@fluentui-react-popover-83cdc4d1-3431-4143-8e7a-e9afae638791.json create mode 100644 change/@fluentui-react-table-ddd67de8-e1fd-4ab5-b0b5-40f161357694.json create mode 100644 change/@fluentui-react-toast-fbabe77b-dce1-41c3-9760-2e0786626a05.json create mode 100644 change/@fluentui-react-utilities-c872de02-afc7-4b9a-9150-71eec49dcd12.json create mode 100644 change/@fluentui-react-virtualizer-80a7f92f-f93e-41be-8cf1-c97f839de717.json diff --git a/change/@fluentui-eslint-plugin-ea97d70f-085e-47bb-a6ac-6faba3f09cfc.json b/change/@fluentui-eslint-plugin-ea97d70f-085e-47bb-a6ac-6faba3f09cfc.json new file mode 100644 index 0000000000000..c7c95b7b663ff --- /dev/null +++ b/change/@fluentui-eslint-plugin-ea97d70f-085e-47bb-a6ac-6faba3f09cfc.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: disallow direct access to globals", + "packageName": "@fluentui/eslint-plugin", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-menu-85b57efe-b104-402f-ac09-c78d05848110.json b/change/@fluentui-react-menu-85b57efe-b104-402f-ac09-c78d05848110.json new file mode 100644 index 0000000000000..6e728afaa9fde --- /dev/null +++ b/change/@fluentui-react-menu-85b57efe-b104-402f-ac09-c78d05848110.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: use setTimeout and clearTimeout from Fluent context.", + "packageName": "@fluentui/react-menu", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-popover-83cdc4d1-3431-4143-8e7a-e9afae638791.json b/change/@fluentui-react-popover-83cdc4d1-3431-4143-8e7a-e9afae638791.json new file mode 100644 index 0000000000000..56f224bcb7e49 --- /dev/null +++ b/change/@fluentui-react-popover-83cdc4d1-3431-4143-8e7a-e9afae638791.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: use setTimeout and clearTimeout from Fluent context.", + "packageName": "@fluentui/react-popover", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-table-ddd67de8-e1fd-4ab5-b0b5-40f161357694.json b/change/@fluentui-react-table-ddd67de8-e1fd-4ab5-b0b5-40f161357694.json new file mode 100644 index 0000000000000..a4adf62ebb1c7 --- /dev/null +++ b/change/@fluentui-react-table-ddd67de8-e1fd-4ab5-b0b5-40f161357694.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: use requestAnimationFrame from Fluent context.", + "packageName": "@fluentui/react-table", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-toast-fbabe77b-dce1-41c3-9760-2e0786626a05.json b/change/@fluentui-react-toast-fbabe77b-dce1-41c3-9760-2e0786626a05.json new file mode 100644 index 0000000000000..588604cd8bb73 --- /dev/null +++ b/change/@fluentui-react-toast-fbabe77b-dce1-41c3-9760-2e0786626a05.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: use setTimeout and clearTimeout from Fluent context.", + "packageName": "@fluentui/react-toast", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-utilities-c872de02-afc7-4b9a-9150-71eec49dcd12.json b/change/@fluentui-react-utilities-c872de02-afc7-4b9a-9150-71eec49dcd12.json new file mode 100644 index 0000000000000..a73febd82e95e --- /dev/null +++ b/change/@fluentui-react-utilities-c872de02-afc7-4b9a-9150-71eec49dcd12.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: use globals from Fluent context.", + "packageName": "@fluentui/react-utilities", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-virtualizer-80a7f92f-f93e-41be-8cf1-c97f839de717.json b/change/@fluentui-react-virtualizer-80a7f92f-f93e-41be-8cf1-c97f839de717.json new file mode 100644 index 0000000000000..90a02faefae23 --- /dev/null +++ b/change/@fluentui-react-virtualizer-80a7f92f-f93e-41be-8cf1-c97f839de717.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "fix: use setTimeout and clearTimeout from Fluent context.", + "packageName": "@fluentui/react-virtualizer", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} From c217c18d86475cb8a743e2a5320c9e3cb4f1650b Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 4 Apr 2024 13:35:19 -0700 Subject: [PATCH 05/25] handle undefined window --- .../react-menu/src/components/Menu/useMenu.tsx | 3 +-- .../src/components/MenuPopover/useMenuPopover.ts | 7 +++---- .../src/components/Popover/usePopover.ts | 3 +-- .../src/hooks/useIntersectionObserver.ts | 15 +++++++-------- .../src/hooks/useStaticPagination.ts | 7 ------- 5 files changed, 12 insertions(+), 23 deletions(-) diff --git a/packages/react-components/react-menu/src/components/Menu/useMenu.tsx b/packages/react-components/react-menu/src/components/Menu/useMenu.tsx index 50d6acf3d9674..d579c7aaa2df9 100644 --- a/packages/react-components/react-menu/src/components/Menu/useMenu.tsx +++ b/packages/react-components/react-menu/src/components/Menu/useMenu.tsx @@ -174,8 +174,7 @@ const useMenuOpenState = ( Pick, ) => { const { targetDocument } = useFluent(); - // eslint-disable-next-line no-restricted-globals - const win = targetDocument?.defaultView ?? window; + const win = targetDocument?.defaultView; const parentSetOpen = useMenuContext_unstable(context => context.setOpen); const onOpenChange: MenuProps['onOpenChange'] = useEventCallback((e, data) => state.onOpenChange?.(e, data)); diff --git a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts index acd78fca8c56a..b03f17f2b54af 100644 --- a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts +++ b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts @@ -29,8 +29,7 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< const restoreFocusSourceAttributes = useRestoreFocusSource(); const { dir, targetDocument } = useFluent(); - // eslint-disable-next-line no-restricted-globals - const win = targetDocument?.defaultView ?? window; + const win = targetDocument?.defaultView; const CloseArrowKey = dir === 'ltr' ? ArrowLeft : ArrowRight; // use DOM listener since react events propagate up the react tree @@ -47,7 +46,7 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< dispatchMenuEnterEvent(popoverRef.current as HTMLElement, e); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore #16889 Node setTimeout type leaking - throttleDispatchTimerRef.current = win.setTimeout(() => (canDispatchCustomEventRef.current = true), 250); + throttleDispatchTimerRef.current = win?.setTimeout(() => (canDispatchCustomEventRef.current = true), 250); } }); } @@ -56,7 +55,7 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< ); React.useEffect(() => { - () => win.clearTimeout(throttleDispatchTimerRef.current); + () => win?.clearTimeout(throttleDispatchTimerRef.current); }, [win]); const inline = useMenuContext_unstable(context => context.inline) ?? false; diff --git a/packages/react-components/react-popover/src/components/Popover/usePopover.ts b/packages/react-components/react-popover/src/components/Popover/usePopover.ts index a9fc196da6e93..c297a155e38e9 100644 --- a/packages/react-components/react-popover/src/components/Popover/usePopover.ts +++ b/packages/react-components/react-popover/src/components/Popover/usePopover.ts @@ -37,8 +37,7 @@ export const usePopover_unstable = (props: PopoverProps): PopoverState => { } as const; const { targetDocument } = useFluent(); - // eslint-disable-next-line no-restricted-globals - const win = targetDocument?.defaultView ?? window; + const win = targetDocument?.defaultView; const children = React.Children.toArray(props.children) as React.ReactElement[]; diff --git a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts index d11ee09c2f177..6c20a8a4ecbd3 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts @@ -14,10 +14,10 @@ import { useMutationObserver } from './useMutationObserver'; */ export const getRTLRootMargin = ( ltrRootMargin: string, - win: Window, target?: Element | Document | null | undefined, + win?: Window | null, ): string => { - if (target) { + if (target && win) { // get the computed dir for the target element const newDir = win.getComputedStyle(target as Element).direction; @@ -61,14 +61,13 @@ export const useIntersectionObserver = ( const observer = useRef(); const [observerList, setObserverList] = useState(); const { targetDocument } = useFluent(); - // eslint-disable-next-line no-restricted-globals - const win = targetDocument?.defaultView ?? window; + const win = targetDocument?.defaultView; // set the initial init with corrected margins based on the observed root's calculated reading direction. const [observerInit, setObserverInit] = useState( options && { ...options, - rootMargin: getRTLRootMargin(options.rootMargin ?? '0px', win, options.root as Element), + rootMargin: getRTLRootMargin(options.rootMargin ?? '0px', options.root as Element, win), }, ); @@ -88,7 +87,7 @@ export const useIntersectionObserver = ( ) { setObserverInit({ ...observerInit, - rootMargin: getRTLRootMargin(ltrRootMargin.current, win, observerInit?.root), + rootMargin: getRTLRootMargin(ltrRootMargin.current, observerInit?.root, win), }); } } @@ -113,7 +112,7 @@ export const useIntersectionObserver = ( observer.current = new win.IntersectionObserver(callback, { ...observerInit, - rootMargin: getRTLRootMargin(ltrRootMargin.current, win, observerInit?.root), + rootMargin: getRTLRootMargin(ltrRootMargin.current, observerInit?.root, win), }); // If we have an instance of IO and a list with elements, observer the elements @@ -140,7 +139,7 @@ export const useIntersectionObserver = ( // Call the internal setter to update the value and ensure if our calculated direction is rtl, we flip the margin setObserverInit({ ...newInit, - rootMargin: getRTLRootMargin(ltrRootMargin.current, win, newInit?.root as Element), + rootMargin: getRTLRootMargin(ltrRootMargin.current, newInit?.root as Element, win), }); }, [ltrRootMargin, setObserverInit, win], diff --git a/packages/react-components/react-virtualizer/src/hooks/useStaticPagination.ts b/packages/react-components/react-virtualizer/src/hooks/useStaticPagination.ts index d7899315418dd..9f6116020d7b1 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useStaticPagination.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useStaticPagination.ts @@ -28,13 +28,6 @@ export const useStaticVirtualizerPagination = ( scrollContainer.current = null; clearScrollTimer(); } - }; - - React.useEffect(() => { - return () => { - clearListeners(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); /** From e68e733890d091b377507f5d5141062c3f01256b Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 4 Apr 2024 14:43:18 -0700 Subject: [PATCH 06/25] fix ssr --- .../src/hooks/useAnimationFrame.ts | 10 +++------- .../react-utilities/src/hooks/useTimeout.ts | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/react-components/react-utilities/src/hooks/useAnimationFrame.ts b/packages/react-components/react-utilities/src/hooks/useAnimationFrame.ts index a3866d161fced..5c7c318374ad5 100644 --- a/packages/react-components/react-utilities/src/hooks/useAnimationFrame.ts +++ b/packages/react-components/react-utilities/src/hooks/useAnimationFrame.ts @@ -1,4 +1,3 @@ -import { canUseDOM } from '../ssr/canUseDOM'; import { useBrowserTimer } from './useBrowserTimer'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; @@ -17,14 +16,11 @@ const cancelAnimationFrameNoop = (handle: number) => handle; * @returns A pair of [requestAnimationFrame, cancelAnimationFrame] that are stable between renders. */ export function useAnimationFrame() { - const isDOM = canUseDOM(); - const { targetDocument } = useFluent(); - // eslint-disable-next-line no-restricted-globals - const win = targetDocument?.defaultView ?? window; + const win = targetDocument?.defaultView; - const setAnimationFrame = isDOM ? win.requestAnimationFrame : setAnimationFrameNoop; - const clearAnimationFrame = isDOM ? win.cancelAnimationFrame : cancelAnimationFrameNoop; + const setAnimationFrame = win ? win.requestAnimationFrame : setAnimationFrameNoop; + const clearAnimationFrame = win ? win.cancelAnimationFrame : cancelAnimationFrameNoop; return useBrowserTimer(setAnimationFrame, clearAnimationFrame); } diff --git a/packages/react-components/react-utilities/src/hooks/useTimeout.ts b/packages/react-components/react-utilities/src/hooks/useTimeout.ts index d140ccde78bbd..5d3f931547448 100644 --- a/packages/react-components/react-utilities/src/hooks/useTimeout.ts +++ b/packages/react-components/react-utilities/src/hooks/useTimeout.ts @@ -1,6 +1,13 @@ import { useBrowserTimer } from './useBrowserTimer'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; +const setTimeoutNoop = (callback: Function) => { + callback(); + return 0; +}; + +const clearTimeoutNoop = (handle: number) => handle; + /** * @internal * Helper to manage a browser timeout. @@ -11,8 +18,10 @@ import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts */ export function useTimeout() { const { targetDocument } = useFluent(); - // eslint-disable-next-line no-restricted-globals - const win = targetDocument?.defaultView ?? window; + const win = targetDocument?.defaultView; + + const setTimerFn = win ? win.setTimeout : setTimeoutNoop; + const clearTimerFn = win ? win.clearTimeout : clearTimeoutNoop; - return useBrowserTimer(win.setTimeout, win.clearTimeout); + return useBrowserTimer(setTimerFn, clearTimerFn); } From 0c5229480eaf322b8ddb6850159cabb886d22ede Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 4 Apr 2024 18:15:56 -0700 Subject: [PATCH 07/25] mock window --- .../src/AriaLiveAnnouncer/useAriaLiveAnnouncer.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-components/react-aria/src/AriaLiveAnnouncer/useAriaLiveAnnouncer.test.tsx b/packages/react-components/react-aria/src/AriaLiveAnnouncer/useAriaLiveAnnouncer.test.tsx index e9fb4577be23b..f042a714b8a5f 100644 --- a/packages/react-components/react-aria/src/AriaLiveAnnouncer/useAriaLiveAnnouncer.test.tsx +++ b/packages/react-components/react-aria/src/AriaLiveAnnouncer/useAriaLiveAnnouncer.test.tsx @@ -17,6 +17,7 @@ describe('useAriaLiveAnnouncer', () => { body: { append: jest.fn(), }, + defaultView: global, } as unknown as Document; const ContextWrapper: React.FC = props => { return {props.children}; From e0ed00af7696c64f1de8414d3be308bed6f1fd38 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 17 Apr 2024 16:32:37 -0700 Subject: [PATCH 08/25] change restricted globals, disable lint rules, rollback previous changes Changes restricted globals to a minimal, curated list of things we want to exclude. Rolls back some previous changes in favor of disabling the issues via eslint exceptions. Codeowners will make the actual changes as appropriate. --- .../src/configs/restricted-globals.js | 222 +----------------- .../src/createResizeObserver.ts | 1 + .../src/components/Menu/useMenu.tsx | 1 - .../components/MenuPopover/useMenuPopover.ts | 13 +- .../MessageBar/useMessageBarReflow.ts | 1 + .../src/components/Popover/usePopover.ts | 2 - .../src/hooks/useMeasureElement.ts | 1 + .../components/Virtualizer/useVirtualizer.ts | 1 + .../src/hooks/hooks.types.ts | 7 +- .../src/hooks/useDynamicPagination.ts | 3 - .../src/hooks/useDynamicVirtualizerMeasure.ts | 1 + .../src/hooks/useIntersectionObserver.ts | 2 + .../src/hooks/useResizeObserverRef.ts | 1 + .../src/hooks/useVirtualizerMeasure.ts | 1 + 14 files changed, 27 insertions(+), 230 deletions(-) diff --git a/packages/eslint-plugin/src/configs/restricted-globals.js b/packages/eslint-plugin/src/configs/restricted-globals.js index 513d23f1aff6f..046a94716c9e7 100644 --- a/packages/eslint-plugin/src/configs/restricted-globals.js +++ b/packages/eslint-plugin/src/configs/restricted-globals.js @@ -1,239 +1,27 @@ -// Via: https://developer.mozilla.org/en-US/docs/Web/API/Window -const windowKeys = [ - // Instance properties - 'caches', - 'clientInformation', - 'closed', - // 'console', // `console` is already disallowed via a different rule - 'credentialless', - 'crypto', +// A list of globals that are problematic when using multiple windows. +const restrictedGlobals = [ 'customElements', 'devicePixelRatio', - 'frameElement', - 'frames', - 'fullScreen', - 'history', - 'indexedDB', - 'innerHeight', - 'innerWidth', - 'isSecureContext', - 'launchQueue', - 'length', - 'localStorage', 'location', - 'locationbar', - 'menubar', - 'name', - 'navigation', 'navigator', - 'opener', - 'origin', - 'outerHeight', - 'outerWidth', - 'pageXOffset', - 'pageYOffset', - 'parent', 'performance', - 'personalbar', - 'scheduler', - 'screen', - 'screenX', - 'screenY', - 'scrollbars', - 'scrollX', - 'scrollY', - 'self', - 'sessionStorage', - 'speechSynthesis', - 'statusbar', - 'toolbar', - 'top', - 'visualViewport', - 'window', - // Deprecated properties - 'defaultStatus', - 'event', - 'external', - 'orientation', - 'returnValue', - 'sidebar', - 'status', - - // Instance methods - 'addEventListener', - 'alert', - 'atob', - 'blur', - 'btoa', 'cancelAnimationFrame', 'cancelIdleCallback', 'clearImmediate', 'clearInterval', 'clearTimeout', - 'close', - 'confirm', - 'createImageBitmap', - 'dispatchEvent', - 'dump', 'fetch', - 'find', - 'focus', 'getComputedStyle', - 'getSelection', 'matchMedia', - 'moveBy', - 'moveTo', - 'open', - 'postMessage', - 'print', - 'prompt', - 'queryLocalFonts', - 'removeEventListener', - 'reportError', 'requestAnimationFrame', 'requestIdleCallback', - 'resizeBy', - 'resizeTo', - 'scroll', - 'scrollBy', - 'scrollTo', 'setImmediate', 'setInterval', 'setTimeout', - 'showDirectoryPicker', - 'showOpenFilePicker', - 'showSaveFilePicker', - 'stop', - 'updateCommands', - - // Deprecated methods - 'back', - 'captureEvents', - 'forward', - 'releaseEvents', - 'showModalDialog', - // Events - 'onabort', - 'onafterprint', - 'onanimationend', - 'onanimationiteration', - 'onanimationstart', - 'onappinstalled', - 'onauxclick', - 'onbeforeinput', - 'onbeforeinstallprompt', - 'onbeforematch', - 'onbeforeprint', - 'onbeforetoggle', - 'onbeforeunload', - 'onbeforexrselect', - 'onblur', - 'oncancel', - 'oncanplay', - 'oncanplaythrough', - 'onchange', - 'onclick', - 'onclose', - 'oncontentvisibilityautostatechange', - 'oncontextlost', - 'oncontextmenu', - 'oncontextrestored', - 'oncuechange', - 'ondblclick', - 'ondevicemotion', - 'ondeviceorientation', - 'ondeviceorientationabsolute', - 'ondrag', - 'ondragend', - 'ondragenter', - 'ondragleave', - 'ondragover', - 'ondragstart', - 'ondrop', - 'ondurationchange', - 'onemptied', - 'onended', - 'onerror', - 'onfocus', - 'onformdata', - 'ongotpointercapture', - 'onhashchange', - 'oninput', - 'oninvalid', - 'onkeydown', - 'onkeypress', - 'onkeyup', - 'onlanguagechange', - 'onload', - 'onloadeddata', - 'onloadedmetadata', - 'onloadstart', - 'onlostpointercapture', - 'onmessage', - 'onmessageerror', - 'onmousedown', - 'onmouseenter', - 'onmouseleave', - 'onmousemove', - 'onmouseout', - 'onmouseover', - 'onmouseup', - 'onmousewheel', - 'onoffline', - 'ononline', - 'onoverscroll', - 'onpagehide', - 'onpageshow', - 'onpause', - 'onplay', - 'onplaying', - 'onpointercancel', - 'onpointerdown', - 'onpointerenter', - 'onpointerleave', - 'onpointermove', - 'onpointerout', - 'onpointerover', - 'onpointerrawupdate', - 'onpointerup', - 'onpopstate', - 'onprogress', - 'onratechange', - 'onrejectionhandled', - 'onreset', - 'onresize', - 'onscroll', - 'onscrollend', - 'onsearch', - 'onsecuritypolicyviolation', - 'onseeked', - 'onseeking', - 'onselect', - 'onselectionchange', - 'onselectstart', - 'onslotchange', - 'onstalled', - 'onstorage', - 'onsubmit', - 'onsuspend', - 'ontimeupdate', - 'ontimezonechange', - 'ontoggle', - 'ontransitioncancel', - 'ontransitionend', - 'ontransitionrun', - 'ontransitionstart', - 'onunhandledrejection', - 'onunload', - 'onvolumechange', - 'onwaiting', - 'onwebkitanimationend', - 'onwebkitanimationiteration', - 'onwebkitanimationstart', - 'onwebkittransitionend', - 'onwheel', + 'ResizeObserver', + 'IntersectionObserver', ]; const reactLegacy = [ @@ -258,7 +46,7 @@ const react = [ name: 'document', message: 'Get a reference to `document` from `useFluent()`.', }, - ...windowKeys.map(key => { + ...restrictedGlobals.map(key => { return { name: key, message: `Get a reference to \`window\` from \`useFluent()\` and access \`${key}\` from there.`, diff --git a/packages/react-components/priority-overflow/src/createResizeObserver.ts b/packages/react-components/priority-overflow/src/createResizeObserver.ts index 7e8a7597c946d..a9563af5f3e08 100644 --- a/packages/react-components/priority-overflow/src/createResizeObserver.ts +++ b/packages/react-components/priority-overflow/src/createResizeObserver.ts @@ -15,6 +15,7 @@ export function observeResize(elementToObserve: HTMLElement, callback: ResizeObs return () => null; } + // eslint-disable-next-line no-restricted-globals let resizeObserver: ResizeObserver | undefined = new GlobalResizeObsever(callback); resizeObserver.observe(elementToObserve); diff --git a/packages/react-components/react-menu/src/components/Menu/useMenu.tsx b/packages/react-components/react-menu/src/components/Menu/useMenu.tsx index d579c7aaa2df9..507b8f072ccad 100644 --- a/packages/react-components/react-menu/src/components/Menu/useMenu.tsx +++ b/packages/react-components/react-menu/src/components/Menu/useMenu.tsx @@ -174,7 +174,6 @@ const useMenuOpenState = ( Pick, ) => { const { targetDocument } = useFluent(); - const win = targetDocument?.defaultView; const parentSetOpen = useMenuContext_unstable(context => context.setOpen); const onOpenChange: MenuProps['onOpenChange'] = useEventCallback((e, data) => state.onOpenChange?.(e, data)); diff --git a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts index b03f17f2b54af..d554374f90a63 100644 --- a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts +++ b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts @@ -28,8 +28,7 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< const throttleDispatchTimerRef = React.useRef(0); const restoreFocusSourceAttributes = useRestoreFocusSource(); - const { dir, targetDocument } = useFluent(); - const win = targetDocument?.defaultView; + const { dir } = useFluent(); const CloseArrowKey = dir === 'ltr' ? ArrowLeft : ArrowRight; // use DOM listener since react events propagate up the react tree @@ -46,17 +45,19 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< dispatchMenuEnterEvent(popoverRef.current as HTMLElement, e); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore #16889 Node setTimeout type leaking - throttleDispatchTimerRef.current = win?.setTimeout(() => (canDispatchCustomEventRef.current = true), 250); + // eslint-disable-next-line no-restricted-globals + throttleDispatchTimerRef.current = setTimeout(() => (canDispatchCustomEventRef.current = true), 250); } }); } }, - [popoverRef, throttleDispatchTimerRef, win], + [popoverRef, throttleDispatchTimerRef], ); React.useEffect(() => { - () => win?.clearTimeout(throttleDispatchTimerRef.current); - }, [win]); + // eslint-disable-next-line no-restricted-globals + () => clearTimeout(throttleDispatchTimerRef.current); + }, []); const inline = useMenuContext_unstable(context => context.inline) ?? false; const mountNode = useMenuContext_unstable(context => context.mountNode); diff --git a/packages/react-components/react-message-bar/src/components/MessageBar/useMessageBarReflow.ts b/packages/react-components/react-message-bar/src/components/MessageBar/useMessageBarReflow.ts index b113ebfcbfeff..5d833058b4fd4 100644 --- a/packages/react-components/react-message-bar/src/components/MessageBar/useMessageBarReflow.ts +++ b/packages/react-components/react-message-bar/src/components/MessageBar/useMessageBarReflow.ts @@ -6,6 +6,7 @@ export function useMessageBarReflow(enabled: boolean = false) { const { targetDocument } = useFluent(); const forceUpdate = React.useReducer(() => ({}), {})[1]; const reflowingRef = React.useRef(false); + // eslint-disable-next-line no-restricted-globals const resizeObserverRef = React.useRef(null); const prevInlineSizeRef = React.useRef(-1); diff --git a/packages/react-components/react-popover/src/components/Popover/usePopover.ts b/packages/react-components/react-popover/src/components/Popover/usePopover.ts index c297a155e38e9..0457a9cf6cbfa 100644 --- a/packages/react-components/react-popover/src/components/Popover/usePopover.ts +++ b/packages/react-components/react-popover/src/components/Popover/usePopover.ts @@ -37,8 +37,6 @@ export const usePopover_unstable = (props: PopoverProps): PopoverState => { } as const; const { targetDocument } = useFluent(); - const win = targetDocument?.defaultView; - const children = React.Children.toArray(props.children) as React.ReactElement[]; if (process.env.NODE_ENV !== 'production') { diff --git a/packages/react-components/react-table/src/hooks/useMeasureElement.ts b/packages/react-components/react-table/src/hooks/useMeasureElement.ts index 2ab924c47ae7c..4fa2d4ca599e7 100644 --- a/packages/react-components/react-table/src/hooks/useMeasureElement.ts +++ b/packages/react-components/react-table/src/hooks/useMeasureElement.ts @@ -11,6 +11,7 @@ export function useMeasureElement() const [width, setWidth] = React.useState(0); const container = React.useRef(undefined); + // eslint-disable-next-line no-restricted-globals const resizeObserverRef = React.useRef(null); const { targetDocument } = useFluent(); diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index d07de5b243aab..d063d37363c75 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -121,6 +121,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Observe intersections of virtualized components const { setObserverList } = useIntersectionObserver( + // eslint-disable-next-line no-restricted-globals (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { /* Sanity check - do we even need virtualization? */ if (virtualizerLength > numItems) { diff --git a/packages/react-components/react-virtualizer/src/hooks/hooks.types.ts b/packages/react-components/react-virtualizer/src/hooks/hooks.types.ts index 436c40bdcf5c7..e8e4e553e06ab 100644 --- a/packages/react-components/react-virtualizer/src/hooks/hooks.types.ts +++ b/packages/react-components/react-virtualizer/src/hooks/hooks.types.ts @@ -46,5 +46,10 @@ export type VirtualizerDynamicPaginationProps = { * Backwards compatible with ResizeObserverCallback if preferred */ export interface ResizeCallbackWithRef { - (entries: ResizeObserverEntry[], observer: ResizeObserver, scrollRef?: MutableRefObject): void; + ( + entries: ResizeObserverEntry[], + // eslint-disable-next-line no-restricted-globals + observer: ResizeObserver, + scrollRef?: MutableRefObject, + ): void; } diff --git a/packages/react-components/react-virtualizer/src/hooks/useDynamicPagination.ts b/packages/react-components/react-virtualizer/src/hooks/useDynamicPagination.ts index 2e3e4fe6ecf88..4ba4c634b25f7 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useDynamicPagination.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useDynamicPagination.ts @@ -16,9 +16,6 @@ export const useDynamicVirtualizerPagination = ( const { axis = 'vertical', currentIndex, progressiveItemSizes, virtualizerLength } = virtualizerProps; const [setScrollTimer, clearScrollTimer] = useTimeout(); - const lastScrollPos = useRef(-1); - const lastIndexScrolled = useRef(-1); - const scrollContainer = React.useRef(null); const clearListeners = () => { diff --git a/packages/react-components/react-virtualizer/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useDynamicVirtualizerMeasure.ts index 91302fe36d734..f97ae38cef139 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useDynamicVirtualizerMeasure.ts @@ -78,6 +78,7 @@ export const useDynamicVirtualizerMeasure = ( const resizeCallback = React.useCallback( ( _entries: ResizeObserverEntry[], + // eslint-disable-next-line no-restricted-globals _observer: ResizeObserver, scrollRef?: React.MutableRefObject, ) => { diff --git a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts index 6c20a8a4ecbd3..1cc44419b8eac 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts @@ -56,8 +56,10 @@ export const useIntersectionObserver = ( ): { setObserverList: Dispatch>; setObserverInit: (newInit: IntersectionObserverInit | undefined) => void; + // eslint-disable-next-line no-restricted-globals observer: MutableRefObject; } => { + // eslint-disable-next-line no-restricted-globals const observer = useRef(); const [observerList, setObserverList] = useState(); const { targetDocument } = useFluent(); diff --git a/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts b/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts index 3de686b456aed..75ba53494e4ba 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts @@ -11,6 +11,7 @@ export const useResizeObserverRef_unstable = (resizeCallback: ResizeCallbackWith const { targetDocument } = useFluent(); const container = React.useRef(null); // the handler for resize observer + // eslint-disable-next-line no-restricted-globals const handleResize = debounce((entries: ResizeObserverEntry[], observer: ResizeObserver) => { resizeCallback(entries, observer, container); }); diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 091a1d074a555..51c811103c377 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -26,6 +26,7 @@ export const useStaticVirtualizerMeasure = ( const resizeCallback = React.useCallback( ( _entries: ResizeObserverEntry[], + // eslint-disable-next-line no-restricted-globals _observer: ResizeObserver, scrollRef?: React.MutableRefObject, ) => { From 2b63a9ea8f12f7755712e337ad52af0354018e5e Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 17 Apr 2024 16:35:47 -0700 Subject: [PATCH 09/25] change files --- ...rity-overflow-3c4d0234-bd4e-473b-9d6f-b4a89e85eb7e.json | 7 +++++++ ...t-message-bar-e51754db-df15-41cc-9872-123edeb4990e.json | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 change/@fluentui-priority-overflow-3c4d0234-bd4e-473b-9d6f-b4a89e85eb7e.json create mode 100644 change/@fluentui-react-message-bar-e51754db-df15-41cc-9872-123edeb4990e.json diff --git a/change/@fluentui-priority-overflow-3c4d0234-bd4e-473b-9d6f-b4a89e85eb7e.json b/change/@fluentui-priority-overflow-3c4d0234-bd4e-473b-9d6f-b4a89e85eb7e.json new file mode 100644 index 0000000000000..75b74abda6919 --- /dev/null +++ b/change/@fluentui-priority-overflow-3c4d0234-bd4e-473b-9d6f-b4a89e85eb7e.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disable eslint rule", + "packageName": "@fluentui/priority-overflow", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-message-bar-e51754db-df15-41cc-9872-123edeb4990e.json b/change/@fluentui-react-message-bar-e51754db-df15-41cc-9872-123edeb4990e.json new file mode 100644 index 0000000000000..bd03f29aab3f5 --- /dev/null +++ b/change/@fluentui-react-message-bar-e51754db-df15-41cc-9872-123edeb4990e.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disable eslint rule", + "packageName": "@fluentui/react-message-bar", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} From c7cd9c159bbac2d036ebb256a35262a657c65943 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 17 Apr 2024 16:43:27 -0700 Subject: [PATCH 10/25] correct change files --- ...luentui-react-menu-85b57efe-b104-402f-ac09-c78d05848110.json | 2 +- ...ntui-react-popover-83cdc4d1-3431-4143-8e7a-e9afae638791.json | 2 +- ...uentui-react-toast-fbabe77b-dce1-41c3-9760-2e0786626a05.json | 2 +- ...-react-virtualizer-80a7f92f-f93e-41be-8cf1-c97f839de717.json | 2 +- .../react-popover/src/components/Popover/usePopover.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/change/@fluentui-react-menu-85b57efe-b104-402f-ac09-c78d05848110.json b/change/@fluentui-react-menu-85b57efe-b104-402f-ac09-c78d05848110.json index 6e728afaa9fde..de115b0b1edb9 100644 --- a/change/@fluentui-react-menu-85b57efe-b104-402f-ac09-c78d05848110.json +++ b/change/@fluentui-react-menu-85b57efe-b104-402f-ac09-c78d05848110.json @@ -1,6 +1,6 @@ { "type": "patch", - "comment": "fix: use setTimeout and clearTimeout from Fluent context.", + "comment": "chore: disable eslint rule.", "packageName": "@fluentui/react-menu", "email": "seanmonahan@microsoft.com", "dependentChangeType": "patch" diff --git a/change/@fluentui-react-popover-83cdc4d1-3431-4143-8e7a-e9afae638791.json b/change/@fluentui-react-popover-83cdc4d1-3431-4143-8e7a-e9afae638791.json index 56f224bcb7e49..de27a9da023be 100644 --- a/change/@fluentui-react-popover-83cdc4d1-3431-4143-8e7a-e9afae638791.json +++ b/change/@fluentui-react-popover-83cdc4d1-3431-4143-8e7a-e9afae638791.json @@ -1,6 +1,6 @@ { "type": "patch", - "comment": "fix: use setTimeout and clearTimeout from Fluent context.", + "comment": "chore: disable eslint rule.", "packageName": "@fluentui/react-popover", "email": "seanmonahan@microsoft.com", "dependentChangeType": "patch" diff --git a/change/@fluentui-react-toast-fbabe77b-dce1-41c3-9760-2e0786626a05.json b/change/@fluentui-react-toast-fbabe77b-dce1-41c3-9760-2e0786626a05.json index 588604cd8bb73..29fc9752fcccb 100644 --- a/change/@fluentui-react-toast-fbabe77b-dce1-41c3-9760-2e0786626a05.json +++ b/change/@fluentui-react-toast-fbabe77b-dce1-41c3-9760-2e0786626a05.json @@ -1,6 +1,6 @@ { "type": "patch", - "comment": "fix: use setTimeout and clearTimeout from Fluent context.", + "comment": "chore: disable eslint rule.", "packageName": "@fluentui/react-toast", "email": "seanmonahan@microsoft.com", "dependentChangeType": "patch" diff --git a/change/@fluentui-react-virtualizer-80a7f92f-f93e-41be-8cf1-c97f839de717.json b/change/@fluentui-react-virtualizer-80a7f92f-f93e-41be-8cf1-c97f839de717.json index 90a02faefae23..8d674474a3d57 100644 --- a/change/@fluentui-react-virtualizer-80a7f92f-f93e-41be-8cf1-c97f839de717.json +++ b/change/@fluentui-react-virtualizer-80a7f92f-f93e-41be-8cf1-c97f839de717.json @@ -1,6 +1,6 @@ { "type": "prerelease", - "comment": "fix: use setTimeout and clearTimeout from Fluent context.", + "comment": "fix: update internal function to optionally use global Fluent context.", "packageName": "@fluentui/react-virtualizer", "email": "seanmonahan@microsoft.com", "dependentChangeType": "patch" diff --git a/packages/react-components/react-popover/src/components/Popover/usePopover.ts b/packages/react-components/react-popover/src/components/Popover/usePopover.ts index 0457a9cf6cbfa..74a57079fa280 100644 --- a/packages/react-components/react-popover/src/components/Popover/usePopover.ts +++ b/packages/react-components/react-popover/src/components/Popover/usePopover.ts @@ -36,7 +36,6 @@ export const usePopover_unstable = (props: PopoverProps): PopoverState => { ...props, } as const; - const { targetDocument } = useFluent(); const children = React.Children.toArray(props.children) as React.ReactElement[]; if (process.env.NODE_ENV !== 'production') { @@ -90,6 +89,7 @@ export const usePopover_unstable = (props: PopoverProps): PopoverState => { ); const positioningRefs = usePopoverRefs(initialState); + const { targetDocument } = useFluent(); useOnClickOutside({ contains: elementContains, From c658164438d8580aeff4b25c30229e632b925dd3 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 6 May 2024 14:30:26 -0700 Subject: [PATCH 11/25] fix rebase conflicts --- .../src/components/Virtualizer/useVirtualizer.ts | 6 +++++- .../react-virtualizer/src/hooks/useDynamicPagination.ts | 3 +++ .../react-virtualizer/src/hooks/useStaticPagination.ts | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index d063d37363c75..ed1ed221babb2 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -111,7 +111,12 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta initializeScrollingTimer(); }, [actualIndex, initializeScrollingTimer]); + const batchUpdateNewIndex = (index: number) => { // Local updates + updateChildRows(index); + updateCurrentItemSizes(index); + + // Set before 'setActualIndex' call // If it changes before render, or injected via context, re-render will update ref. actualIndexRef.current = index; @@ -121,7 +126,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Observe intersections of virtualized components const { setObserverList } = useIntersectionObserver( - // eslint-disable-next-line no-restricted-globals (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { /* Sanity check - do we even need virtualization? */ if (virtualizerLength > numItems) { diff --git a/packages/react-components/react-virtualizer/src/hooks/useDynamicPagination.ts b/packages/react-components/react-virtualizer/src/hooks/useDynamicPagination.ts index 4ba4c634b25f7..2e3e4fe6ecf88 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useDynamicPagination.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useDynamicPagination.ts @@ -16,6 +16,9 @@ export const useDynamicVirtualizerPagination = ( const { axis = 'vertical', currentIndex, progressiveItemSizes, virtualizerLength } = virtualizerProps; const [setScrollTimer, clearScrollTimer] = useTimeout(); + const lastScrollPos = useRef(-1); + const lastIndexScrolled = useRef(-1); + const scrollContainer = React.useRef(null); const clearListeners = () => { diff --git a/packages/react-components/react-virtualizer/src/hooks/useStaticPagination.ts b/packages/react-components/react-virtualizer/src/hooks/useStaticPagination.ts index 9f6116020d7b1..d7899315418dd 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useStaticPagination.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useStaticPagination.ts @@ -28,6 +28,13 @@ export const useStaticVirtualizerPagination = ( scrollContainer.current = null; clearScrollTimer(); } + }; + + React.useEffect(() => { + return () => { + clearListeners(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); /** From 345f5b28d6b98a08d5f25bd60e2984b2162f41d5 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 6 May 2024 14:30:39 -0700 Subject: [PATCH 12/25] use culled globals list --- packages/eslint-plugin/src/configs/restricted-globals.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/configs/restricted-globals.js b/packages/eslint-plugin/src/configs/restricted-globals.js index 046a94716c9e7..0a931330df035 100644 --- a/packages/eslint-plugin/src/configs/restricted-globals.js +++ b/packages/eslint-plugin/src/configs/restricted-globals.js @@ -20,8 +20,9 @@ const restrictedGlobals = [ 'setInterval', 'setTimeout', - 'ResizeObserver', 'IntersectionObserver', + 'MutationObserver', + 'ResizeObserver', ]; const reactLegacy = [ From 1d59831fa74ee5ed00e89deb832c6195de839696 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 6 May 2024 14:59:06 -0700 Subject: [PATCH 13/25] fix lint in react-tabster --- .../react-tabster/src/tabster-types-6.0.1-do-not-use.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-components/react-tabster/src/tabster-types-6.0.1-do-not-use.ts b/packages/react-components/react-tabster/src/tabster-types-6.0.1-do-not-use.ts index 5c58d65d790cb..fa9f531c2b906 100644 --- a/packages/react-components/react-tabster/src/tabster-types-6.0.1-do-not-use.ts +++ b/packages/react-components/react-tabster/src/tabster-types-6.0.1-do-not-use.ts @@ -123,6 +123,7 @@ export interface TabsterCoreProps { } export interface DOMAPI { + // eslint-disable-next-line no-restricted-globals createMutationObserver: (callback: MutationCallback) => MutationObserver; createTreeWalker(doc: Document, root: Node, whatToShow?: number, filter?: NodeFilter | null): TreeWalker; getParentNode(node: Node | null | undefined): ParentNode | null; @@ -744,7 +745,6 @@ export const MoverKeys: MoverKeys = { End: 8, }; -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface MoverAPI extends MoverAPIInternal, Disposable { /** @internal (will likely be exposed once the API is fully stable) */ moveFocus(fromElement: HTMLElement, key: MoverKey): HTMLElement | null; @@ -794,7 +794,6 @@ export interface GroupperAPIInternal { handleKeyPress(element: HTMLElement, event: KeyboardEvent, fromModalizer?: boolean): void; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface GroupperAPI extends GroupperAPIInternal, Disposable { /** @internal (will likely be exposed once the API is fully stable) */ moveFocus(element: HTMLElement, action: GroupperMoveFocusAction): HTMLElement | null; From 6127be582fb1f0c5f7cb9d9278916b6ce7430607 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 6 May 2024 16:03:17 -0700 Subject: [PATCH 14/25] change file --- ...react-tabster-fcd4a805-ad5d-4f87-b659-ba102deedd56.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-tabster-fcd4a805-ad5d-4f87-b659-ba102deedd56.json diff --git a/change/@fluentui-react-tabster-fcd4a805-ad5d-4f87-b659-ba102deedd56.json b/change/@fluentui-react-tabster-fcd4a805-ad5d-4f87-b659-ba102deedd56.json new file mode 100644 index 0000000000000..ee2df09007aa1 --- /dev/null +++ b/change/@fluentui-react-tabster-fcd4a805-ad5d-4f87-b659-ba102deedd56.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "update eslint comments", + "packageName": "@fluentui/react-tabster", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "none" +} From e6795fd3e48360de56e84f68f003ab1e5d287073 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 6 May 2024 16:46:04 -0700 Subject: [PATCH 15/25] fix: update `scrollIntoView` to accept option "window" argument This allows scrollIntoView to reference the correct window when calling "getComputedStyle()". --- .../activedescendant/scrollIntoView.test.ts | 32 +++++++++++++++++++ .../src/activedescendant/scrollIntoView.ts | 11 ++++--- .../activedescendant/useActiveDescendant.ts | 4 ++- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/packages/react-components/react-aria/src/activedescendant/scrollIntoView.test.ts b/packages/react-components/react-aria/src/activedescendant/scrollIntoView.test.ts index 7d33854079d96..9188534869416 100644 --- a/packages/react-components/react-aria/src/activedescendant/scrollIntoView.test.ts +++ b/packages/react-components/react-aria/src/activedescendant/scrollIntoView.test.ts @@ -187,4 +187,36 @@ describe('scrollIntoView', () => { expect(mockAncestorScrollTo).toHaveBeenCalledWith(0, 122); }); + + it('should use the provided `window` argument', () => { + listboxGrandParent = { + ...listboxGrandParent, + scrollTop: 0, + }; + listboxParent = { + ...listboxParent, + offsetParent: listboxGrandParent as Element, + parentElement: listboxGrandParent as HTMLElement, + }; + listbox = { + ...listbox, + scrollHeight: 100, + offsetHeight: 100, + offsetParent: listboxGrandParent as Element, + parentElement: listboxParent as HTMLElement, + }; + const option: Partial = { + offsetHeight: 10, + offsetTop: 160, + offsetParent: listboxGrandParent as HTMLElement, + parentElement: listbox as HTMLElement, + contains: jest.fn().mockReturnValue(false), + }; + + const getComputedStyleSpy = jest.spyOn(window, 'getComputedStyle'); + + scrollIntoView(option as HTMLElement); + + expect(getComputedStyleSpy).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/react-components/react-aria/src/activedescendant/scrollIntoView.ts b/packages/react-components/react-aria/src/activedescendant/scrollIntoView.ts index c415d4b2c3ba0..3ca94fe44b16e 100644 --- a/packages/react-components/react-aria/src/activedescendant/scrollIntoView.ts +++ b/packages/react-components/react-aria/src/activedescendant/scrollIntoView.ts @@ -1,8 +1,11 @@ -export const scrollIntoView = (target: HTMLElement | null | undefined) => { +export const scrollIntoView = (target: HTMLElement | null | undefined, winArg?: Window | null) => { if (!target) { return; } + // eslint-disable-next-line no-restricted-globals + const win = winArg ?? window; + const scrollParent = findScrollableParent(target.parentElement as HTMLElement); if (!scrollParent) { return; @@ -11,7 +14,7 @@ export const scrollIntoView = (target: HTMLElement | null | undefined) => { const { offsetHeight } = target; const offsetTop = getTotalOffsetTop(target, scrollParent); - const { scrollMarginTop, scrollMarginBottom } = getScrollMargins(target); + const { scrollMarginTop, scrollMarginBottom } = getScrollMargins(target, win); const { offsetHeight: parentOffsetHeight, scrollTop } = scrollParent; @@ -52,8 +55,8 @@ const getTotalOffsetTop = (element: HTMLElement, scrollParent: HTMLElement): num return element.offsetTop + getTotalOffsetTop(element.offsetParent as HTMLElement, scrollParent); }; -const getScrollMargins = (element: HTMLElement) => { - const computedStyles = getComputedStyle(element); +const getScrollMargins = (element: HTMLElement, win: Window) => { + const computedStyles = win.getComputedStyle(element); const scrollMarginTop = getIntValueOfComputedStyle(computedStyles.scrollMarginTop) ?? getIntValueOfComputedStyle(computedStyles.scrollMarginBlockStart); diff --git a/packages/react-components/react-aria/src/activedescendant/useActiveDescendant.ts b/packages/react-components/react-aria/src/activedescendant/useActiveDescendant.ts index 0512298e8e968..43f906ccd571c 100644 --- a/packages/react-components/react-aria/src/activedescendant/useActiveDescendant.ts +++ b/packages/react-components/react-aria/src/activedescendant/useActiveDescendant.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; import { useEventCallback, useMergedRefs } from '@fluentui/react-utilities'; import { useOnKeyboardNavigationChange } from '@fluentui/react-tabster'; import { useOptionWalker } from './useOptionWalker'; @@ -32,6 +33,7 @@ export function useActiveDescendant(null); const activeParentRef = React.useRef(null); const attributeVisibilityRef = React.useRef(true); + const { targetDocument } = useFluent(); const removeAttribute = React.useCallback(() => { activeParentRef.current?.removeAttribute('aria-activedescendant'); @@ -87,7 +89,7 @@ export function useActiveDescendant Date: Tue, 7 May 2024 09:17:03 -0700 Subject: [PATCH 16/25] change files --- ...ui-react-aria-a93678b3-8ffb-47d5-9d3b-1fbf8b4ba011.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-aria-a93678b3-8ffb-47d5-9d3b-1fbf8b4ba011.json diff --git a/change/@fluentui-react-aria-a93678b3-8ffb-47d5-9d3b-1fbf8b4ba011.json b/change/@fluentui-react-aria-a93678b3-8ffb-47d5-9d3b-1fbf8b4ba011.json new file mode 100644 index 0000000000000..97c92988994b2 --- /dev/null +++ b/change/@fluentui-react-aria-a93678b3-8ffb-47d5-9d3b-1fbf8b4ba011.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "add optional `window` argument to `scrollIntoView`.", + "packageName": "@fluentui/react-aria", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} From 6a810414162fa02f473912d9c5017912e6fd698c Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 7 May 2024 10:41:14 -0700 Subject: [PATCH 17/25] fix useCallback deps --- .../react-aria/src/activedescendant/useActiveDescendant.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-aria/src/activedescendant/useActiveDescendant.ts b/packages/react-components/react-aria/src/activedescendant/useActiveDescendant.ts index 43f906ccd571c..33413f848f42a 100644 --- a/packages/react-components/react-aria/src/activedescendant/useActiveDescendant.ts +++ b/packages/react-components/react-aria/src/activedescendant/useActiveDescendant.ts @@ -34,6 +34,7 @@ export function useActiveDescendant(null); const attributeVisibilityRef = React.useRef(true); const { targetDocument } = useFluent(); + const win = targetDocument?.defaultView; const removeAttribute = React.useCallback(() => { activeParentRef.current?.removeAttribute('aria-activedescendant'); @@ -89,7 +90,7 @@ export function useActiveDescendant Date: Tue, 7 May 2024 14:40:29 -0700 Subject: [PATCH 18/25] fix lint errors --- .../src/components/Virtualizer/useVirtualizer.ts | 1 + .../src/hooks/useIntersectionObserver.ts | 3 +-- .../src/hooks/useMutationObserver.ts | 11 ++++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index ed1ed221babb2..132f78a1c81ae 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -126,6 +126,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Observe intersections of virtualized components const { setObserverList } = useIntersectionObserver( + // eslint-disable-next-line no-restricted-globals (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { /* Sanity check - do we even need virtualization? */ if (virtualizerLength > numItems) { diff --git a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts index 1cc44419b8eac..a61b4811e54ce 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts @@ -107,7 +107,6 @@ export const useIntersectionObserver = ( // Observer elements in passed in list and clean up previous list // This effect is only triggered when observerList is updated useIsomorphicLayoutEffect(() => { - const win = targetDocument?.defaultView; if (!win) { return; } @@ -130,7 +129,7 @@ export const useIntersectionObserver = ( observer.current.disconnect(); } }; - }, [observerList, observerInit, callback, targetDocument?.defaultView]); + }, [observerList, observerInit, callback, win]); // Do not use internally, we need to track external settings only here const setObserverInitExternal = useCallback( diff --git a/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts b/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts index c46b43e82dd97..63461d1070b4b 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts @@ -1,5 +1,6 @@ import type { MutableRefObject } from 'react'; import * as React from 'react'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; const { useRef, useEffect } = React; @@ -8,14 +9,18 @@ export const useMutationObserver = ( callback: MutationCallback, options?: MutationObserverInit, ): { - observer: MutableRefObject; + observer: MutableRefObject; // eslint-disable-line no-restricted-globals } => { + // eslint-disable-next-line no-restricted-globals const observer = useRef(); + const { targetDocument } = useFluent(); + // eslint-disable-next-line no-restricted-globals + const win = targetDocument?.defaultView ?? window; useEffect(() => { // Create an observer instance linked to the callback function - observer.current = new MutationObserver(callback); - }, [callback]); + observer.current = new win.MutationObserver(callback); + }, [callback, win]); useEffect(() => { if (target) { From 6b1df12ee9b56fc2d1f2f15b5c97afccd1dc19a9 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 7 May 2024 15:42:52 -0700 Subject: [PATCH 19/25] fix ssr --- .../react-virtualizer/src/hooks/useMutationObserver.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts b/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts index 63461d1070b4b..c58b8fbc27388 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts @@ -14,10 +14,12 @@ export const useMutationObserver = ( // eslint-disable-next-line no-restricted-globals const observer = useRef(); const { targetDocument } = useFluent(); - // eslint-disable-next-line no-restricted-globals - const win = targetDocument?.defaultView ?? window; + const win = targetDocument?.defaultView; useEffect(() => { + if (!win) { + return; + } // Create an observer instance linked to the callback function observer.current = new win.MutationObserver(callback); }, [callback, win]); From 43af8c329c3ef418ed54b6f05384746eae3e2193 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 9 May 2024 14:07:05 -0700 Subject: [PATCH 20/25] use MutationObserver from global context --- .../src/components/Carousel/useCarousel.ts | 12 ++++++++++-- .../TeachingPopoverCarousel/Carousel/Carousel.tsx | 13 ++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/react-components/react-carousel-preview/src/components/Carousel/useCarousel.ts b/packages/react-components/react-carousel-preview/src/components/Carousel/useCarousel.ts index 7dbab3c0267f4..fd870d3773e88 100644 --- a/packages/react-components/react-carousel-preview/src/components/Carousel/useCarousel.ts +++ b/packages/react-components/react-carousel-preview/src/components/Carousel/useCarousel.ts @@ -7,6 +7,8 @@ import { useEventCallback, useMergedRefs, } from '@fluentui/react-utilities'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; + import type { CarouselProps, CarouselState } from './Carousel.types'; import { useCarouselWalker_unstable } from '../useCarouselWalker'; import { createCarouselStore } from '../createCarouselStore'; @@ -25,6 +27,8 @@ import type { CarouselContextValue } from '../CarouselContext.types'; export function useCarousel_unstable(props: CarouselProps, ref: React.Ref): CarouselState { const { onValueChange } = props; + const { targetDocument } = useFluent(); + const win = targetDocument?.defaultView; const { ref: carouselRef, walker: carouselWalker } = useCarouselWalker_unstable(); const [store] = React.useState(() => createCarouselStore()); @@ -60,6 +64,10 @@ export function useCarousel_unstable(props: CarouselProps, ref: React.Ref { + if (!win) { + return; + } + const config: MutationObserverInit = { attributes: true, attributeFilter: [CAROUSEL_ITEM], @@ -94,7 +102,7 @@ export function useCarousel_unstable(props: CarouselProps, ref: React.Ref { observer.disconnect(); }; - }, [carouselWalker, store]); + }, [carouselWalker, store, win]); const selectPageByDirection: CarouselContextValue['selectPageByDirection'] = useEventCallback((event, direction) => { const active = carouselWalker.active(); diff --git a/packages/react-components/react-teaching-popover/src/components/TeachingPopoverCarousel/Carousel/Carousel.tsx b/packages/react-components/react-teaching-popover/src/components/TeachingPopoverCarousel/Carousel/Carousel.tsx index a2e6f321ca1d8..80f47575f915a 100644 --- a/packages/react-components/react-teaching-popover/src/components/TeachingPopoverCarousel/Carousel/Carousel.tsx +++ b/packages/react-components/react-teaching-popover/src/components/TeachingPopoverCarousel/Carousel/Carousel.tsx @@ -1,3 +1,4 @@ +import * as React from 'react'; import { isHTMLElement, useMergedRefs, @@ -5,7 +6,7 @@ import { type EventHandler, useEventCallback, } from '@fluentui/react-utilities'; -import * as React from 'react'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; import { CAROUSEL_ITEM } from './constants'; import { useCarouselWalker_unstable } from './useCarouselWalker'; @@ -26,6 +27,8 @@ export type UseCarouselOptions = { export function useCarousel_unstable(options: UseCarouselOptions) { const { onValueChange, onFinish } = options; + const { targetDocument } = useFluent(); + const win = targetDocument?.defaultView; const { ref: carouselRef, walker: carouselWalker } = useCarouselWalker_unstable(); const [store] = React.useState(() => createCarouselStore()); @@ -61,6 +64,10 @@ export function useCarousel_unstable(options: UseCarouselOptions) { }, [store]); React.useEffect(() => { + if (!win) { + return; + } + const config: MutationObserverInit = { attributes: true, attributeFilter: [CAROUSEL_ITEM], @@ -95,7 +102,7 @@ export function useCarousel_unstable(options: UseCarouselOptions) { }; // Create an observer instance linked to the callback function - const observer = new MutationObserver(callback); + const observer = new win.MutationObserver(callback); // Start observing the target node for configured mutations observer.observe(rootRef.current!, config); @@ -104,7 +111,7 @@ export function useCarousel_unstable(options: UseCarouselOptions) { return () => { observer.disconnect(); }; - }, [carouselWalker, store]); + }, [carouselWalker, store, win]); const selectPageByDirection: CarouselContextValue['selectPageByDirection'] = useEventCallback((event, direction) => { const active = carouselWalker.active(); From 1cadc2766a35f8f7c1140393b94f262dc11429aa Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 9 May 2024 14:07:56 -0700 Subject: [PATCH 21/25] change file --- ...ching-popover-bcd345f2-959c-4653-b294-b4bfa456d469.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-teaching-popover-bcd345f2-959c-4653-b294-b4bfa456d469.json diff --git a/change/@fluentui-react-teaching-popover-bcd345f2-959c-4653-b294-b4bfa456d469.json b/change/@fluentui-react-teaching-popover-bcd345f2-959c-4653-b294-b4bfa456d469.json new file mode 100644 index 0000000000000..9d2bbaedf4f00 --- /dev/null +++ b/change/@fluentui-react-teaching-popover-bcd345f2-959c-4653-b294-b4bfa456d469.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Use `MutationObserver` from global context.", + "packageName": "@fluentui/react-teaching-popover", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} From 5a8f8373e7ac44b4eab7b9dca60f17d74f2e75a7 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 15 May 2024 10:35:51 -0700 Subject: [PATCH 22/25] pr feedback --- .../priority-overflow/src/createResizeObserver.ts | 7 ++++--- .../react-icons-compat/src/icon.ts | 1 + .../src/components/MenuPopover/useMenuPopover.ts | 14 +++++--------- .../components/MessageBar/useMessageBarReflow.ts | 1 + .../react-table/src/hooks/useMeasureElement.ts | 1 + .../src/tabster-types-6.0.1-do-not-use.ts | 1 + .../react-utilities/src/hooks/useTimeout.ts | 8 ++------ .../src/components/Virtualizer/useVirtualizer.ts | 1 + .../react-virtualizer/src/hooks/hooks.types.ts | 1 + .../src/hooks/useDynamicVirtualizerMeasure.ts | 1 + .../src/hooks/useIntersectionObserver.ts | 2 ++ .../src/hooks/useMutationObserver.ts | 1 + .../src/hooks/useResizeObserverRef.ts | 1 + .../src/hooks/useVirtualizerMeasure.ts | 1 + 14 files changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/react-components/priority-overflow/src/createResizeObserver.ts b/packages/react-components/priority-overflow/src/createResizeObserver.ts index a9563af5f3e08..a24be2302c967 100644 --- a/packages/react-components/priority-overflow/src/createResizeObserver.ts +++ b/packages/react-components/priority-overflow/src/createResizeObserver.ts @@ -5,9 +5,9 @@ * @returns function to cleanup the resize observer */ export function observeResize(elementToObserve: HTMLElement, callback: ResizeObserverCallback) { - const GlobalResizeObsever = elementToObserve.ownerDocument.defaultView?.ResizeObserver; + const GlobalResizeObserver = elementToObserve.ownerDocument.defaultView?.ResizeObserver; - if (!GlobalResizeObsever) { + if (!GlobalResizeObserver) { if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line no-console console.error('@fluentui/priority-overflow', 'ResizeObserver does not exist on container window'); @@ -15,8 +15,9 @@ export function observeResize(elementToObserve: HTMLElement, callback: ResizeObs return () => null; } + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals - let resizeObserver: ResizeObserver | undefined = new GlobalResizeObsever(callback); + let resizeObserver: ResizeObserver | undefined = new GlobalResizeObserver(callback); resizeObserver.observe(elementToObserve); return () => { diff --git a/packages/react-components/react-icons-compat/src/icon.ts b/packages/react-components/react-icons-compat/src/icon.ts index 6bcce7fb14726..52e5bbebf577f 100644 --- a/packages/react-components/react-icons-compat/src/icon.ts +++ b/packages/react-components/react-icons-compat/src/icon.ts @@ -197,6 +197,7 @@ export function setIconOptions(options: Partial): void { } let _missingIcons: string[] = []; +// TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals let _missingIconsTimer: ReturnType | undefined = undefined; diff --git a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts index d554374f90a63..f9cc14d353629 100644 --- a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts +++ b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { ArrowLeft, Tab, ArrowRight, Escape } from '@fluentui/keyboard-keys'; -import { getIntrinsicElementProps, useEventCallback, useMergedRefs, slot } from '@fluentui/react-utilities'; +import { getIntrinsicElementProps, useEventCallback, useMergedRefs, slot, useTimeout } from '@fluentui/react-utilities'; import { MenuPopoverProps, MenuPopoverState } from './MenuPopover.types'; import { useMenuContext_unstable } from '../../contexts/menuContext'; import { dispatchMenuEnterEvent } from '../../utils/index'; @@ -25,8 +25,8 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< const triggerRef = useMenuContext_unstable(context => context.triggerRef); const isSubmenu = useIsSubmenu(); const canDispatchCustomEventRef = React.useRef(true); - const throttleDispatchTimerRef = React.useRef(0); const restoreFocusSourceAttributes = useRestoreFocusSource(); + const [setThrottleTimeout, clearThrottleTimeout] = useTimeout(); const { dir } = useFluent(); const CloseArrowKey = dir === 'ltr' ? ArrowLeft : ArrowRight; @@ -43,20 +43,16 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< if (canDispatchCustomEventRef.current) { canDispatchCustomEventRef.current = false; dispatchMenuEnterEvent(popoverRef.current as HTMLElement, e); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore #16889 Node setTimeout type leaking - // eslint-disable-next-line no-restricted-globals - throttleDispatchTimerRef.current = setTimeout(() => (canDispatchCustomEventRef.current = true), 250); + setThrottleTimeout(() => (canDispatchCustomEventRef.current = true), 250); } }); } }, - [popoverRef, throttleDispatchTimerRef], + [popoverRef, setThrottleTimeout], ); React.useEffect(() => { - // eslint-disable-next-line no-restricted-globals - () => clearTimeout(throttleDispatchTimerRef.current); + () => clearThrottleTimeout(); }, []); const inline = useMenuContext_unstable(context => context.inline) ?? false; diff --git a/packages/react-components/react-message-bar/src/components/MessageBar/useMessageBarReflow.ts b/packages/react-components/react-message-bar/src/components/MessageBar/useMessageBarReflow.ts index 5d833058b4fd4..53034e7a4c48b 100644 --- a/packages/react-components/react-message-bar/src/components/MessageBar/useMessageBarReflow.ts +++ b/packages/react-components/react-message-bar/src/components/MessageBar/useMessageBarReflow.ts @@ -6,6 +6,7 @@ export function useMessageBarReflow(enabled: boolean = false) { const { targetDocument } = useFluent(); const forceUpdate = React.useReducer(() => ({}), {})[1]; const reflowingRef = React.useRef(false); + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals const resizeObserverRef = React.useRef(null); const prevInlineSizeRef = React.useRef(-1); diff --git a/packages/react-components/react-table/src/hooks/useMeasureElement.ts b/packages/react-components/react-table/src/hooks/useMeasureElement.ts index 4fa2d4ca599e7..c86a93261db11 100644 --- a/packages/react-components/react-table/src/hooks/useMeasureElement.ts +++ b/packages/react-components/react-table/src/hooks/useMeasureElement.ts @@ -11,6 +11,7 @@ export function useMeasureElement() const [width, setWidth] = React.useState(0); const container = React.useRef(undefined); + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals const resizeObserverRef = React.useRef(null); diff --git a/packages/react-components/react-tabster/src/tabster-types-6.0.1-do-not-use.ts b/packages/react-components/react-tabster/src/tabster-types-6.0.1-do-not-use.ts index fa9f531c2b906..e8487dcc5b406 100644 --- a/packages/react-components/react-tabster/src/tabster-types-6.0.1-do-not-use.ts +++ b/packages/react-components/react-tabster/src/tabster-types-6.0.1-do-not-use.ts @@ -123,6 +123,7 @@ export interface TabsterCoreProps { } export interface DOMAPI { + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals createMutationObserver: (callback: MutationCallback) => MutationObserver; createTreeWalker(doc: Document, root: Node, whatToShow?: number, filter?: NodeFilter | null): TreeWalker; diff --git a/packages/react-components/react-utilities/src/hooks/useTimeout.ts b/packages/react-components/react-utilities/src/hooks/useTimeout.ts index 5d3f931547448..14ae3b8ef09dd 100644 --- a/packages/react-components/react-utilities/src/hooks/useTimeout.ts +++ b/packages/react-components/react-utilities/src/hooks/useTimeout.ts @@ -1,12 +1,8 @@ import { useBrowserTimer } from './useBrowserTimer'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; -const setTimeoutNoop = (callback: Function) => { - callback(); - return 0; -}; - -const clearTimeoutNoop = (handle: number) => handle; +const setTimeoutNoop = (_callback: Function) => -1; +const clearTimeoutNoop = (_handle: number) => undefined; /** * @internal diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index 132f78a1c81ae..73fabf08ec18d 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -126,6 +126,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Observe intersections of virtualized components const { setObserverList } = useIntersectionObserver( + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { /* Sanity check - do we even need virtualization? */ diff --git a/packages/react-components/react-virtualizer/src/hooks/hooks.types.ts b/packages/react-components/react-virtualizer/src/hooks/hooks.types.ts index e8e4e553e06ab..7d8d27dcb730c 100644 --- a/packages/react-components/react-virtualizer/src/hooks/hooks.types.ts +++ b/packages/react-components/react-virtualizer/src/hooks/hooks.types.ts @@ -48,6 +48,7 @@ export type VirtualizerDynamicPaginationProps = { export interface ResizeCallbackWithRef { ( entries: ResizeObserverEntry[], + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals observer: ResizeObserver, scrollRef?: MutableRefObject, diff --git a/packages/react-components/react-virtualizer/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useDynamicVirtualizerMeasure.ts index f97ae38cef139..9eda6d577c275 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useDynamicVirtualizerMeasure.ts @@ -78,6 +78,7 @@ export const useDynamicVirtualizerMeasure = ( const resizeCallback = React.useCallback( ( _entries: ResizeObserverEntry[], + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals _observer: ResizeObserver, scrollRef?: React.MutableRefObject, diff --git a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts index a61b4811e54ce..017217ab5b6c0 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts @@ -56,10 +56,12 @@ export const useIntersectionObserver = ( ): { setObserverList: Dispatch>; setObserverInit: (newInit: IntersectionObserverInit | undefined) => void; + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals observer: MutableRefObject; } => { // eslint-disable-next-line no-restricted-globals + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 const observer = useRef(); const [observerList, setObserverList] = useState(); const { targetDocument } = useFluent(); diff --git a/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts b/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts index c58b8fbc27388..f7f4133adea68 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useMutationObserver.ts @@ -11,6 +11,7 @@ export const useMutationObserver = ( ): { observer: MutableRefObject; // eslint-disable-line no-restricted-globals } => { + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals const observer = useRef(); const { targetDocument } = useFluent(); diff --git a/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts b/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts index 75ba53494e4ba..75d4d4be7c767 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts @@ -11,6 +11,7 @@ export const useResizeObserverRef_unstable = (resizeCallback: ResizeCallbackWith const { targetDocument } = useFluent(); const container = React.useRef(null); // the handler for resize observer + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals const handleResize = debounce((entries: ResizeObserverEntry[], observer: ResizeObserver) => { resizeCallback(entries, observer, container); diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 51c811103c377..aeeec5bb5a6db 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -26,6 +26,7 @@ export const useStaticVirtualizerMeasure = ( const resizeCallback = React.useCallback( ( _entries: ResizeObserverEntry[], + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals _observer: ResizeObserver, scrollRef?: React.MutableRefObject, From e8a591c58f64a14521389e9330b620bd214686b9 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 16 May 2024 14:55:18 -0700 Subject: [PATCH 23/25] fix order on lint exception --- .../react-virtualizer/src/hooks/useIntersectionObserver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts index 017217ab5b6c0..8067e83e6afba 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts @@ -60,8 +60,8 @@ export const useIntersectionObserver = ( // eslint-disable-next-line no-restricted-globals observer: MutableRefObject; } => { - // eslint-disable-next-line no-restricted-globals // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 + // eslint-disable-next-line no-restricted-globals const observer = useRef(); const [observerList, setObserverList] = useState(); const { targetDocument } = useFluent(); From b64236c3ae6b12c0ad347f8b318e655464f72374 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Thu, 16 May 2024 15:37:25 -0700 Subject: [PATCH 24/25] fix useEffect deps --- .../react-menu/src/components/MenuPopover/useMenuPopover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts index f9cc14d353629..aadc45946067e 100644 --- a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts +++ b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts @@ -53,7 +53,7 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< React.useEffect(() => { () => clearThrottleTimeout(); - }, []); + }, [clearThrottleTimeout]); const inline = useMenuContext_unstable(context => context.inline) ?? false; const mountNode = useMenuContext_unstable(context => context.mountNode); From 6253fe468fb8718b0f34a6f2cd257c8d8af72bb1 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 22 May 2024 13:40:30 -0700 Subject: [PATCH 25/25] improve typing --- .../priority-overflow/src/createResizeObserver.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/react-components/priority-overflow/src/createResizeObserver.ts b/packages/react-components/priority-overflow/src/createResizeObserver.ts index a24be2302c967..0035dd853a170 100644 --- a/packages/react-components/priority-overflow/src/createResizeObserver.ts +++ b/packages/react-components/priority-overflow/src/createResizeObserver.ts @@ -15,9 +15,7 @@ export function observeResize(elementToObserve: HTMLElement, callback: ResizeObs return () => null; } - // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 - // eslint-disable-next-line no-restricted-globals - let resizeObserver: ResizeObserver | undefined = new GlobalResizeObserver(callback); + let resizeObserver: InstanceType | undefined = new GlobalResizeObserver(callback); resizeObserver.observe(elementToObserve); return () => {