diff --git a/apps/perf-test-react-components/.eslintrc.json b/apps/perf-test-react-components/.eslintrc.json index 2948bbf09dea57..2b95bc03dc3f57 100644 --- a/apps/perf-test-react-components/.eslintrc.json +++ b/apps/perf-test-react-components/.eslintrc.json @@ -3,6 +3,7 @@ "root": true, "rules": { "@griffel/styles-file": "off", - "no-console": "off" + "no-console": "off", + "no-restricted-globals": "off" } } diff --git a/apps/public-docsite-v9/.eslintrc.json b/apps/public-docsite-v9/.eslintrc.json index b3f394f7cbaca5..54ac641d7b3bfc 100644 --- a/apps/public-docsite-v9/.eslintrc.json +++ b/apps/public-docsite-v9/.eslintrc.json @@ -5,6 +5,7 @@ "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/jsx-no-bind": "off", "deprecation/deprecation": "off", - "import/no-extraneous-dependencies": ["error", { "packageDir": [".", "../.."] }] + "import/no-extraneous-dependencies": ["error", { "packageDir": [".", "../.."] }], + "no-restricted-globals": "off" } } diff --git a/apps/react-18-tests-v8/.eslintrc.json b/apps/react-18-tests-v8/.eslintrc.json index 92f2e74b606c9e..b2f318c6b6a1e4 100644 --- a/apps/react-18-tests-v8/.eslintrc.json +++ b/apps/react-18-tests-v8/.eslintrc.json @@ -1,5 +1,7 @@ { "extends": ["plugin:@fluentui/eslint-plugin/react"], "root": true, - "rules": {} + "rules": { + "no-restricted-globals": "off" + } } diff --git a/apps/react-18-tests-v9/.eslintrc.json b/apps/react-18-tests-v9/.eslintrc.json index 92f2e74b606c9e..b2f318c6b6a1e4 100644 --- a/apps/react-18-tests-v9/.eslintrc.json +++ b/apps/react-18-tests-v9/.eslintrc.json @@ -1,5 +1,7 @@ { "extends": ["plugin:@fluentui/eslint-plugin/react"], "root": true, - "rules": {} + "rules": { + "no-restricted-globals": "off" + } } diff --git a/apps/recipes-react-components/.eslintrc.json b/apps/recipes-react-components/.eslintrc.json index 453f767c8db077..b9d702c5e755da 100644 --- a/apps/recipes-react-components/.eslintrc.json +++ b/apps/recipes-react-components/.eslintrc.json @@ -7,6 +7,7 @@ "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/jsx-no-bind": "off", "deprecation/deprecation": "off", - "import/no-extraneous-dependencies": ["error", { "packageDir": [".", "../.."] }] + "import/no-extraneous-dependencies": ["error", { "packageDir": [".", "../.."] }], + "no-restricted-globals": "off" } } diff --git a/apps/ssr-tests-v9/.eslintrc.json b/apps/ssr-tests-v9/.eslintrc.json index f502b07b22439c..b6719ff5c00cf2 100644 --- a/apps/ssr-tests-v9/.eslintrc.json +++ b/apps/ssr-tests-v9/.eslintrc.json @@ -2,6 +2,7 @@ "extends": ["plugin:@fluentui/eslint-plugin/node"], "root": true, "rules": { - "import/no-extraneous-dependencies": ["error", { "packageDir": ["../../", "./"] }] + "import/no-extraneous-dependencies": ["error", { "packageDir": ["../../", "./"] }], + "no-restricted-globals": "off" } } diff --git a/apps/stress-test/.eslintrc.json b/apps/stress-test/.eslintrc.json index f502b07b22439c..b6719ff5c00cf2 100644 --- a/apps/stress-test/.eslintrc.json +++ b/apps/stress-test/.eslintrc.json @@ -2,6 +2,7 @@ "extends": ["plugin:@fluentui/eslint-plugin/node"], "root": true, "rules": { - "import/no-extraneous-dependencies": ["error", { "packageDir": ["../../", "./"] }] + "import/no-extraneous-dependencies": ["error", { "packageDir": ["../../", "./"] }], + "no-restricted-globals": "off" } } diff --git a/apps/vr-tests-react-components/.eslintrc.json b/apps/vr-tests-react-components/.eslintrc.json index 76ca2160a370b9..ffeab27cf4c88b 100644 --- a/apps/vr-tests-react-components/.eslintrc.json +++ b/apps/vr-tests-react-components/.eslintrc.json @@ -8,6 +8,7 @@ "@typescript-eslint/naming-convention": "off", "@typescript-eslint/jsx-no-bind": "off", "deprecation/deprecation": "off", - "import/no-extraneous-dependencies": ["error", { "packageDir": [".", "../.."] }] + "import/no-extraneous-dependencies": ["error", { "packageDir": [".", "../.."] }], + "no-restricted-globals": "off" } } diff --git a/change/@fluentui-cra-template-49db9598-b35e-4d1c-901b-ead2f5a354e0.json b/change/@fluentui-cra-template-49db9598-b35e-4d1c-901b-ead2f5a354e0.json new file mode 100644 index 00000000000000..f9e92f127f9ae7 --- /dev/null +++ b/change/@fluentui-cra-template-49db9598-b35e-4d1c-901b-ead2f5a354e0.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disallow `window` and `document` access", + "packageName": "@fluentui/cra-template", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-eslint-plugin-ec3bdd41-2624-4271-bda6-b13ed5f7d0c5.json b/change/@fluentui-eslint-plugin-ec3bdd41-2624-4271-bda6-b13ed5f7d0c5.json new file mode 100644 index 00000000000000..10bead64bbd14e --- /dev/null +++ b/change/@fluentui-eslint-plugin-ec3bdd41-2624-4271-bda6-b13ed5f7d0c5.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disallow `window` and `document` access for @fluentui/react-components", + "packageName": "@fluentui/eslint-plugin", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-global-context-1e3db17a-f6cc-40b8-a367-feabee08ad3b.json b/change/@fluentui-global-context-1e3db17a-f6cc-40b8-a367-feabee08ad3b.json new file mode 100644 index 00000000000000..8f952325fc2220 --- /dev/null +++ b/change/@fluentui-global-context-1e3db17a-f6cc-40b8-a367-feabee08ad3b.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "chore: disallow `window` and `document` access", + "packageName": "@fluentui/global-context", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-calendar-compat-0d185f2c-2c07-45d3-a65a-11a19f4ae6ba.json b/change/@fluentui-react-calendar-compat-0d185f2c-2c07-45d3-a65a-11a19f4ae6ba.json new file mode 100644 index 00000000000000..3253bf30a954d7 --- /dev/null +++ b/change/@fluentui-react-calendar-compat-0d185f2c-2c07-45d3-a65a-11a19f4ae6ba.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disallow `window` and `document` access", + "packageName": "@fluentui/react-calendar-compat", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-motion-preview-5c6d53e5-4c7e-4418-a389-54ff5bceaf66.json b/change/@fluentui-react-motion-preview-5c6d53e5-4c7e-4418-a389-54ff5bceaf66.json new file mode 100644 index 00000000000000..e36a0abab75926 --- /dev/null +++ b/change/@fluentui-react-motion-preview-5c6d53e5-4c7e-4418-a389-54ff5bceaf66.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disallow `window` and `document` access", + "packageName": "@fluentui/react-motion-preview", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-shared-contexts-a56a4c13-fffd-4819-a2ca-0334abbc53a9.json b/change/@fluentui-react-shared-contexts-a56a4c13-fffd-4819-a2ca-0334abbc53a9.json new file mode 100644 index 00000000000000..ea37e72866d6d1 --- /dev/null +++ b/change/@fluentui-react-shared-contexts-a56a4c13-fffd-4819-a2ca-0334abbc53a9.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disallow `window` and `document` access", + "packageName": "@fluentui/react-shared-contexts", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-utilities-c5034c4b-aba2-4c9d-b59c-54708e33539c.json b/change/@fluentui-react-utilities-c5034c4b-aba2-4c9d-b59c-54708e33539c.json new file mode 100644 index 00000000000000..7581a62e820c9c --- /dev/null +++ b/change/@fluentui-react-utilities-c5034c4b-aba2-4c9d-b59c-54708e33539c.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disallow `window` and `document` access", + "packageName": "@fluentui/react-utilities", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-window-provider-5257f709-976b-4616-9470-013dfe88e991.json b/change/@fluentui-react-window-provider-5257f709-976b-4616-9470-013dfe88e991.json new file mode 100644 index 00000000000000..218346cc9426df --- /dev/null +++ b/change/@fluentui-react-window-provider-5257f709-976b-4616-9470-013dfe88e991.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disallow `window` and `document` access", + "packageName": "@fluentui/react-window-provider", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/cra-template/template/src/index.tsx b/packages/cra-template/template/src/index.tsx index 90113a51c98270..7e39450d98bf54 100644 --- a/packages/cra-template/template/src/index.tsx +++ b/packages/cra-template/template/src/index.tsx @@ -13,6 +13,7 @@ mergeStyles({ }, }); +// eslint-disable-next-line no-restricted-globals ReactDOM.render(, document.getElementById('root')); // If you want to start measuring performance in your app, pass a function diff --git a/packages/eslint-plugin/src/configs/react-config.js b/packages/eslint-plugin/src/configs/react-config.js index 2663520b69bd57..98a8643d1113c7 100644 --- a/packages/eslint-plugin/src/configs/react-config.js +++ b/packages/eslint-plugin/src/configs/react-config.js @@ -1,4 +1,5 @@ const configHelpers = require('../utils/configHelpers'); +const { react: restrictedGlobals } = require('./restricted-globals'); /** @type {import("eslint").Linter.Config} */ module.exports = { @@ -28,6 +29,7 @@ module.exports = { '@griffel/hook-naming': 'error', '@griffel/no-shorthands': 'error', '@griffel/styles-file': 'error', + 'no-restricted-globals': restrictedGlobals, /** * react eslint rules * @see https://github.com/yannickcr/eslint-plugin-react @@ -122,6 +124,7 @@ module.exports = { files: [...configHelpers.testFiles], rules: { 'react/jsx-no-bind': 'off', + 'no-restricted-globals': 'off', }, }, { @@ -129,6 +132,7 @@ module.exports = { rules: { // allow makeStyles calls in stories as examples should be defined in a single file '@griffel/styles-file': 'off', + 'no-restricted-globals': 'off', // allow arrow functions in stories for now (may want to change this later since using // constantly-mutating functions can be an anti-pattern which we may not want to demonstrate // in our converged components docs; it happened to be allowed starting out because .stories diff --git a/packages/eslint-plugin/src/configs/react-legacy.js b/packages/eslint-plugin/src/configs/react-legacy.js index 8d696ec41bfa1c..108f202b0d9a7e 100644 --- a/packages/eslint-plugin/src/configs/react-legacy.js +++ b/packages/eslint-plugin/src/configs/react-legacy.js @@ -9,6 +9,7 @@ module.exports = { rules: { 'jsdoc/check-tag-names': 'off', '@griffel/no-shorthands': 'off', + 'no-restricted-globals': 'off', }, overrides: [], }; diff --git a/packages/eslint-plugin/src/configs/restricted-globals.js b/packages/eslint-plugin/src/configs/restricted-globals.js new file mode 100644 index 00000000000000..b8870e2883ecc0 --- /dev/null +++ b/packages/eslint-plugin/src/configs/restricted-globals.js @@ -0,0 +1,262 @@ +// 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', + +// // 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', +// ]; + +const react = [ + 'error', + { + name: 'window', + message: 'Get a reference to `window` from `useFluent()`.', + }, + { + 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.`, + // }; + // }), +]; + +module.exports = { + react, +}; diff --git a/packages/eslint-plugin/src/utils/configHelpers.js b/packages/eslint-plugin/src/utils/configHelpers.js index 496ef038a2dc5f..37af2f6c4f7c0e 100644 --- a/packages/eslint-plugin/src/utils/configHelpers.js +++ b/packages/eslint-plugin/src/utils/configHelpers.js @@ -31,7 +31,7 @@ function getProjectMetadata(options) { } const testFiles = [ - '**/*{.,-}{test,spec,e2e}.{ts,tsx}', + '**/*{.,-}{test,spec,e2e,cy}.{ts,tsx}', '**/{test,tests}/**', '**/testUtilities.{ts,tsx}', '**/common/{isConformant,snapshotSerializers}.{ts,tsx}', diff --git a/packages/public-docsite-setup/.eslintrc.json b/packages/public-docsite-setup/.eslintrc.json index 5c7aa8ffa91c02..cae3998301ac60 100644 --- a/packages/public-docsite-setup/.eslintrc.json +++ b/packages/public-docsite-setup/.eslintrc.json @@ -5,7 +5,8 @@ { "files": ["bin/*.js", "src/loadSite.ts"], "rules": { - "no-console": "off" + "no-console": "off", + "no-restricted-globals": "off" } }, { diff --git a/packages/react-components/global-context/src/global-context-selector.ts b/packages/react-components/global-context/src/global-context-selector.ts index ec2d843afd7e66..591a1544925d70 100644 --- a/packages/react-components/global-context/src/global-context-selector.ts +++ b/packages/react-components/global-context/src/global-context-selector.ts @@ -5,6 +5,7 @@ import { getMajorVersion } from './utils'; import { GlobalObject } from './types'; const isBrowser = canUseDOM(); +// eslint-disable-next-line no-restricted-globals const globalObject: GlobalObject = isBrowser ? window : global; // Identifier for the symbol, for easy idenfitifaction of symbols created by this util diff --git a/packages/react-components/global-context/src/global-context.ts b/packages/react-components/global-context/src/global-context.ts index a994ddea8f0e49..39e3b8471fe0cf 100644 --- a/packages/react-components/global-context/src/global-context.ts +++ b/packages/react-components/global-context/src/global-context.ts @@ -4,6 +4,7 @@ import { GlobalObject } from './types'; import { getMajorVersion } from './utils'; const isBrowser = canUseDOM(); +// eslint-disable-next-line no-restricted-globals const globalObject: GlobalObject = isBrowser ? window : global; // Identifier for the symbol, for easy idenfitifaction of symbols created by this util diff --git a/packages/react-components/react-calendar-compat/src/components/Calendar/Calendar.tsx b/packages/react-components/react-calendar-compat/src/components/Calendar/Calendar.tsx index 5dbc4a88f88121..62638ff4d40844 100644 --- a/packages/react-components/react-calendar-compat/src/components/Calendar/Calendar.tsx +++ b/packages/react-components/react-calendar-compat/src/components/Calendar/Calendar.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Backspace, Enter, Escape, PageDown, PageUp, Space } from '@fluentui/keyboard-keys'; import { useControllableState } from '@fluentui/react-utilities'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; import { addMonths, addYears, @@ -10,7 +11,6 @@ import { DEFAULT_DATE_FORMATTING, FirstWeekOfYear, focusAsync, - getWindow, } from '../../utils'; import { CalendarDay } from '../CalendarDay/CalendarDay'; import { CalendarMonth } from '../CalendarMonth/CalendarMonth'; @@ -85,20 +85,21 @@ function useVisibilityState({ showMonthPickerAsOverlay, }: CalendarProps) { /** State used to show/hide month picker */ + const showMonthPickerAsOverlayState = useShowMonthPickerAsOverlay({ + isDayPickerVisible: isDayPickerVisibleProp, + showMonthPickerAsOverlay, + }); + const [isMonthPickerVisible, setIsMonthPickerVisible] = useControllableState({ defaultState: false, initialState: true, - state: getShowMonthPickerAsOverlay({ isDayPickerVisible: isDayPickerVisibleProp, showMonthPickerAsOverlay }) - ? undefined - : isMonthPickerVisibleProp, + state: showMonthPickerAsOverlayState ? undefined : isMonthPickerVisibleProp, }); /** State used to show/hide day picker */ const [isDayPickerVisible, setIsDayPickerVisible] = useControllableState({ defaultState: true, initialState: true, - state: getShowMonthPickerAsOverlay({ isDayPickerVisible: isDayPickerVisibleProp, showMonthPickerAsOverlay }) - ? undefined - : isDayPickerVisibleProp, + state: showMonthPickerAsOverlayState ? undefined : isDayPickerVisibleProp, }); const toggleDayMonthPickerVisibility = () => { @@ -113,14 +114,16 @@ function useFocusLogic({ componentRef }: CalendarProps, isDayPickerVisible: bool const dayPicker = React.useRef(null); const monthPicker = React.useRef(null); const focusOnUpdate = React.useRef(false); + const { targetDocument } = useFluent(); + const win = targetDocument?.defaultView; const focus = React.useCallback(() => { if (isDayPickerVisible && dayPicker.current) { - focusAsync(dayPicker.current); + focusAsync(dayPicker.current, win); } else if (isMonthPickerVisible && monthPicker.current) { - focusAsync(monthPicker.current); + focusAsync(monthPicker.current, win); } - }, [isDayPickerVisible, isMonthPickerVisible]); + }, [isDayPickerVisible, isMonthPickerVisible, win]); React.useImperativeHandle(componentRef, () => ({ focus }), [focus]); @@ -240,10 +243,12 @@ export const Calendar: React.FunctionComponent = React.forwardRef navigateDay(date); }; - const onHeaderSelect = getShowMonthPickerAsOverlay({ + const showMonthPickerAsOverlay = useShowMonthPickerAsOverlay({ isDayPickerVisible: isDayPickerVisibleProp, showMonthPickerAsOverlay: showMonthPickerAsOverlayProp, - }) + }); + + const onHeaderSelect = showMonthPickerAsOverlay ? (): void => { toggleDayMonthPickerVisibility(); @@ -306,10 +311,6 @@ export const Calendar: React.FunctionComponent = React.forwardRef break; } }; - const showMonthPickerAsOverlay = getShowMonthPickerAsOverlay({ - isDayPickerVisible: isDayPickerVisibleProp, - showMonthPickerAsOverlay: showMonthPickerAsOverlayProp, - }); const monthPickerOnly = !showMonthPickerAsOverlay && !isDayPickerVisible; @@ -410,7 +411,8 @@ export const Calendar: React.FunctionComponent = React.forwardRef ); Calendar.displayName = 'Calendar'; -function getShowMonthPickerAsOverlay({ isDayPickerVisible, showMonthPickerAsOverlay }: CalendarProps) { - const win = getWindow(); +const useShowMonthPickerAsOverlay = ({ isDayPickerVisible, showMonthPickerAsOverlay }: CalendarProps) => { + const { targetDocument } = useFluent(); + const win = targetDocument?.defaultView; return showMonthPickerAsOverlay || (isDayPickerVisible && win && win.innerWidth <= MIN_SIZE_FORCE_OVERLAY); -} +}; diff --git a/packages/react-components/react-calendar-compat/src/utils/dom.ts b/packages/react-components/react-calendar-compat/src/utils/dom.ts deleted file mode 100644 index 4ce45e2fa22b1f..00000000000000 --- a/packages/react-components/react-calendar-compat/src/utils/dom.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { canUseDOM } from '@fluentui/react-utilities'; - -export function getWindow(targetElement?: Element | null): Window | undefined { - if (!canUseDOM() || typeof window === 'undefined') { - return undefined; - } - - const el = targetElement as Element; - - return el && el.ownerDocument && el.ownerDocument.defaultView ? el.ownerDocument.defaultView : window; -} diff --git a/packages/react-components/react-calendar-compat/src/utils/focus.ts b/packages/react-components/react-calendar-compat/src/utils/focus.ts index 97bc5ddb7bfa8b..ce48f80e48029b 100644 --- a/packages/react-components/react-calendar-compat/src/utils/focus.ts +++ b/packages/react-components/react-calendar-compat/src/utils/focus.ts @@ -1,5 +1,3 @@ -import { getWindow } from './dom'; - let targetToFocusOnNextRepaint: HTMLElement | { focus: () => void } | null | undefined = undefined; /** @@ -8,7 +6,10 @@ let targetToFocusOnNextRepaint: HTMLElement | { focus: () => void } | null | und * only the latest called focusAsync element will actually be focused * @param element - The element to focus */ -export function focusAsync(element: HTMLElement | { focus: () => void } | undefined | null): void { +export function focusAsync( + element: HTMLElement | { focus: () => void } | undefined | null, + win: Window | undefined | null, +): void { if (element) { // An element was already queued to be focused, so replace that one with the new element if (targetToFocusOnNextRepaint) { @@ -18,8 +19,6 @@ export function focusAsync(element: HTMLElement | { focus: () => void } | undefi targetToFocusOnNextRepaint = element; - const win = getWindow(element as Element); - if (win) { // element.focus() is a no-op if the element is no longer in the DOM, meaning this is always safe win.requestAnimationFrame(() => { diff --git a/packages/react-components/react-calendar-compat/src/utils/index.ts b/packages/react-components/react-calendar-compat/src/utils/index.ts index 488020805bb61c..cb8c73d5cadcb6 100644 --- a/packages/react-components/react-calendar-compat/src/utils/index.ts +++ b/packages/react-components/react-calendar-compat/src/utils/index.ts @@ -3,5 +3,4 @@ export * from './constants'; export * from './dateFormatting'; export * from './dateGrid'; export * from './dateMath'; -export * from './dom'; export * from './focus'; diff --git a/packages/react-components/react-components/stories/AccessibilityScenarios/utils.tsx b/packages/react-components/react-components/stories/AccessibilityScenarios/utils.tsx index 06438c6e95fc41..540d8e94f0f625 100644 --- a/packages/react-components/react-components/stories/AccessibilityScenarios/utils.tsx +++ b/packages/react-components/react-components/stories/AccessibilityScenarios/utils.tsx @@ -30,6 +30,7 @@ export const BackLink = () => Go back to main menu = ({ pageTitle, children }) => { React.useEffect(() => { + // eslint-disable-next-line no-restricted-globals document.title = pageTitle + APP_TITLE_SEPARATOR + APP_TITLE; }, [pageTitle]); diff --git a/packages/react-components/react-motion-preview/src/utils/dom-style.ts b/packages/react-components/react-motion-preview/src/utils/dom-style.ts index faa0aef71fa582..bbde104c95ffd1 100644 --- a/packages/react-components/react-motion-preview/src/utils/dom-style.ts +++ b/packages/react-components/react-motion-preview/src/utils/dom-style.ts @@ -81,7 +81,11 @@ export const hasCSSOMSupport = (node: HTMLElementWithStyledMap) => { * @returns - CSS styles. */ export const getElementComputedStyle = (node: HTMLElement): CSSStyleDeclaration => { - const win = canUseDOM() && (node.ownerDocument?.defaultView ?? window); + const win = + canUseDOM() && + (node.ownerDocument?.defaultView ?? + // eslint-disable-next-line no-restricted-globals + window); if (!win) { return { diff --git a/packages/react-components/react-shared-contexts/src/ProviderContext/ProviderContext.ts b/packages/react-components/react-shared-contexts/src/ProviderContext/ProviderContext.ts index 5651db55545680..4b965b8e85a8fc 100644 --- a/packages/react-components/react-shared-contexts/src/ProviderContext/ProviderContext.ts +++ b/packages/react-components/react-shared-contexts/src/ProviderContext/ProviderContext.ts @@ -16,6 +16,7 @@ const ProviderContext = React.createContext( ) as React.Context; const providerContextDefaultValue: ProviderContextValue = { + // eslint-disable-next-line no-restricted-globals targetDocument: typeof document === 'object' ? document : undefined, dir: 'ltr' as const, }; diff --git a/packages/react-components/react-utilities/package.json b/packages/react-components/react-utilities/package.json index 4f290812772e77..1c17653f62f735 100644 --- a/packages/react-components/react-utilities/package.json +++ b/packages/react-components/react-utilities/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@fluentui/keyboard-keys": "^9.0.7", + "@fluentui/react-shared-contexts": "^9.13.0", "@swc/helpers": "^0.5.1" }, "peerDependencies": { diff --git a/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts b/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts index 74b414a8b8ed58..1ae1f9b3e5f1bc 100644 --- a/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts +++ b/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import { useEventCallback } from './useEventCallback'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; /** * @internal @@ -45,6 +46,8 @@ const DEFAULT_CONTAINS: UseOnClickOrScrollOutsideOptions['contains'] = (parent, * Utility to perform checks where a click/touch event was made outside a component */ export const useOnClickOutside = (options: UseOnClickOrScrollOutsideOptions) => { + const { targetDocument } = useFluent(); + const win = targetDocument?.defaultView; const { refs, callback, element, disabled, disabledFocusOnIframe, contains = DEFAULT_CONTAINS } = options; const timeoutId = React.useRef(undefined); @@ -80,7 +83,7 @@ export const useOnClickOutside = (options: UseOnClickOrScrollOutsideOptions) => // Store the current event to avoid triggering handlers immediately // Note this depends on a deprecated but extremely well supported quirk of the web platform // https://github.com/facebook/react/issues/20074 - let currentEvent = getWindowEvent(window); + let currentEvent = getWindowEvent(win); const conditionalHandler = (event: MouseEvent | TouchEvent) => { // Skip if this event is the same as the one running when we added the handlers @@ -99,7 +102,7 @@ export const useOnClickOutside = (options: UseOnClickOrScrollOutsideOptions) => element?.addEventListener('mousedown', handleMouseDown, true); // Garbage collect this event after it's no longer useful to avoid memory leaks - timeoutId.current = window.setTimeout(() => { + timeoutId.current = win?.setTimeout(() => { currentEvent = undefined; }, 1); @@ -109,13 +112,13 @@ export const useOnClickOutside = (options: UseOnClickOrScrollOutsideOptions) => element?.removeEventListener('contextmenu', conditionalHandler, true); element?.removeEventListener('mousedown', handleMouseDown, true); - clearTimeout(timeoutId.current); + win?.clearTimeout(timeoutId.current); currentEvent = undefined; }; - }, [listener, element, disabled, handleMouseDown]); + }, [listener, element, disabled, handleMouseDown, win]); }; -const getWindowEvent = (target: Node | Window): Event | undefined => { +const getWindowEvent = (target: Node | Window | null | undefined): Event | undefined => { if (target) { if (typeof (target as Window).window === 'object' && (target as Window).window === target) { // eslint-disable-next-line deprecation/deprecation diff --git a/packages/react-components/react-utilities/src/ssr/canUseDOM.ts b/packages/react-components/react-utilities/src/ssr/canUseDOM.ts index 01520b98ccc7a8..97a67ead443ed5 100644 --- a/packages/react-components/react-utilities/src/ssr/canUseDOM.ts +++ b/packages/react-components/react-utilities/src/ssr/canUseDOM.ts @@ -3,11 +3,7 @@ */ export function canUseDOM(): boolean { return ( - typeof window !== 'undefined' && - !!( - window.document && - // eslint-disable-next-line deprecation/deprecation - window.document.createElement - ) + // eslint-disable-next-line deprecation/deprecation, no-restricted-globals + typeof window !== 'undefined' && !!(window.document && window.document.createElement) ); } diff --git a/packages/react-components/theme-designer/.eslintrc.json b/packages/react-components/theme-designer/.eslintrc.json index a86d9b8501ad5b..eab62c7c347c87 100644 --- a/packages/react-components/theme-designer/.eslintrc.json +++ b/packages/react-components/theme-designer/.eslintrc.json @@ -2,6 +2,7 @@ "extends": ["plugin:@fluentui/eslint-plugin/react"], "root": true, "rules": { - "@griffel/styles-file": "off" + "@griffel/styles-file": "off", + "no-restricted-globals": "off" } } diff --git a/packages/react-window-provider/src/WindowProvider.tsx b/packages/react-window-provider/src/WindowProvider.tsx index 61758d0c12ddf9..4b03b2d7ceb21a 100644 --- a/packages/react-window-provider/src/WindowProvider.tsx +++ b/packages/react-window-provider/src/WindowProvider.tsx @@ -16,6 +16,7 @@ export type WindowProviderProps = { */ // eslint-disable-next-line @fluentui/no-context-default-value export const WindowContext = React.createContext({ + // eslint-disable-next-line no-restricted-globals window: typeof window === 'object' ? window : undefined, }); diff --git a/packages/set-version/.eslintrc.json b/packages/set-version/.eslintrc.json index 242cf7dcccffbd..636059d5b036f9 100644 --- a/packages/set-version/.eslintrc.json +++ b/packages/set-version/.eslintrc.json @@ -3,6 +3,7 @@ "root": true, "rules": { "deprecation/deprecation": "off", - "prefer-const": "off" + "prefer-const": "off", + "no-restricted-globals": "off" } }