From 7e748a6061602976f1ceb1c0f75a5716aacaa675 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 16 Aug 2023 17:33:06 +0000 Subject: [PATCH 01/44] chore: disallow `document` and `window` via eslint for React --- ...t-plugin-12ef753c-74ec-4c3c-8e65-e5121c05bde3.json | 7 +++++++ packages/eslint-plugin/src/configs/react-config.js | 11 +++++++++++ 2 files changed, 18 insertions(+) create mode 100644 change/@fluentui-eslint-plugin-12ef753c-74ec-4c3c-8e65-e5121c05bde3.json diff --git a/change/@fluentui-eslint-plugin-12ef753c-74ec-4c3c-8e65-e5121c05bde3.json b/change/@fluentui-eslint-plugin-12ef753c-74ec-4c3c-8e65-e5121c05bde3.json new file mode 100644 index 00000000000000..f842f73133064b --- /dev/null +++ b/change/@fluentui-eslint-plugin-12ef753c-74ec-4c3c-8e65-e5121c05bde3.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disable global `document` and `window` access for React.", + "packageName": "@fluentui/eslint-plugin", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/eslint-plugin/src/configs/react-config.js b/packages/eslint-plugin/src/configs/react-config.js index 2663520b69bd57..bffc88fff24991 100644 --- a/packages/eslint-plugin/src/configs/react-config.js +++ b/packages/eslint-plugin/src/configs/react-config.js @@ -28,6 +28,17 @@ module.exports = { '@griffel/hook-naming': 'error', '@griffel/no-shorthands': 'error', '@griffel/styles-file': 'error', + 'no-restricted-globals': [ + 'error', + { + name: 'window', + message: 'Get a reference to `window` via context.', + }, + { + name: 'document', + message: 'Get a reference to `document` via context.', + }, + ], /** * react eslint rules * @see https://github.com/yannickcr/eslint-plugin-react From 1c202d1d7b5767e05363366679c89aaaf9dfd1be Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 16 Aug 2023 20:13:00 +0000 Subject: [PATCH 02/44] disable 'no-restricted-globals' for tests --- packages/eslint-plugin/src/configs/react-config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/eslint-plugin/src/configs/react-config.js b/packages/eslint-plugin/src/configs/react-config.js index bffc88fff24991..87562b56cec670 100644 --- a/packages/eslint-plugin/src/configs/react-config.js +++ b/packages/eslint-plugin/src/configs/react-config.js @@ -132,6 +132,7 @@ module.exports = { // Test overrides files: [...configHelpers.testFiles], rules: { + 'no-restricted-globals': 'off', 'react/jsx-no-bind': 'off', }, }, From e82e43c2c7836b6b9b572ef01701734b5db1680f Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 6 Sep 2023 18:53:39 +0000 Subject: [PATCH 03/44] chore: update no-restricted-globals eslint rules For v8 this rule disallows direct usage of `document` and `window`. For v9 this rule disallows `document`, `window` and all properties, methods and events on `windows` (i.e., all globals). Rules for v9 are more restrictive as we believe this is safer for multi-window scenarios. Less restriction is applied for v8 because regardless of safety, v8's behavior is well-established at this point and we do not want to change it save for reported bugs. --- .../eslint-plugin/src/configs/react-config.js | 13 +- .../eslint-plugin/src/configs/react-legacy.js | 2 + .../src/configs/restricted-globals.js | 272 ++++++++++++++++++ 3 files changed, 276 insertions(+), 11 deletions(-) create mode 100644 packages/eslint-plugin/src/configs/restricted-globals.js diff --git a/packages/eslint-plugin/src/configs/react-config.js b/packages/eslint-plugin/src/configs/react-config.js index 87562b56cec670..606ad39ae7d33a 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,17 +29,7 @@ module.exports = { '@griffel/hook-naming': 'error', '@griffel/no-shorthands': 'error', '@griffel/styles-file': 'error', - 'no-restricted-globals': [ - 'error', - { - name: 'window', - message: 'Get a reference to `window` via context.', - }, - { - name: 'document', - message: 'Get a reference to `document` via context.', - }, - ], + 'no-restricted-globals': restrictedGlobals, /** * react eslint rules * @see https://github.com/yannickcr/eslint-plugin-react diff --git a/packages/eslint-plugin/src/configs/react-legacy.js b/packages/eslint-plugin/src/configs/react-legacy.js index 8d696ec41bfa1c..a0036618cefa58 100644 --- a/packages/eslint-plugin/src/configs/react-legacy.js +++ b/packages/eslint-plugin/src/configs/react-legacy.js @@ -1,6 +1,7 @@ // @ts-check const path = require('path'); +const { reactLegacy: restrictedGlobals } = require('./restricted-globals'); /** @type {import("eslint").Linter.Config} */ module.exports = { @@ -9,6 +10,7 @@ module.exports = { rules: { 'jsdoc/check-tag-names': 'off', '@griffel/no-shorthands': 'off', + 'no-restricted-globals': restrictedGlobals, }, 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..c204be64150f50 --- /dev/null +++ b/packages/eslint-plugin/src/configs/restricted-globals.js @@ -0,0 +1,272 @@ +// 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 reactLegacy = [ + 'error', + { + name: 'window', + message: 'Get a reference to `window` via context.', + }, + { + name: 'document', + message: 'Get a reference to `document` via context.', + }, +]; + +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 = { + reactLegacy, + react, +}; From 3b634d4a24baaf82f9235b5246b7b24b0bbe4737 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 4 Oct 2023 18:36:26 +0000 Subject: [PATCH 04/44] temporarily disable stricter rules for @fluentui/react-components --- .../eslint-plugin/src/configs/react-legacy.js | 13 +- .../src/configs/restricted-globals.js | 476 +++++++++--------- .../Callout/CalloutContent.base.tsx | 7 +- .../ChoiceGroup/ChoiceGroup.base.tsx | 10 +- .../components/Coachmark/Coachmark.base.tsx | 21 +- .../PositioningContainer.tsx | 17 +- .../ColorRectangle/ColorRectangle.base.tsx | 8 +- .../src/components/ComboBox/ComboBox.tsx | 9 +- .../DetailsList/DetailsList.base.tsx | 7 +- .../DocumentCard/DocumentCard.base.tsx | 10 +- .../DocumentCard/DocumentCardTitle.base.tsx | 12 +- .../src/components/Dropdown/Dropdown.base.tsx | 12 +- .../FocusTrapZone/FocusTrapZone.tsx | 6 +- .../KeytipLayer/KeytipLayer.base.tsx | 20 +- .../src/components/KeytipLayer/KeytipTree.ts | 10 +- packages/react/src/components/List/List.tsx | 8 +- .../MarqueeSelection.base.tsx | 16 +- .../react/src/components/Nav/Nav.base.tsx | 7 +- .../OverflowSet/OverflowSet.base.tsx | 6 +- .../react/src/components/Panel/Panel.base.tsx | 15 +- .../ScrollablePane/ScrollablePane.base.tsx | 10 +- .../ScrollablePane/ScrollablePane.types.ts | 6 +- .../BaseSelectedItemsList.tsx | 14 +- .../react/src/components/Slider/useSlider.ts | 10 +- .../react/src/components/Sticky/Sticky.tsx | 16 +- .../SwatchColorPicker.base.tsx | 10 +- .../src/components/Tooltip/Tooltip.styles.ts | 7 +- .../components/Tooltip/TooltipHost.base.tsx | 6 +- .../src/components/pickers/BasePicker.tsx | 10 +- .../utilities/DraggableZone/DraggableZone.tsx | 11 +- .../react/src/utilities/color/cssColor.ts | 20 +- .../src/utilities/color/getColorFromString.ts | 8 +- packages/react/src/utilities/dom.ts | 41 ++ .../src/utilities/positioning/positioning.ts | 25 +- .../src/utilities/selection/SelectionZone.tsx | 15 +- 35 files changed, 557 insertions(+), 342 deletions(-) create mode 100644 packages/react/src/utilities/dom.ts diff --git a/packages/eslint-plugin/src/configs/react-legacy.js b/packages/eslint-plugin/src/configs/react-legacy.js index a0036618cefa58..c355c69c8836d1 100644 --- a/packages/eslint-plugin/src/configs/react-legacy.js +++ b/packages/eslint-plugin/src/configs/react-legacy.js @@ -1,5 +1,5 @@ // @ts-check - +const configHelpers = require('../utils/configHelpers'); const path = require('path'); const { reactLegacy: restrictedGlobals } = require('./restricted-globals'); @@ -12,5 +12,14 @@ module.exports = { '@griffel/no-shorthands': 'off', 'no-restricted-globals': restrictedGlobals, }, - overrides: [], + overrides: [ + { + // Test overrides + files: [...configHelpers.testFiles], + rules: { + 'no-restricted-globals': 'off', + 'react/jsx-no-bind': 'off', + }, + }, + ], }; diff --git a/packages/eslint-plugin/src/configs/restricted-globals.js b/packages/eslint-plugin/src/configs/restricted-globals.js index c204be64150f50..f645af6956606b 100644 --- a/packages/eslint-plugin/src/configs/restricted-globals.js +++ b/packages/eslint-plugin/src/configs/restricted-globals.js @@ -1,240 +1,240 @@ // 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', +// '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', @@ -258,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 = { diff --git a/packages/react/src/components/Callout/CalloutContent.base.tsx b/packages/react/src/components/Callout/CalloutContent.base.tsx index 5f666dd3be6c32..e14239192e386e 100644 --- a/packages/react/src/components/Callout/CalloutContent.base.tsx +++ b/packages/react/src/components/Callout/CalloutContent.base.tsx @@ -20,6 +20,7 @@ import type { ICalloutProps, ICalloutContentStyleProps, ICalloutContentStyles } import type { Point, IRectangle } from '../../Utilities'; import type { ICalloutPositionedInfo, IPositionProps, IPosition } from '../../Positioning'; import type { Target } from '@fluentui/react-hooks'; +import { useWindowEx } from '../../utilities/dom'; const COMPONENT_NAME = 'CalloutContentBase'; @@ -161,6 +162,7 @@ function usePositions( const positionAttempts = React.useRef(0); const previousTarget = React.useRef(); const async = useAsync(); + const win = useWindowEx(); const { hidden, target, finalHeight, calloutMaxHeight, onPositioned, directionalHint } = props; React.useEffect(() => { @@ -184,8 +186,8 @@ function usePositions( // If there is a finalHeight given then we assume that the user knows and will handle // additional positioning adjustments so we should call positionCard const newPositions: ICalloutPositionedInfo = finalHeight - ? positionCard(currentProps, hostElement.current, dupeCalloutElement, previousPositions) - : positionCallout(currentProps, hostElement.current, dupeCalloutElement, previousPositions); + ? positionCard(currentProps, hostElement.current, dupeCalloutElement, previousPositions, win) + : positionCallout(currentProps, hostElement.current, dupeCalloutElement, previousPositions, win); // clean up duplicate calloutElement calloutElement.parentElement?.removeChild(dupeCalloutElement); @@ -233,6 +235,7 @@ function usePositions( positions, props, target, + win, ]); return positions; diff --git a/packages/react/src/components/ChoiceGroup/ChoiceGroup.base.tsx b/packages/react/src/components/ChoiceGroup/ChoiceGroup.base.tsx index 32d982585eee84..f081267d1effc8 100644 --- a/packages/react/src/components/ChoiceGroup/ChoiceGroup.base.tsx +++ b/packages/react/src/components/ChoiceGroup/ChoiceGroup.base.tsx @@ -20,6 +20,7 @@ import type { IChoiceGroup, } from './ChoiceGroup.types'; import type { IChoiceGroupOptionProps } from './ChoiceGroupOption/ChoiceGroupOption.types'; +import { useDocumentEx } from '../../utilities/dom'; const getClassNames = classNamesFunction(); @@ -36,9 +37,10 @@ const focusSelectedOption = ( keyChecked: IChoiceGroupProps['selectedKey'], id: string, focusProviders?: React.RefObject[], + doc?: Document, ) => { const optionToFocus = findOption(options, keyChecked) || options.filter(option => !option.disabled)[0]; - const elementToFocus = optionToFocus && document.getElementById(getOptionId(optionToFocus, id)); + const elementToFocus = optionToFocus && doc?.getElementById(getOptionId(optionToFocus, id)); if (elementToFocus) { elementToFocus.focus(); @@ -57,6 +59,8 @@ const useComponentRef = ( componentRef?: IRefObject, focusProviders?: React.RefObject[], ) => { + const doc = useDocumentEx(); + React.useImperativeHandle( componentRef, () => ({ @@ -64,10 +68,10 @@ const useComponentRef = ( return findOption(options, keyChecked); }, focus() { - focusSelectedOption(options, keyChecked, id, focusProviders); + focusSelectedOption(options, keyChecked, id, focusProviders, doc); }, }), - [options, keyChecked, id, focusProviders], + [options, keyChecked, id, focusProviders, doc], ); }; diff --git a/packages/react/src/components/Coachmark/Coachmark.base.tsx b/packages/react/src/components/Coachmark/Coachmark.base.tsx index ce307961c3c059..e349b247bc49ab 100644 --- a/packages/react/src/components/Coachmark/Coachmark.base.tsx +++ b/packages/react/src/components/Coachmark/Coachmark.base.tsx @@ -26,6 +26,7 @@ import type { IPositionedData } from '../../Positioning'; import type { IPositioningContainerProps } from './PositioningContainer/PositioningContainer.types'; import type { ICoachmarkProps, ICoachmarkStyles, ICoachmarkStyleProps } from './Coachmark.types'; import type { IBeakProps } from './Beak/Beak.types'; +import { useDocumentEx, useWindowEx } from '../../utilities/dom'; const getClassNames = classNamesFunction(); @@ -253,6 +254,8 @@ function useProximityHandlers( /** The target element the mouse would be in proximity to */ const targetElementRect = React.useRef(); + const win = useWindowEx(); + const doc = useDocumentEx(); React.useEffect(() => { const setTargetElementRect = (): void => { @@ -277,7 +280,7 @@ function useProximityHandlers( // When the window resizes we want to async get the bounding client rectangle. // Every time the event is triggered we want to setTimeout and then clear any previous // instances of setTimeout. - events.on(window, 'resize', (): void => { + events.on(win, 'resize', (): void => { timeoutIds.forEach((value: number): void => { clearTimeout(value); }); @@ -286,7 +289,7 @@ function useProximityHandlers( timeoutIds.push( setTimeout((): void => { setTargetElementRect(); - setBounds(getBounds(props.isPositionForced, props.positioningContainerProps)); + setBounds(getBounds(win, props.isPositionForced, props.positioningContainerProps)); }, 100), ); }); @@ -294,7 +297,7 @@ function useProximityHandlers( // Every time the document's mouse move is triggered, we want to check if inside of an element // and set the state with the result. - events.on(document, 'mousemove', (e: MouseEvent) => { + events.on(doc, 'mousemove', (e: MouseEvent) => { const mouseY = e.clientY; const mouseX = e.clientX; setTargetElementRect(); @@ -412,6 +415,7 @@ export const CoachmarkBase: React.FunctionComponent = React.for >((propsWithoutDefaults, forwardedRef) => { const props = getPropsWithDefaults(DEFAULT_PROPS, propsWithoutDefaults); + const win = useWindowEx(); const entityInnerHostElementRef = React.useRef(null); const translateAnimationContainer = React.useRef(null); @@ -420,7 +424,7 @@ export const CoachmarkBase: React.FunctionComponent = React.for const [beakPositioningProps, transformOrigin] = useBeakPosition(props, targetAlignment, targetPosition); const [isMeasuring, entityInnerHostRect] = useEntityHostMeasurements(props, entityInnerHostElementRef); const [bounds, setBounds] = React.useState( - getBounds(props.isPositionForced, props.positioningContainerProps), + getBounds(win, props.isPositionForced, props.positioningContainerProps), ); const alertText = useAriaAlert(props); const entityHost = useAutoFocus(props); @@ -431,8 +435,8 @@ export const CoachmarkBase: React.FunctionComponent = React.for useDeprecationWarning(props); React.useEffect(() => { - setBounds(getBounds(props.isPositionForced, props.positioningContainerProps)); - }, [props.isPositionForced, props.positioningContainerProps]); + setBounds(getBounds(win, props.isPositionForced, props.positioningContainerProps)); + }, [props.isPositionForced, props.positioningContainerProps, win]); const { beaconColorOne, @@ -537,6 +541,7 @@ export const CoachmarkBase: React.FunctionComponent = React.for CoachmarkBase.displayName = COMPONENT_NAME; function getBounds( + win: Window, isPositionForced?: boolean, positioningContainerProps?: IPositioningContainerProps, ): IRectangle | undefined { @@ -552,8 +557,8 @@ function getBounds( left: 0, top: -Infinity, bottom: Infinity, - right: window.innerWidth, - width: window.innerWidth, + right: win.innerWidth, + width: win.innerWidth, height: Infinity, }; } else { diff --git a/packages/react/src/components/Coachmark/PositioningContainer/PositioningContainer.tsx b/packages/react/src/components/Coachmark/PositioningContainer/PositioningContainer.tsx index 0bce232488344d..54750747253754 100644 --- a/packages/react/src/components/Coachmark/PositioningContainer/PositioningContainer.tsx +++ b/packages/react/src/components/Coachmark/PositioningContainer/PositioningContainer.tsx @@ -14,6 +14,7 @@ import { useMergedRefs, useAsync, useTarget } from '@fluentui/react-hooks'; import type { IPositioningContainerProps } from './PositioningContainer.types'; import type { Point, IRectangle } from '../../../Utilities'; import type { IPositionedData, IPositionProps, IPosition } from '../../../Positioning'; +import { useDocumentEx, useWindowEx } from '../../../utilities/dom'; const OFF_SCREEN_STYLE = { opacity: 0 }; @@ -64,6 +65,8 @@ function usePositionState( getCachedBounds: () => IRectangle, ) { const async = useAsync(); + const doc = useDocumentEx(); + const win = useWindowEx(); /** * Current set of calculated positions for the outermost parent container. */ @@ -90,13 +93,15 @@ function usePositionState( // or don't check anything else if the target is a Point or Rectangle if ( (!(target as Element).getBoundingClientRect && !(target as MouseEvent).preventDefault) || - document.body.contains(target as Node) + doc.body.contains(target as Node) ) { currentProps!.gapSpace = offsetFromTarget; const newPositions: IPositionedData = positionElement( currentProps!, hostElement, positioningContainerElement, + undefined, + win, ); // Set the new position only when the positions are not exists or one of the new positioningContainer // positions are different. The position should not change if the position is within 2 decimal places. @@ -152,6 +157,7 @@ function useMaxHeight( * without going beyond the window or target bounds */ const maxHeight = React.useRef(); + const win = useWindowEx(); // If the target element changed, reset the max height. If we are tracking // target with class name, always reset because we do not know if @@ -171,7 +177,14 @@ function useMaxHeight( if (!maxHeight.current) { if (directionalHintFixed && targetRef.current) { const gapSpace = offsetFromTarget ? offsetFromTarget : 0; - maxHeight.current = getMaxHeight(targetRef.current, directionalHint!, gapSpace, getCachedBounds()); + maxHeight.current = getMaxHeight( + targetRef.current, + directionalHint!, + gapSpace, + getCachedBounds(), + undefined, + win, + ); } else { maxHeight.current = getCachedBounds().height! - BORDER_WIDTH * 2; } diff --git a/packages/react/src/components/ColorPicker/ColorRectangle/ColorRectangle.base.tsx b/packages/react/src/components/ColorPicker/ColorRectangle/ColorRectangle.base.tsx index 1666f4076356e6..07ccd0de8d5b60 100644 --- a/packages/react/src/components/ColorPicker/ColorRectangle/ColorRectangle.base.tsx +++ b/packages/react/src/components/ColorPicker/ColorRectangle/ColorRectangle.base.tsx @@ -5,6 +5,8 @@ import { MAX_COLOR_SATURATION, MAX_COLOR_VALUE } from '../../../utilities/color/ import { getFullColorString } from '../../../utilities/color/getFullColorString'; import { updateSV } from '../../../utilities/color/updateSV'; import { clamp } from '../../../utilities/color/clamp'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getWindowEx } from '../../../utilities/dom'; import type { IColorRectangleProps, IColorRectangleStyleProps, @@ -26,6 +28,7 @@ export class ColorRectangleBase extends React.Component implements IColorRectangle { + public static contextType = WindowContext; public static defaultProps: Partial = { minSize: 220, ariaLabel: 'Saturation and brightness', @@ -183,9 +186,10 @@ export class ColorRectangleBase } private _onMouseDown = (ev: React.MouseEvent): void => { + const win = getWindowEx(this.context); this._disposables.push( - on(window, 'mousemove', this._onMouseMove as (ev: MouseEvent) => void, true), - on(window, 'mouseup', this._disposeListeners, true), + on(win, 'mousemove', this._onMouseMove as (ev: MouseEvent) => void, true), + on(win, 'mouseup', this._disposeListeners, true), ); this._onMouseMove(ev); diff --git a/packages/react/src/components/ComboBox/ComboBox.tsx b/packages/react/src/components/ComboBox/ComboBox.tsx index 630e7ba599f962..80e4c8ccc4bf93 100644 --- a/packages/react/src/components/ComboBox/ComboBox.tsx +++ b/packages/react/src/components/ComboBox/ComboBox.tsx @@ -41,6 +41,8 @@ import type { import type { IButtonStyles } from '../../Button'; import type { ICalloutProps } from '../../Callout'; import { getChildren } from '@fluentui/utilities'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getDocumentEx } from '../../utilities/dom'; export interface IComboBoxState { /** The open state */ @@ -243,6 +245,8 @@ function findFirstDescendant(element: HTMLElement, match: (element: HTMLElement) @customizable('ComboBox', ['theme', 'styles'], true) class ComboBoxInternal extends React.Component implements IComboBox { + public static contextType = WindowContext; + /** The input aspect of the combo box */ private _autofill = React.createRef(); @@ -368,6 +372,7 @@ class ComboBoxInternal extends React.Component this._scrollIntoView(), 0); } + const doc = getDocumentEx(this.context); // if an action is taken that put focus in the ComboBox // and If we are open or we are just closed, shouldFocusAfterClose is set, // but we are not the activeElement set focus on the input @@ -378,7 +383,7 @@ class ComboBoxInternal extends React.Component(); const COMPONENT_NAME = 'DetailsList'; @@ -779,6 +781,8 @@ export class DetailsListBase extends React.Component(); @@ -934,6 +938,7 @@ export class DetailsListBase extends React.Component 0 && this.state.focusedItemIndex !== -1 && - !elementContains(this._root.current, document.activeElement as HTMLElement, false) + !elementContains(this._root.current, doc.activeElement as HTMLElement, false) ) { // Item set has changed and previously-focused item is gone. // Set focus to item at index of previously-focused item if it is in range, diff --git a/packages/react/src/components/DocumentCard/DocumentCard.base.tsx b/packages/react/src/components/DocumentCard/DocumentCard.base.tsx index cac8816f5241e0..29fab300731206 100644 --- a/packages/react/src/components/DocumentCard/DocumentCard.base.tsx +++ b/packages/react/src/components/DocumentCard/DocumentCard.base.tsx @@ -16,6 +16,8 @@ import type { IDocumentCardStyleProps, IDocumentCardStyles, } from './DocumentCard.types'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getWindowEx } from '../../utilities/dom'; const getClassNames = classNamesFunction(); @@ -31,6 +33,8 @@ export class DocumentCardBase extends React.Component i type: DocumentCardType.normal, }; + public static contextType = WindowContext; + private _rootElement = React.createRef(); private _classNames: IProcessedStyleSet; @@ -109,14 +113,16 @@ export class DocumentCardBase extends React.Component i private _onAction = (ev: React.SyntheticEvent): void => { const { onClick, onClickHref, onClickTarget } = this.props; + const win = getWindowEx(this.context); + if (onClick) { onClick(ev); } else if (!onClick && onClickHref) { // If no onClick Function was provided and we do have an onClickHref, redirect to the onClickHref if (onClickTarget) { - window.open(onClickHref, onClickTarget, 'noreferrer noopener nofollow'); + win.open(onClickHref, onClickTarget, 'noreferrer noopener nofollow'); } else { - window.location.href = onClickHref; + win.location.href = onClickHref; } ev.preventDefault(); diff --git a/packages/react/src/components/DocumentCard/DocumentCardTitle.base.tsx b/packages/react/src/components/DocumentCard/DocumentCardTitle.base.tsx index 0dbaa541322973..3abe764a832b77 100644 --- a/packages/react/src/components/DocumentCard/DocumentCardTitle.base.tsx +++ b/packages/react/src/components/DocumentCard/DocumentCardTitle.base.tsx @@ -9,6 +9,8 @@ import type { } from './DocumentCardTitle.types'; import type { IProcessedStyleSet } from '../../Styling'; import { DocumentCardContext } from './DocumentCard.base'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getWindowEx } from '../../utilities/dom'; const getClassNames = classNamesFunction(); @@ -23,6 +25,8 @@ const TRUNCATION_VERTICAL_OVERFLOW_THRESHOLD = 5; * {@docCategory DocumentCard} */ export class DocumentCardTitleBase extends React.Component { + public static contextType = WindowContext; + private _titleElement = React.createRef(); private _classNames: IProcessedStyleSet; private _async: Async; @@ -53,12 +57,13 @@ export class DocumentCardTitleBase extends React.Component { @@ -71,7 +76,8 @@ export class DocumentCardTitleBase extends React.Component(); @@ -187,6 +189,8 @@ class DropdownInternal extends React.Component(); private _focusZone = React.createRef(); private _dropDown = React.createRef(); @@ -937,14 +941,15 @@ class DropdownInternal extends React.Component { + const win = getWindowEx(this.context); if (!this._isScrollIdle && this._scrollIdleTimeoutId !== undefined) { - clearTimeout(this._scrollIdleTimeoutId); + win.clearTimeout(this._scrollIdleTimeoutId); this._scrollIdleTimeoutId = undefined; } else { this._isScrollIdle = false; } - this._scrollIdleTimeoutId = window.setTimeout(() => { + this._scrollIdleTimeoutId = win.setTimeout(() => { this._isScrollIdle = true; }, this._scrollIdleDelay); }; @@ -959,10 +964,11 @@ class DropdownInternal extends React.Component): void { + const doc = getDocumentEx(this.context); const targetElement = ev.currentTarget as HTMLElement; this._gotMouseMove = true; - if (!this._isScrollIdle || document.activeElement === targetElement) { + if (!this._isScrollIdle || doc.activeElement === targetElement) { return; } diff --git a/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx b/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx index b4b6c6e9a75ef6..db9e9693fb0003 100644 --- a/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx +++ b/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx @@ -15,6 +15,7 @@ import { useId, useConst, useMergedRefs, useEventCallback, usePrevious, useUnmou import { useDocument } from '../../WindowProvider'; import type { IRefObject } from '../../Utilities'; import type { IFocusTrapZoneProps, IFocusTrapZone } from './FocusTrapZone.types'; +import { useWindowEx } from '../../utilities/dom'; interface IFocusTrapZoneInternalState { previouslyFocusedElementInTrapZone?: HTMLElement; @@ -62,6 +63,7 @@ export const FocusTrapZone: React.FunctionComponent & { const lastBumper = React.useRef(null); const mergedRootRef = useMergedRefs(root, ref) as React.Ref; const doc = useDocument(); + const win = useWindowEx(); const isFirstRender = usePrevious(false) ?? true; @@ -263,10 +265,10 @@ export const FocusTrapZone: React.FunctionComponent & { const disposables: Array<() => void> = []; if (forceFocusInsideTrap) { - disposables.push(on(window, 'focus', forceFocusOrClickInTrap, true)); + disposables.push(on(win, 'focus', forceFocusOrClickInTrap, true)); } if (!isClickableOutsideFocusTrap) { - disposables.push(on(window, 'click', forceFocusOrClickInTrap, true)); + disposables.push(on(win, 'click', forceFocusOrClickInTrap, true)); } return () => { diff --git a/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx b/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx index 6f539afcfd1d7b..457a85d574deae 100644 --- a/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx +++ b/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx @@ -28,6 +28,8 @@ import type { IKeytipLayerProps, IKeytipLayerStyles, IKeytipLayerStyleProps } fr import type { IKeytipProps } from '../../Keytip'; import type { IKeytipTreeNode } from './IKeytipTreeNode'; import type { KeytipTransitionModifier, IKeytipTransitionKey } from '../../utilities/keytips/IKeytipTransitionKey'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getDocumentEx, getWindowEx } from '../../utilities/dom'; export interface IKeytipLayerState { inKeytipMode: boolean; @@ -63,6 +65,8 @@ export class KeytipLayerBase extends React.Component { + const doc = getDocumentEx(this.context); const targetSelector = ktpTargetFromSequences(keySequences); - const matchingElements = document.querySelectorAll(targetSelector); + const matchingElements = doc.querySelectorAll(targetSelector); // If there are multiple elements for the keytip sequence, return true if the element instance // that corresponds to the keytip instance is visible, otherwise return if there is only one instance diff --git a/packages/react/src/components/KeytipLayer/KeytipTree.ts b/packages/react/src/components/KeytipLayer/KeytipTree.ts index 42d8f922df505f..6313928d0f2f16 100644 --- a/packages/react/src/components/KeytipLayer/KeytipTree.ts +++ b/packages/react/src/components/KeytipLayer/KeytipTree.ts @@ -122,9 +122,15 @@ export class KeytipTree { * * @param keySequence - string to match * @param currentKeytip - The keytip whose children will try to match + * @param doc - The document for DOM operations * @returns The node that exactly matched the keySequence, or undefined if none matched */ - public getExactMatchedNode(keySequence: string, currentKeytip: IKeytipTreeNode): IKeytipTreeNode | undefined { + public getExactMatchedNode( + keySequence: string, + currentKeytip: IKeytipTreeNode, + // eslint-disable-next-line no-restricted-globals + doc: Document = document, + ): IKeytipTreeNode | undefined { const possibleNodes = this.getNodes(currentKeytip.children); const matchingNodes = possibleNodes.filter((node: IKeytipTreeNode) => { return this._getNodeSequence(node) === keySequence && !node.disabled; @@ -149,7 +155,7 @@ export class KeytipTree { const overflowSetSequence = node.overflowSetSequence; const fullKeySequences = overflowSetSequence ? mergeOverflows(keySequences, overflowSetSequence) : keySequences; const keytipTargetSelector = ktpTargetFromSequences(fullKeySequences); - const potentialTargetElements = document.querySelectorAll(keytipTargetSelector); + const potentialTargetElements = doc.querySelectorAll(keytipTargetSelector); // If we have less nodes than the potential target elements, // we won't be able to map element to node, return the first node. diff --git a/packages/react/src/components/List/List.tsx b/packages/react/src/components/List/List.tsx index 9ffc4d26f85b6c..003bc18fd71a8b 100644 --- a/packages/react/src/components/List/List.tsx +++ b/packages/react/src/components/List/List.tsx @@ -24,6 +24,8 @@ import type { IListOnRenderSurfaceProps, IListOnRenderRootProps, } from './List.types'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getWindowEx } from '../../utilities/dom'; const RESIZE_DELAY = 16; const MIN_SCROLL_UPDATE_DELAY = 100; @@ -105,6 +107,8 @@ export class List extends React.Component, IListState> renderedWindowsBehind: DEFAULT_RENDERED_WINDOWS_BEHIND, }; + public static contextType = WindowContext; + private _root = React.createRef(); private _surface = React.createRef(); private _pageRefs: Record = {}; @@ -342,7 +346,9 @@ export class List extends React.Component, IListState> this.setState({ ...this._updatePages(this.props, this.state), hasMounted: true }); this._measureVersion++; - this._events.on(window, 'resize', this._onAsyncResize); + const win = getWindowEx(this.context); + + this._events.on(win, 'resize', this._onAsyncResize); if (this._root.current) { this._events.on(this._root.current, 'focus', this._onFocus, true); } diff --git a/packages/react/src/components/MarqueeSelection/MarqueeSelection.base.tsx b/packages/react/src/components/MarqueeSelection/MarqueeSelection.base.tsx index 79de834f3dedd8..8929e7d1e25444 100644 --- a/packages/react/src/components/MarqueeSelection/MarqueeSelection.base.tsx +++ b/packages/react/src/components/MarqueeSelection/MarqueeSelection.base.tsx @@ -16,6 +16,8 @@ import type { IMarqueeSelectionStyleProps, IMarqueeSelectionStyles, } from './MarqueeSelection.types'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getDocumentEx, getWindowEx } from '../../utilities/dom'; const getClassNames = classNamesFunction(); @@ -41,6 +43,8 @@ export class MarqueeSelectionBase extends React.Component(); @@ -71,8 +75,10 @@ export class MarqueeSelectionBase extends React.Component implements IN groups: null, }; + public static contextType = WindowContext; + private _focusZone = React.createRef(); constructor(props: INavProps) { super(props); @@ -360,8 +364,9 @@ export class NavBase extends React.Component implements IN // resolve is not supported for ssr return false; } else { + const doc = getDocumentEx(this.context); // If selectedKey is undefined in props and state, then check URL - _urlResolver = _urlResolver || document.createElement('a'); + _urlResolver = _urlResolver || doc.createElement('a'); _urlResolver.href = link.url || ''; const target: string = _urlResolver.href; diff --git a/packages/react/src/components/OverflowSet/OverflowSet.base.tsx b/packages/react/src/components/OverflowSet/OverflowSet.base.tsx index 32d0ac872e0811..91efe6a63e14d0 100644 --- a/packages/react/src/components/OverflowSet/OverflowSet.base.tsx +++ b/packages/react/src/components/OverflowSet/OverflowSet.base.tsx @@ -4,11 +4,13 @@ import { classNamesFunction, divProperties, elementContains, getNativeProps, foc import { OverflowButton } from './OverflowButton'; import type { IProcessedStyleSet } from '../../Styling'; import type { IOverflowSetProps, IOverflowSetStyles, IOverflowSetStyleProps, IOverflowSet } from './OverflowSet.types'; +import { useDocumentEx } from '../../utilities/dom'; const getClassNames = classNamesFunction(); const COMPONENT_NAME = 'OverflowSet'; const useComponentRef = (props: IOverflowSetProps, divContainer: React.RefObject) => { + const doc = useDocumentEx(); React.useImperativeHandle( props.componentRef, (): IOverflowSet => ({ @@ -26,12 +28,12 @@ const useComponentRef = (props: IOverflowSetProps, divContainer: React.RefObject } if (divContainer.current && elementContains(divContainer.current, childElement)) { childElement.focus(); - focusSucceeded = document.activeElement === childElement; + focusSucceeded = doc.activeElement === childElement; } return focusSucceeded; }, }), - [divContainer], + [divContainer, doc], ); }; diff --git a/packages/react/src/components/Panel/Panel.base.tsx b/packages/react/src/components/Panel/Panel.base.tsx index 8438e2ad06a36b..a6b0ed35b6a29a 100644 --- a/packages/react/src/components/Panel/Panel.base.tsx +++ b/packages/react/src/components/Panel/Panel.base.tsx @@ -22,6 +22,8 @@ import { FocusTrapZone } from '../FocusTrapZone/index'; import { PanelType } from './Panel.types'; import type { IProcessedStyleSet } from '../../Styling'; import type { IPanel, IPanelProps, IPanelStyleProps, IPanelStyles } from './Panel.types'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getDocumentEx, getWindowEx } from '../../utilities/dom'; const getClassNames = classNamesFunction(); const COMPONENT_NAME = 'Panel'; @@ -48,6 +50,8 @@ export class PanelBase extends React.Component impleme type: PanelType.smallFixedFar, }; + public static contextType = WindowContext; + private _async: Async; private _events: EventGroup; private _panel = React.createRef(); @@ -107,11 +111,13 @@ export class PanelBase extends React.Component impleme public componentDidMount(): void { this._async = new Async(this); this._events = new EventGroup(this); + const win = getWindowEx(this.context); + const doc = getDocumentEx(this.context); - this._events.on(window, 'resize', this._updateFooterPosition); + this._events.on(win, 'resize', this._updateFooterPosition); if (this._shouldListenForOuterClick(this.props)) { - this._events.on(document.body, 'mousedown', this._dismissOnOuterClick, true); + this._events.on(doc.body, 'mousedown', this._dismissOnOuterClick, true); } if (this.props.isOpen) { @@ -132,10 +138,11 @@ export class PanelBase extends React.Component impleme } } + const doc = getDocumentEx(this.context); if (shouldListenOnOuterClick && !previousShouldListenOnOuterClick) { - this._events.on(document.body, 'mousedown', this._dismissOnOuterClick, true); + this._events.on(doc.body, 'mousedown', this._dismissOnOuterClick, true); } else if (!shouldListenOnOuterClick && previousShouldListenOnOuterClick) { - this._events.off(document.body, 'mousedown', this._dismissOnOuterClick, true); + this._events.off(doc.body, 'mousedown', this._dismissOnOuterClick, true); } } diff --git a/packages/react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/react/src/components/ScrollablePane/ScrollablePane.base.tsx index ce2d43c27d7194..9a3a0450db491f 100644 --- a/packages/react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -17,6 +17,8 @@ import type { IScrollablePaneStyleProps, IScrollablePaneStyles, } from './ScrollablePane.types'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getWindowEx } from '../../utilities/dom'; export interface IScrollablePaneState { stickyTopHeight: number; @@ -31,6 +33,8 @@ export class ScrollablePaneBase extends React.Component implements IScrollablePane { + public static contextType = WindowContext; + private _root = React.createRef(); private _stickyAboveRef = React.createRef(); private _stickyBelowRef = React.createRef(); @@ -79,8 +83,9 @@ export class ScrollablePaneBase public componentDidMount() { const { initialScrollPosition } = this.props; + const win = getWindowEx(this.context); this._events.on(this.contentContainer, 'scroll', this._onScroll); - this._events.on(window, 'resize', this._onWindowResize); + this._events.on(win, 'resize', this._onWindowResize); if (this.contentContainer && initialScrollPosition) { this.contentContainer.scrollTop = initialScrollPosition; } @@ -92,7 +97,7 @@ export class ScrollablePaneBase }); this.notifySubscribers(); - if ('MutationObserver' in window) { + if ('MutationObserver' in win) { this._mutationObserver = new MutationObserver(mutation => { // Function to check if mutation is occuring in stickyAbove or stickyBelow function checkIfMutationIsSticky(mutationRecord: MutationRecord): boolean { @@ -354,6 +359,7 @@ export class ScrollablePaneBase notifySubscribers: this.notifySubscribers, syncScrollSticky: this.syncScrollSticky, }, + window: getWindowEx(this.context), }; }; diff --git a/packages/react/src/components/ScrollablePane/ScrollablePane.types.ts b/packages/react/src/components/ScrollablePane/ScrollablePane.types.ts index fe6a24d16d3c6e..78ab4edfc1a4e1 100644 --- a/packages/react/src/components/ScrollablePane/ScrollablePane.types.ts +++ b/packages/react/src/components/ScrollablePane/ScrollablePane.types.ts @@ -130,6 +130,10 @@ export interface IScrollablePaneContext { notifySubscribers: (sort?: boolean) => void; syncScrollSticky: (sticky: Sticky) => void; }; + window: Window | undefined; } -export const ScrollablePaneContext = React.createContext({ scrollablePane: undefined }); +export const ScrollablePaneContext = React.createContext({ + scrollablePane: undefined, + window: undefined, +}); diff --git a/packages/react/src/components/SelectedItemsList/BaseSelectedItemsList.tsx b/packages/react/src/components/SelectedItemsList/BaseSelectedItemsList.tsx index 5254f6ab1f19da..ec4ceee549c750 100644 --- a/packages/react/src/components/SelectedItemsList/BaseSelectedItemsList.tsx +++ b/packages/react/src/components/SelectedItemsList/BaseSelectedItemsList.tsx @@ -7,6 +7,8 @@ import type { ISelectedItemProps, } from './BaseSelectedItemsList.types'; import type { IObjectWithKey } from '../../Utilities'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getDocumentEx } from '../../utilities/dom'; export interface IBaseSelectedItemsListState { items: T[]; @@ -16,6 +18,8 @@ export class BaseSelectedItemsList> extends React.Component> implements IBaseSelectedItemsList { + public static contextType = WindowContext; + protected root: HTMLElement; private _defaultSelection: Selection; @@ -213,22 +217,24 @@ export class BaseSelectedItemsList> if (this.props.onCopyItems) { const copyText = (this.props.onCopyItems as any)(items); - const copyInput = document.createElement('input') as HTMLInputElement; - document.body.appendChild(copyInput); + const doc = getDocumentEx(this.context); + + const copyInput = doc.createElement('input') as HTMLInputElement; + doc.body.appendChild(copyInput); try { // Try to copy the text directly to the clipboard copyInput.value = copyText; copyInput.select(); // eslint-disable-next-line deprecation/deprecation - if (!document.execCommand('copy')) { + if (!doc.execCommand('copy')) { // The command failed. Fallback to the method below. throw new Error(); } } catch (err) { // no op } finally { - document.body.removeChild(copyInput); + doc.body.removeChild(copyInput); } } } diff --git a/packages/react/src/components/Slider/useSlider.ts b/packages/react/src/components/Slider/useSlider.ts index ddfa1015218520..4f2c0684845694 100644 --- a/packages/react/src/components/Slider/useSlider.ts +++ b/packages/react/src/components/Slider/useSlider.ts @@ -12,6 +12,7 @@ import { } from '@fluentui/utilities'; import type { ISliderProps, ISliderStyleProps, ISliderStyles } from './Slider.types'; import type { ILabelProps } from '../Label/index'; +import { useWindowEx } from '../../utilities/dom'; export const ONKEYDOWN_TIMEOUT_DURATION = 1000; @@ -101,6 +102,7 @@ export const useSlider = (props: ISliderProps, ref: React.ForwardedRef void)[]>([]); const { setTimeout, clearTimeout } = useSetTimeout(); const sliderLine = React.useRef(null); + const win = useWindowEx(); // Casting here is necessary because useControllableValue expects the event for the change callback // to extend React.SyntheticEvent, when in fact for Slider, the event could be either a React event @@ -305,13 +307,13 @@ export const useSlider = (props: ISliderProps, ref: React.ForwardedRef void, true), - on(window, 'mouseup', onMouseUpOrTouchEnd, true), + on(win, 'mousemove', onMouseMoveOrTouchMove as (ev: Event) => void, true), + on(win, 'mouseup', onMouseUpOrTouchEnd, true), ); } else if (event.type === 'touchstart') { disposables.current.push( - on(window, 'touchmove', onMouseMoveOrTouchMove as (ev: Event) => void, true), - on(window, 'touchend', onMouseUpOrTouchEnd, true), + on(win, 'touchmove', onMouseMoveOrTouchMove as (ev: Event) => void, true), + on(win, 'touchend', onMouseUpOrTouchEnd, true), ); } onMouseMoveOrTouchMove(event, true); diff --git a/packages/react/src/components/Sticky/Sticky.tsx b/packages/react/src/components/Sticky/Sticky.tsx index fa89e9c5c7b980..6e35a44910e1b3 100644 --- a/packages/react/src/components/Sticky/Sticky.tsx +++ b/packages/react/src/components/Sticky/Sticky.tsx @@ -263,6 +263,8 @@ export class Sticky extends React.Component { const distanceFromTop = this._getNonStickyDistanceFromTop(container); let isStickyTop = false; let isStickyBottom = false; + // eslint-disable-next-line no-restricted-globals + const doc = (this._getContext().window ?? window)?.document; if (this.canStickyTop) { const distanceToStickTop = distanceFromTop - this._getStickyDistanceFromTop(); @@ -279,11 +281,11 @@ export class Sticky extends React.Component { } if ( - document.activeElement && - this.nonStickyContent.contains(document.activeElement) && + doc?.activeElement && + this.nonStickyContent.contains(doc?.activeElement) && (this.state.isStickyTop !== isStickyTop || this.state.isStickyBottom !== isStickyBottom) ) { - this._activeElement = document.activeElement as HTMLElement; + this._activeElement = doc?.activeElement as HTMLElement; } else { this._activeElement = undefined; } @@ -342,10 +344,12 @@ export class Sticky extends React.Component { } let curr: HTMLElement = this.root; + // eslint-disable-next-line no-restricted-globals + const win = this._getContext().window ?? window; while ( - window.getComputedStyle(curr).getPropertyValue('background-color') === 'rgba(0, 0, 0, 0)' || - window.getComputedStyle(curr).getPropertyValue('background-color') === 'transparent' + win?.getComputedStyle(curr).getPropertyValue('background-color') === 'rgba(0, 0, 0, 0)' || + win?.getComputedStyle(curr).getPropertyValue('background-color') === 'transparent' ) { if (curr.tagName === 'HTML') { // Fallback color if no element has a declared background-color attribute @@ -355,7 +359,7 @@ export class Sticky extends React.Component { curr = curr.parentElement; } } - return window.getComputedStyle(curr).getPropertyValue('background-color'); + return win?.getComputedStyle(curr).getPropertyValue('background-color'); } } diff --git a/packages/react/src/components/SwatchColorPicker/SwatchColorPicker.base.tsx b/packages/react/src/components/SwatchColorPicker/SwatchColorPicker.base.tsx index f8b4365120e2a5..10ba7e2e2aa635 100644 --- a/packages/react/src/components/SwatchColorPicker/SwatchColorPicker.base.tsx +++ b/packages/react/src/components/SwatchColorPicker/SwatchColorPicker.base.tsx @@ -10,6 +10,7 @@ import type { } from './SwatchColorPicker.types'; import type { IColorCellProps } from './ColorPickerGridCell.types'; import type { IButtonGridProps } from '../../utilities/ButtonGrid/ButtonGrid.types'; +import { useDocumentEx } from '../../utilities/dom'; interface ISwatchColorPickerInternalState { isNavigationIdle: boolean; @@ -40,6 +41,7 @@ export const SwatchColorPickerBase: React.FunctionComponent((props, ref) => { const defaultId = useId('swatchColorPicker'); const id = props.id || defaultId; + const doc = useDocumentEx(); const internalState = useConst({ isNavigationIdle: true, @@ -161,13 +163,13 @@ export const SwatchColorPickerBase: React.FunctionComponent { // The math here is done to account for the 45 degree rotation of the beak // and sub-pixel rounding that differs across browsers, which is more noticeable when // the device pixel ratio is larger - const tooltipGapSpace = -(Math.sqrt((beakWidth * beakWidth) / 2) + gapSpace) + 1 / window.devicePixelRatio; + const tooltipGapSpace = + -(Math.sqrt((beakWidth * beakWidth) / 2) + gapSpace) + + 1 / + // There isn't really a great way to pass in a `window` reference here so disabling the line rule + // eslint-disable-next-line no-restricted-globals + window.devicePixelRatio; return { root: [ diff --git a/packages/react/src/components/Tooltip/TooltipHost.base.tsx b/packages/react/src/components/Tooltip/TooltipHost.base.tsx index 39175c58acb404..eb779d88c9e0ce 100644 --- a/packages/react/src/components/Tooltip/TooltipHost.base.tsx +++ b/packages/react/src/components/Tooltip/TooltipHost.base.tsx @@ -16,6 +16,8 @@ import { TooltipOverflowMode } from './TooltipHost.types'; import { Tooltip } from './Tooltip'; import { TooltipDelay } from './Tooltip.types'; import type { ITooltipHostProps, ITooltipHostStyles, ITooltipHostStyleProps, ITooltipHost } from './TooltipHost.types'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getDocumentEx } from '../../utilities/dom'; export interface ITooltipHostState { /** @deprecated No longer used internally */ @@ -30,6 +32,8 @@ export class TooltipHostBase extends React.Component { this._hideTooltip(); diff --git a/packages/react/src/components/pickers/BasePicker.tsx b/packages/react/src/components/pickers/BasePicker.tsx index 6a6de045a6aedb..e3b7dbe83be739 100644 --- a/packages/react/src/components/pickers/BasePicker.tsx +++ b/packages/react/src/components/pickers/BasePicker.tsx @@ -29,6 +29,8 @@ import type { import type { IBasePicker, IBasePickerProps, IBasePickerStyleProps, IBasePickerStyles } from './BasePicker.types'; import type { IAutofill } from '../Autofill/index'; import type { IPickerItemProps } from './PickerItem.types'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getDocumentEx } from '../../utilities/dom'; const legacyStyles: any = stylesImport; @@ -95,6 +97,7 @@ export class BasePicker> extends React.Component> implements IBasePicker { + public static contextType = WindowContext; // Refs protected root = React.createRef(); protected input = React.createRef(); @@ -505,7 +508,8 @@ export class BasePicker> }); } else { this.setState({ - suggestionsVisible: this.input.current! && this.input.current!.inputElement === document.activeElement, + suggestionsVisible: + this.input.current! && this.input.current!.inputElement === getDocumentEx(this.context).activeElement, }); } @@ -599,7 +603,7 @@ export class BasePicker> // even when it's not. Using document.activeElement is another way // for us to be able to get what the relatedTarget without relying // on the event - relatedTarget = document.activeElement; + relatedTarget = getDocumentEx(this.context).activeElement; } if (relatedTarget && !elementContains(this.root.current!, relatedTarget as HTMLElement)) { this.setState({ isFocused: false }); @@ -1032,7 +1036,7 @@ export class BasePicker> const areSuggestionsVisible = this.input.current !== undefined && this.input.current !== null && - this.input.current.inputElement === document.activeElement && + this.input.current.inputElement === getDocumentEx(this.context).activeElement && this.input.current.value !== ''; return areSuggestionsVisible; diff --git a/packages/react/src/utilities/DraggableZone/DraggableZone.tsx b/packages/react/src/utilities/DraggableZone/DraggableZone.tsx index c9df1913d94781..bca483a0d55ccd 100644 --- a/packages/react/src/utilities/DraggableZone/DraggableZone.tsx +++ b/packages/react/src/utilities/DraggableZone/DraggableZone.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { getClassNames } from './DraggableZone.styles'; import { on } from '../../Utilities'; import type { IDraggableZoneProps, ICoordinates, IDragData } from './DraggableZone.types'; +import { WindowContext } from '@fluentui/react-window-provider'; +import { getDocumentEx } from '../dom'; export interface IDraggableZoneState { isDragging: boolean; @@ -27,6 +29,8 @@ const eventMapping = { type MouseTouchEvent = React.MouseEvent & React.TouchEvent & Event; export class DraggableZone extends React.Component { + public static contextType = WindowContext; + private _touchId?: number; private _currentEventType = eventMapping.mouse; private _events: (() => void)[] = []; @@ -153,9 +157,10 @@ export class DraggableZone extends React.Component { + // eslint-disable-next-line no-restricted-globals + return useDocument() ?? document; +}; + +/** + * Wrapper for `useWindow()` that falls back to `window` if the provider is not set. + * @returns Window + */ +export const useWindowEx = () => { + // eslint-disable-next-line no-restricted-globals + return useWindow() ?? window; +}; + +/** + * Helper for reading class component WindowContext. Falls back to `document` if the provider is not set. + * + * @param ctx - Class component WindowContext + * @returns Document + */ +export const getDocumentEx = (ctx: Pick | undefined) => { + // eslint-disable-next-line no-restricted-globals + return ctx?.window?.document ?? document; +}; + +/** + * Helper for reading class component WindowContext. Falls back to `window` if the provider is not set. + * + * @param ctx - Class component WindowContext + * @returns Window + */ +export const getWindowEx = (ctx: Pick | undefined) => { + // eslint-disable-next-line no-restricted-globals + return ctx?.window ?? window; +}; diff --git a/packages/react/src/utilities/positioning/positioning.ts b/packages/react/src/utilities/positioning/positioning.ts index cd88583b5a3e17..657ec0f149af4c 100644 --- a/packages/react/src/utilities/positioning/positioning.ts +++ b/packages/react/src/utilities/positioning/positioning.ts @@ -864,6 +864,7 @@ function _finalizePositionData( } function _positionElement( + win: Window, props: IPositionProps, hostElement: HTMLElement, elementToPosition: HTMLElement, @@ -871,7 +872,7 @@ function _positionElement( ): IPositionedData { const boundingRect: Rectangle = props.bounds ? _getRectangleFromIRect(props.bounds) - : new Rectangle(0, window.innerWidth - getScrollbarWidth(), 0, window.innerHeight); + : new Rectangle(0, win.innerWidth - getScrollbarWidth(), 0, win.innerHeight); const positionedElement: IElementPosition = _positionElementRelative( props, elementToPosition, @@ -882,6 +883,7 @@ function _positionElement( } function _positionCallout( + win: Window, props: ICalloutPositionProps, hostElement: HTMLElement, callout: HTMLElement, @@ -894,7 +896,7 @@ function _positionCallout( positionProps.gapSpace = gap; const boundingRect: Rectangle = props.bounds ? _getRectangleFromIRect(props.bounds) - : new Rectangle(0, window.innerWidth - getScrollbarWidth(), 0, window.innerHeight); + : new Rectangle(0, win.innerWidth - getScrollbarWidth(), 0, win.innerHeight); const positionedElement: IElementPositionInfo = _positionElementRelative( positionProps, callout, @@ -916,12 +918,13 @@ function _positionCallout( } function _positionCard( + win: Window, props: ICalloutPositionProps, hostElement: HTMLElement, callout: HTMLElement, previousPositions?: ICalloutPositionedInfo, ): ICalloutPositionedInfo { - return _positionCallout(props, hostElement, callout, previousPositions, true); + return _positionCallout(win, props, hostElement, callout, previousPositions, true); } // END PRIVATE FUNCTIONS @@ -945,8 +948,10 @@ export function positionElement( hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: IPositionedData, + // eslint-disable-next-line no-restricted-globals + win: Window = window, ): IPositionedData { - return _positionElement(props, hostElement, elementToPosition, previousPositions); + return _positionElement(win, props, hostElement, elementToPosition, previousPositions); } export function positionCallout( @@ -954,8 +959,10 @@ export function positionCallout( hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: ICalloutPositionedInfo, + // eslint-disable-next-line no-restricted-globals + win: Window = window, ): ICalloutPositionedInfo { - return _positionCallout(props, hostElement, elementToPosition, previousPositions); + return _positionCallout(win, props, hostElement, elementToPosition, previousPositions); } export function positionCard( @@ -963,8 +970,10 @@ export function positionCard( hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: ICalloutPositionedInfo, + // eslint-disable-next-line no-restricted-globals + win: Window = window, ): ICalloutPositionedInfo { - return _positionCard(props, hostElement, elementToPosition, previousPositions); + return _positionCard(win, props, hostElement, elementToPosition, previousPositions); } /** @@ -979,6 +988,8 @@ export function getMaxHeight( gapSpace: number = 0, bounds?: IRectangle, coverTarget?: boolean, + // eslint-disable-next-line no-restricted-globals + win: Window = window, ): number { const mouseTarget: MouseEvent = target as MouseEvent; const elementTarget: Element = target as Element; @@ -986,7 +997,7 @@ export function getMaxHeight( let targetRect: Rectangle; const boundingRectangle = bounds ? _getRectangleFromIRect(bounds) - : new Rectangle(0, window.innerWidth - getScrollbarWidth(), 0, window.innerHeight); + : new Rectangle(0, win.innerWidth - getScrollbarWidth(), 0, win.innerHeight); // eslint-disable-next-line deprecation/deprecation const left = rectOrPointTarget.left || rectOrPointTarget.x; diff --git a/packages/react/src/utilities/selection/SelectionZone.tsx b/packages/react/src/utilities/selection/SelectionZone.tsx index 01fafadb6f6e70..82ed097d3b7271 100644 --- a/packages/react/src/utilities/selection/SelectionZone.tsx +++ b/packages/react/src/utilities/selection/SelectionZone.tsx @@ -200,12 +200,13 @@ export class SelectionZone extends React.Component): void => { let target = ev.target as HTMLElement; + const win = getWindow(this._root.current); + const doc = win?.document; - if (document.activeElement !== target && !elementContains(document.activeElement as HTMLElement, target)) { + if (doc?.activeElement !== target && !elementContains(doc?.activeElement as HTMLElement, target)) { this.ignoreNextFocus(); return; } @@ -737,9 +740,11 @@ export class SelectionZone extends React.Component Date: Wed, 4 Oct 2023 21:46:19 +0000 Subject: [PATCH 05/44] update @fluentui/react API snapshot --- packages/react/etc/react.api.md | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/react/etc/react.api.md b/packages/react/etc/react.api.md index 46d51c6deb703b..8175036057ddcb 100644 --- a/packages/react/etc/react.api.md +++ b/packages/react/etc/react.api.md @@ -691,6 +691,8 @@ export class BasePicker> extends React_2.Compon // (undocumented) componentWillUnmount(): void; // (undocumented) + static contextType: React_2.Context; + // (undocumented) protected currentPromise: PromiseLike | undefined; // (undocumented) dismissSuggestions: (ev?: any) => void; @@ -797,6 +799,8 @@ export class BaseSelectedItemsList> // (undocumented) componentDidUpdate(oldProps: P, oldState: IBaseSelectedItemsListState): void; // (undocumented) + static contextType: React_2.Context; + // (undocumented) protected copyItems(items: T[]): void; // (undocumented) static getDerivedStateFromProps(newProps: IBaseSelectedItemsListProps): { @@ -1173,7 +1177,7 @@ export { createTheme } export { css } // @public -export function cssColor(color?: string): IRGB | undefined; +export function cssColor(color?: string, doc?: Document): IRGB | undefined; export { customizable } @@ -1306,6 +1310,8 @@ export class DetailsListBase extends React_2.Component; + // (undocumented) static defaultProps: { layoutMode: DetailsListLayoutMode; selectionMode: SelectionMode_2; @@ -1746,7 +1752,7 @@ export function getColorFromHSV(hsv: IHSV, a?: number): IColor; export function getColorFromRGBA(rgba: IRGB): IColor; // @public -export function getColorFromString(inputColor: string): IColor | undefined; +export function getColorFromString(inputColor: string, doc?: Document): IColor | undefined; // @public (undocumented) export const getCommandBarStyles: (props: ICommandBarStyleProps) => ICommandBarStyles; @@ -1856,7 +1862,7 @@ export function getLayerHostSelector(): string | undefined; export const getLayerStyles: (props: ILayerStyleProps) => ILayerStyles; // @public -export function getMaxHeight(target: Element | MouseEvent | Point | Rectangle, targetEdge: DirectionalHint, gapSpace?: number, bounds?: IRectangle, coverTarget?: boolean): number; +export function getMaxHeight(target: Element | MouseEvent | Point | Rectangle, targetEdge: DirectionalHint, gapSpace?: number, bounds?: IRectangle, coverTarget?: boolean, win?: Window): number; // @public export const getMeasurementCache: () => { @@ -8124,6 +8130,8 @@ export interface IScrollablePaneContext { notifySubscribers: (sort?: boolean) => void; syncScrollSticky: (sticky: Sticky) => void; }; + // (undocumented) + window: Window | undefined; } // @public (undocumented) @@ -9769,6 +9777,8 @@ export class KeytipLayerBase extends React_2.Component; + // (undocumented) static defaultProps: IKeytipLayerProps; // (undocumented) getCurrentSequence(): string; @@ -9877,6 +9887,8 @@ export class List extends React_2.Component, IListState; + // (undocumented) static defaultProps: { startIndex: number; onRenderCell: (item: any, index: number, containsFocus: boolean) => JSX.Element; @@ -10040,6 +10052,8 @@ export const Nav: React_2.FunctionComponent; export class NavBase extends React_2.Component implements INav { constructor(props: INavProps); // (undocumented) + static contextType: React_2.Context; + // (undocumented) static defaultProps: INavProps; focus(forceIntoFirstElement?: boolean): boolean; // (undocumented) @@ -10132,6 +10146,8 @@ export class PanelBase extends React_2.Component imple // (undocumented) componentWillUnmount(): void; // (undocumented) + static contextType: React_2.Context; + // (undocumented) static defaultProps: IPanelProps; // (undocumented) dismiss: (ev?: React_2.SyntheticEvent | KeyboardEvent) => void; @@ -10395,13 +10411,13 @@ export enum Position { } // @public (undocumented) -export function positionCallout(props: IPositionProps, hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: ICalloutPositionedInfo): ICalloutPositionedInfo; +export function positionCallout(props: IPositionProps, hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: ICalloutPositionedInfo, win?: Window): ICalloutPositionedInfo; // @public (undocumented) -export function positionCard(props: IPositionProps, hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: ICalloutPositionedInfo): ICalloutPositionedInfo; +export function positionCard(props: IPositionProps, hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: ICalloutPositionedInfo, win?: Window): ICalloutPositionedInfo; // @public -export function positionElement(props: IPositionProps, hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: IPositionedData): IPositionedData; +export function positionElement(props: IPositionProps, hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: IPositionedData, win?: Window): IPositionedData; // @public (undocumented) export const PositioningContainer: React_2.FunctionComponent; @@ -10582,6 +10598,8 @@ export class ScrollablePaneBase extends React_2.Component; + // (undocumented) forceLayoutUpdate(): void; // (undocumented) getScrollPosition: () => number; @@ -11348,6 +11366,8 @@ export class TooltipHostBase extends React_2.Component; + // (undocumented) static defaultProps: { delay: TooltipDelay; }; From 8b7370cd5584fc9adafcd29a5793ac778617fb97 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 9 Oct 2023 19:55:47 +0000 Subject: [PATCH 06/44] fix lint errors --- packages/public-docsite-setup/src/index.ts | 4 ++-- packages/public-docsite-setup/src/loadSite.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/public-docsite-setup/src/index.ts b/packages/public-docsite-setup/src/index.ts index a898ed053c65f1..58f28f8b85016c 100644 --- a/packages/public-docsite-setup/src/index.ts +++ b/packages/public-docsite-setup/src/index.ts @@ -1,3 +1,3 @@ -export * from './constants'; -export * from './types'; +export { BUNDLE_NAME, MANIFEST_NAME_FORMAT, MANIFEST_VARIANTS } from './constants'; +export type { VersionMenuItem, VersionSwitcherDefinition, SiteConfig, SiteGlobals, ManifestVariant } from './types'; // loadSite is NOT exported (see comment in file) diff --git a/packages/public-docsite-setup/src/loadSite.ts b/packages/public-docsite-setup/src/loadSite.ts index 5e87676f24748e..b97e1d15a9981e 100644 --- a/packages/public-docsite-setup/src/loadSite.ts +++ b/packages/public-docsite-setup/src/loadSite.ts @@ -186,7 +186,7 @@ function loadSiteInternal(options: { // This is used by the example editor in 7+ (but having it defined in 5-6 is harmless) window.MonacoConfig = { - baseUrl: baseUrl, + baseUrl, useMinified, crossDomain: true, }; @@ -214,10 +214,12 @@ function getParameterByName(name: string) { } function loadScript(src: string, onLoad?: () => void, onError?: () => void) { + // eslint-disable-next-line no-restricted-globals const script = document.createElement('script'); script.src = src; script.onload = onLoad || null; script.onerror = onError || null; + // eslint-disable-next-line no-restricted-globals document.head.appendChild(script); } From 30f6bdc0e6733144c727c456dbdbea7fb5edd503 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 9 Oct 2023 19:57:21 +0000 Subject: [PATCH 07/44] change files --- ...docsite-setup-99131765-5261-4a19-9b79-7a22a24cdf75.json | 7 +++++++ ...luentui-react-118c9f1a-8f95-44bc-9e6c-f1e1258a8aaf.json | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 change/@fluentui-public-docsite-setup-99131765-5261-4a19-9b79-7a22a24cdf75.json create mode 100644 change/@fluentui-react-118c9f1a-8f95-44bc-9e6c-f1e1258a8aaf.json diff --git a/change/@fluentui-public-docsite-setup-99131765-5261-4a19-9b79-7a22a24cdf75.json b/change/@fluentui-public-docsite-setup-99131765-5261-4a19-9b79-7a22a24cdf75.json new file mode 100644 index 00000000000000..4e969bd98ac0f4 --- /dev/null +++ b/change/@fluentui-public-docsite-setup-99131765-5261-4a19-9b79-7a22a24cdf75.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: lint errors", + "packageName": "@fluentui/public-docsite-setup", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-118c9f1a-8f95-44bc-9e6c-f1e1258a8aaf.json b/change/@fluentui-react-118c9f1a-8f95-44bc-9e6c-f1e1258a8aaf.json new file mode 100644 index 00000000000000..d02e573529a193 --- /dev/null +++ b/change/@fluentui-react-118c9f1a-8f95-44bc-9e6c-f1e1258a8aaf.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: use `document` and `window` globals from context", + "packageName": "@fluentui/react", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} From bcfdb6a21f15114ef5eae58d874ede1ea4ef9e50 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 9 Oct 2023 20:16:59 +0000 Subject: [PATCH 08/44] add lint exception --- packages/set-version/src/setVersion.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/set-version/src/setVersion.ts b/packages/set-version/src/setVersion.ts index 5e0afcbcca17da..a0a25ad237f6eb 100644 --- a/packages/set-version/src/setVersion.ts +++ b/packages/set-version/src/setVersion.ts @@ -6,6 +6,7 @@ const packagesCache: { [name: string]: string } = {}; let _win: Window | undefined = undefined; try { + // eslint-disable-next-line no-restricted-globals _win = window; } catch (e) { /* no-op */ From 7481959bcbbec4c830a1e2c4ad94e7e220ba3fa7 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 9 Oct 2023 20:17:31 +0000 Subject: [PATCH 09/44] change files --- ...i-set-version-2eb367d7-e877-4209-9ae4-72a3c1096a55.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-set-version-2eb367d7-e877-4209-9ae4-72a3c1096a55.json diff --git a/change/@fluentui-set-version-2eb367d7-e877-4209-9ae4-72a3c1096a55.json b/change/@fluentui-set-version-2eb367d7-e877-4209-9ae4-72a3c1096a55.json new file mode 100644 index 00000000000000..a26ee2a09da746 --- /dev/null +++ b/change/@fluentui-set-version-2eb367d7-e877-4209-9ae4-72a3c1096a55.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: lint exception comment", + "packageName": "@fluentui/set-version", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "none" +} From 7db8456c0d177c35a5981acf52c087af8216e10d Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 9 Oct 2023 22:31:17 +0000 Subject: [PATCH 10/44] wire up globals where needed --- packages/dom-utilities/etc/dom-utilities.api.md | 7 +++---- .../dom-utilities/src/elementContainsAttribute.ts | 13 +++++++++++-- .../dom-utilities/src/findElementRecursive.ts | 4 +++- .../dom-utilities/src/portalContainsElement.ts | 8 +++++++- .../src/components/FocusZone/FocusZone.tsx | 7 +++++-- .../react/src/components/ComboBox/ComboBox.tsx | 5 +++-- .../components/KeytipLayer/KeytipLayer.base.tsx | 3 ++- .../src/components/KeytipLayer/KeytipTree.ts | 2 +- .../src/components/Tooltip/TooltipHost.base.tsx | 3 ++- packages/utilities/etc/utilities.api.md | 4 ++-- .../src/dom/getFirstVisibleElementFromSelector.ts | 7 +++++-- packages/utilities/src/focus.ts | 15 +++++++++------ 12 files changed, 53 insertions(+), 25 deletions(-) diff --git a/packages/dom-utilities/etc/dom-utilities.api.md b/packages/dom-utilities/etc/dom-utilities.api.md index 24373f59c4fcc0..2e27fa6ecb1977 100644 --- a/packages/dom-utilities/etc/dom-utilities.api.md +++ b/packages/dom-utilities/etc/dom-utilities.api.md @@ -11,10 +11,10 @@ export const DATA_PORTAL_ATTRIBUTE = "data-portal-element"; export function elementContains(parent: HTMLElement | null, child: HTMLElement | null, allowVirtualParents?: boolean): boolean; // @public -export function elementContainsAttribute(element: HTMLElement, attribute: string): string | null; +export function elementContainsAttribute(element: HTMLElement, attribute: string, doc?: Document): string | null; // @public -export function findElementRecursive(element: HTMLElement | null, matchFunction: (element: HTMLElement) => boolean): HTMLElement | null; +export function findElementRecursive(element: HTMLElement | null, matchFunction: (element: HTMLElement) => boolean, doc?: Document): HTMLElement | null; // @public export function getChildren(parent: HTMLElement, allowVirtualChildren?: boolean): HTMLElement[]; @@ -38,7 +38,7 @@ export interface IVirtualElement extends HTMLElement { } // @public -export function portalContainsElement(target: HTMLElement, parent?: HTMLElement): boolean; +export function portalContainsElement(target: HTMLElement, parent?: HTMLElement, doc?: Document): boolean; // @public export function setPortalAttribute(element: HTMLElement): void; @@ -46,7 +46,6 @@ export function setPortalAttribute(element: HTMLElement): void; // @public export function setVirtualParent(child: HTMLElement, parent: HTMLElement | null): void; - // (No @packageDocumentation comment for this package) ``` diff --git a/packages/dom-utilities/src/elementContainsAttribute.ts b/packages/dom-utilities/src/elementContainsAttribute.ts index 04a53bc19884ca..f6968ea3edcf65 100644 --- a/packages/dom-utilities/src/elementContainsAttribute.ts +++ b/packages/dom-utilities/src/elementContainsAttribute.ts @@ -6,8 +6,17 @@ import { findElementRecursive } from './findElementRecursive'; * @param attribute - the attribute to search for * @returns the value of the first instance found */ -export function elementContainsAttribute(element: HTMLElement, attribute: string): string | null { - const elementMatch = findElementRecursive(element, (testElement: HTMLElement) => testElement.hasAttribute(attribute)); +export function elementContainsAttribute( + element: HTMLElement, + attribute: string, + // eslint-disable-next-line no-restricted-globals + doc: Document = document, +): string | null { + const elementMatch = findElementRecursive( + element, + (testElement: HTMLElement) => testElement.hasAttribute(attribute), + doc, + ); return elementMatch && elementMatch.getAttribute(attribute); } diff --git a/packages/dom-utilities/src/findElementRecursive.ts b/packages/dom-utilities/src/findElementRecursive.ts index 9dc8118575595f..d94f064882f2fe 100644 --- a/packages/dom-utilities/src/findElementRecursive.ts +++ b/packages/dom-utilities/src/findElementRecursive.ts @@ -8,8 +8,10 @@ import { getParent } from './getParent'; export function findElementRecursive( element: HTMLElement | null, matchFunction: (element: HTMLElement) => boolean, + // eslint-disable-next-line no-restricted-globals + doc: Document = document, ): HTMLElement | null { - if (!element || element === document.body) { + if (!element || element === doc.body) { return null; } return matchFunction(element) ? element : findElementRecursive(getParent(element), matchFunction); diff --git a/packages/dom-utilities/src/portalContainsElement.ts b/packages/dom-utilities/src/portalContainsElement.ts index a5b9391aa576af..62fd2df8a70bb9 100644 --- a/packages/dom-utilities/src/portalContainsElement.ts +++ b/packages/dom-utilities/src/portalContainsElement.ts @@ -9,10 +9,16 @@ import { DATA_PORTAL_ATTRIBUTE } from './setPortalAttribute'; * @param parent - Optional parent perspective. Search for containing portal stops at parent * (or root if parent is undefined or invalid.) */ -export function portalContainsElement(target: HTMLElement, parent?: HTMLElement): boolean { +export function portalContainsElement( + target: HTMLElement, + parent?: HTMLElement, + // eslint-disable-next-line no-restricted-globals + doc: Document = document, +): boolean { const elementMatch = findElementRecursive( target, (testElement: HTMLElement) => parent === testElement || testElement.hasAttribute(DATA_PORTAL_ATTRIBUTE), + doc, ); return elementMatch !== null && elementMatch.hasAttribute(DATA_PORTAL_ATTRIBUTE); } diff --git a/packages/react-focus/src/components/FocusZone/FocusZone.tsx b/packages/react-focus/src/components/FocusZone/FocusZone.tsx index a8ae33009abcc4..f04745ebdc1831 100644 --- a/packages/react-focus/src/components/FocusZone/FocusZone.tsx +++ b/packages/react-focus/src/components/FocusZone/FocusZone.tsx @@ -59,12 +59,14 @@ function raiseClickFromKeyboardEvent(target: Element, ev?: React.KeyboardEvent implements IFocu this._activeElement && elementContains(this._root.current, this._activeElement) && isElementTabbable(this._activeElement) && - (!bypassHiddenElements || isElementVisibleAndNotHidden(this._activeElement)) + (!bypassHiddenElements || + isElementVisibleAndNotHidden(this._activeElement, this._getDocument().defaultView ?? undefined)) ) { this._activeElement.focus(); return true; @@ -1442,7 +1445,7 @@ export class FocusZone extends React.Component implements IFocu * Returns true if the element is a descendant of the FocusZone through a React portal. */ private _portalContainsElement(element: HTMLElement): boolean { - return element && !!this._root.current && portalContainsElement(element, this._root.current); + return element && !!this._root.current && portalContainsElement(element, this._root.current, this._getDocument()); } private _getDocument(): Document { diff --git a/packages/react/src/components/ComboBox/ComboBox.tsx b/packages/react/src/components/ComboBox/ComboBox.tsx index 80e4c8ccc4bf93..1e608df5144e9d 100644 --- a/packages/react/src/components/ComboBox/ComboBox.tsx +++ b/packages/react/src/components/ComboBox/ComboBox.tsx @@ -1220,6 +1220,7 @@ class ComboBoxInternal extends React.Component): void => { + const doc = getDocumentEx(this.context); // Do nothing if the blur is coming from something // inside the comboBox root or the comboBox menu since // it we are not really blurring from the whole comboBox @@ -1230,7 +1231,7 @@ class ComboBoxInternal extends React.Component element === relatedTarget); + findElementRecursive(this._comboBoxMenu.current, (element: HTMLElement) => element === relatedTarget, doc); if (isBlurFromComboBoxTitle || isBlurFromComboBoxMenu || isBlurFromComboBoxMenuAncestor) { if ( diff --git a/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx b/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx index 457a85d574deae..fc0e94b05aecbb 100644 --- a/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx +++ b/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx @@ -390,13 +390,14 @@ export class KeytipLayerBase extends React.Component { const doc = getDocumentEx(this.context); + const win = getWindowEx(this.context); const targetSelector = ktpTargetFromSequences(keySequences); const matchingElements = doc.querySelectorAll(targetSelector); // If there are multiple elements for the keytip sequence, return true if the element instance // that corresponds to the keytip instance is visible, otherwise return if there is only one instance return matchingElements.length > 1 && instanceCount <= matchingElements.length - ? isElementVisibleAndNotHidden(matchingElements[instanceCount - 1] as HTMLElement) + ? isElementVisibleAndNotHidden(matchingElements[instanceCount - 1] as HTMLElement, win ?? undefined) : instanceCount === 1; }; diff --git a/packages/react/src/components/KeytipLayer/KeytipTree.ts b/packages/react/src/components/KeytipLayer/KeytipTree.ts index 6313928d0f2f16..9dfc4c25e685b0 100644 --- a/packages/react/src/components/KeytipLayer/KeytipTree.ts +++ b/packages/react/src/components/KeytipLayer/KeytipTree.ts @@ -167,7 +167,7 @@ export class KeytipTree { // Attempt to find the node that corresponds to the first visible/non-hidden element const matchingIndex = Array.from(potentialTargetElements).findIndex((element: HTMLElement) => - isElementVisibleAndNotHidden(element), + isElementVisibleAndNotHidden(element, doc.defaultView ?? undefined), ); if (matchingIndex !== -1) { return matchingNodes[matchingIndex]; diff --git a/packages/react/src/components/Tooltip/TooltipHost.base.tsx b/packages/react/src/components/Tooltip/TooltipHost.base.tsx index eb779d88c9e0ce..6265255f4c76e3 100644 --- a/packages/react/src/components/Tooltip/TooltipHost.base.tsx +++ b/packages/react/src/components/Tooltip/TooltipHost.base.tsx @@ -206,6 +206,7 @@ export class TooltipHostBase extends React.Component { const { overflowMode, delay } = this.props; + const doc = getDocumentEx(this.context); if (TooltipHostBase._currentVisibleTooltip && TooltipHostBase._currentVisibleTooltip !== this) { TooltipHostBase._currentVisibleTooltip.dismiss(); @@ -219,7 +220,7 @@ export class TooltipHostBase extends React.Component void): export function shallowCompare(a: TA, b: TB): boolean; // @public -export function shouldWrapFocus(element: HTMLElement, noWrapDataAttribute: 'data-no-vertical-wrap' | 'data-no-horizontal-wrap'): boolean; +export function shouldWrapFocus(element: HTMLElement, noWrapDataAttribute: 'data-no-vertical-wrap' | 'data-no-horizontal-wrap', doc?: Document): boolean; // @public export function styled, TStyleProps, TStyleSet extends IStyleSet>(Component: React_2.ComponentClass | React_2.FunctionComponent, baseStyles: IStyleFunctionOrObject, getProps?: (props: TComponentProps) => Partial, customizable?: ICustomizableProps, pure?: boolean): React_2.FunctionComponent; diff --git a/packages/utilities/src/dom/getFirstVisibleElementFromSelector.ts b/packages/utilities/src/dom/getFirstVisibleElementFromSelector.ts index 253b0f16116db7..303f9a5abcf451 100644 --- a/packages/utilities/src/dom/getFirstVisibleElementFromSelector.ts +++ b/packages/utilities/src/dom/getFirstVisibleElementFromSelector.ts @@ -9,8 +9,11 @@ import { getDocument } from './getDocument'; * @public */ export function getFirstVisibleElementFromSelector(selector: string): Element | undefined { - const elements = getDocument()!.querySelectorAll(selector); + const doc = getDocument()!; + const elements = doc.querySelectorAll(selector); // Iterate across the elements that match the selector and return the first visible/non-hidden element - return Array.from(elements).find((element: HTMLElement) => isElementVisibleAndNotHidden(element)); + return Array.from(elements).find((element: HTMLElement) => + isElementVisibleAndNotHidden(element, doc.defaultView ?? undefined), + ); } diff --git a/packages/utilities/src/focus.ts b/packages/utilities/src/focus.ts index e3ab748badb23b..889e5771d7caa6 100644 --- a/packages/utilities/src/focus.ts +++ b/packages/utilities/src/focus.ts @@ -382,12 +382,13 @@ export function isElementVisible(element: HTMLElement | undefined | null): boole * * @public */ -export function isElementVisibleAndNotHidden(element: HTMLElement | undefined | null): boolean { +export function isElementVisibleAndNotHidden( + element: HTMLElement | undefined | null, + // eslint-disable-next-line no-restricted-globals + win: Window = window, +): boolean { return ( - !!element && - isElementVisible(element) && - !element.hidden && - window.getComputedStyle(element).visibility !== 'hidden' + !!element && isElementVisible(element) && !element.hidden && win.getComputedStyle(element).visibility !== 'hidden' ); } @@ -473,8 +474,10 @@ export function doesElementContainFocus(element: HTMLElement): boolean { export function shouldWrapFocus( element: HTMLElement, noWrapDataAttribute: 'data-no-vertical-wrap' | 'data-no-horizontal-wrap', + // eslint-disable-next-line no-restricted-globals + doc: Document = document, ): boolean { - return elementContainsAttribute(element, noWrapDataAttribute) === 'true' ? false : true; + return elementContainsAttribute(element, noWrapDataAttribute, doc) === 'true' ? false : true; } let animationId: number | undefined = undefined; From 09f233fda7b28f2e596110cff6a4cffced7dfe83 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 9 Oct 2023 22:33:04 +0000 Subject: [PATCH 11/44] change files --- ...dom-utilities-18f5f20c-92d5-4e9f-bd2d-494657065c63.json | 7 +++++++ ...i-react-focus-de2be8a6-f279-4e12-9677-371c811e5831.json | 7 +++++++ ...tui-utilities-530b2e82-88d8-48c2-aba1-1b791c2bed2c.json | 7 +++++++ 3 files changed, 21 insertions(+) create mode 100644 change/@fluentui-dom-utilities-18f5f20c-92d5-4e9f-bd2d-494657065c63.json create mode 100644 change/@fluentui-react-focus-de2be8a6-f279-4e12-9677-371c811e5831.json create mode 100644 change/@fluentui-utilities-530b2e82-88d8-48c2-aba1-1b791c2bed2c.json diff --git a/change/@fluentui-dom-utilities-18f5f20c-92d5-4e9f-bd2d-494657065c63.json b/change/@fluentui-dom-utilities-18f5f20c-92d5-4e9f-bd2d-494657065c63.json new file mode 100644 index 00000000000000..e992714bdc0968 --- /dev/null +++ b/change/@fluentui-dom-utilities-18f5f20c-92d5-4e9f-bd2d-494657065c63.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: allow `document` and `window` globals to be passed in where needed.", + "packageName": "@fluentui/dom-utilities", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-focus-de2be8a6-f279-4e12-9677-371c811e5831.json b/change/@fluentui-react-focus-de2be8a6-f279-4e12-9677-371c811e5831.json new file mode 100644 index 00000000000000..6e2a5dc114b256 --- /dev/null +++ b/change/@fluentui-react-focus-de2be8a6-f279-4e12-9677-371c811e5831.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: allow `document` and `window` globals to be passed in where needed.", + "packageName": "@fluentui/react-focus", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-utilities-530b2e82-88d8-48c2-aba1-1b791c2bed2c.json b/change/@fluentui-utilities-530b2e82-88d8-48c2-aba1-1b791c2bed2c.json new file mode 100644 index 00000000000000..2b78746fb4f44d --- /dev/null +++ b/change/@fluentui-utilities-530b2e82-88d8-48c2-aba1-1b791c2bed2c.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: allow `document` and `window` globals to be passed in where needed.", + "packageName": "@fluentui/utilities", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} From 9ab27f402418583fdf27da66c114589975e1cbeb Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 9 Oct 2023 23:14:48 +0000 Subject: [PATCH 12/44] fix: address lint for restricted globals --- packages/merge-styles/src/StyleOptionsState.ts | 7 +++---- packages/merge-styles/src/Stylesheet.ts | 1 + packages/merge-styles/src/getVendorSettings.ts | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/merge-styles/src/StyleOptionsState.ts b/packages/merge-styles/src/StyleOptionsState.ts index c59ad4bba7ca9c..93a6359275cccc 100644 --- a/packages/merge-styles/src/StyleOptionsState.ts +++ b/packages/merge-styles/src/StyleOptionsState.ts @@ -14,10 +14,9 @@ export function setRTL(isRTL: boolean): void { */ export function getRTL(): boolean { if (_rtl === undefined) { - _rtl = - typeof document !== 'undefined' && - !!document.documentElement && - document.documentElement.getAttribute('dir') === 'rtl'; + // eslint-disable-next-line no-restricted-globals + const doc = document; + _rtl = typeof doc !== 'undefined' && !!doc.documentElement && doc.documentElement.getAttribute('dir') === 'rtl'; } return _rtl; } diff --git a/packages/merge-styles/src/Stylesheet.ts b/packages/merge-styles/src/Stylesheet.ts index 3be6223b3d3119..e291a23cac9fdb 100644 --- a/packages/merge-styles/src/Stylesheet.ts +++ b/packages/merge-styles/src/Stylesheet.ts @@ -1,3 +1,4 @@ +/* eslint no-restricted-globals: 0 */ // globals in stylesheets will be addressed in a separate PR import { IStyle } from './IStyle'; export const InjectionMode = { diff --git a/packages/merge-styles/src/getVendorSettings.ts b/packages/merge-styles/src/getVendorSettings.ts index dfdcbdba55797b..41ba34307a3981 100644 --- a/packages/merge-styles/src/getVendorSettings.ts +++ b/packages/merge-styles/src/getVendorSettings.ts @@ -9,6 +9,7 @@ let _vendorSettings: IVendorSettings | undefined; export function getVendorSettings(): IVendorSettings { if (!_vendorSettings) { + // eslint-disable-next-line no-restricted-globals const doc = typeof document !== 'undefined' ? document : undefined; const nav = typeof navigator !== 'undefined' ? navigator : undefined; const userAgent = nav?.userAgent?.toLowerCase(); From b218faad3097c9c20f5de84887755b7ac4359134 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 9 Oct 2023 23:15:21 +0000 Subject: [PATCH 13/44] change files --- ...-merge-styles-41e63bcf-870e-4e2b-bef8-645de9934120.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-merge-styles-41e63bcf-870e-4e2b-bef8-645de9934120.json diff --git a/change/@fluentui-merge-styles-41e63bcf-870e-4e2b-bef8-645de9934120.json b/change/@fluentui-merge-styles-41e63bcf-870e-4e2b-bef8-645de9934120.json new file mode 100644 index 00000000000000..099de7d83f5bad --- /dev/null +++ b/change/@fluentui-merge-styles-41e63bcf-870e-4e2b-bef8-645de9934120.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: address lint for restricted globals", + "packageName": "@fluentui/merge-styles", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} From 4f270db37ff2eabd9a19bcafe32ff63334bf99a7 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 9 Oct 2023 23:48:28 +0000 Subject: [PATCH 14/44] fix: lint errors and explicit exports --- packages/react-window-provider/src/WindowProvider.tsx | 1 + packages/react-window-provider/src/index.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) 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/react-window-provider/src/index.ts b/packages/react-window-provider/src/index.ts index 0283d9270b3180..79c324bb43a308 100644 --- a/packages/react-window-provider/src/index.ts +++ b/packages/react-window-provider/src/index.ts @@ -1,3 +1,4 @@ -export * from './WindowProvider'; +export { WindowContext, useWindow, useDocument, WindowProvider } from './WindowProvider'; +export type { WindowProviderProps } from './WindowProvider'; import './version'; From 6fc827513328b9e4fa3add4e5f9fced1ff523fcf Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Mon, 9 Oct 2023 23:49:05 +0000 Subject: [PATCH 15/44] change files --- ...ndow-provider-6899238f-933b-4bb8-9c5e-27c84a62b74c.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-window-provider-6899238f-933b-4bb8-9c5e-27c84a62b74c.json diff --git a/change/@fluentui-react-window-provider-6899238f-933b-4bb8-9c5e-27c84a62b74c.json b/change/@fluentui-react-window-provider-6899238f-933b-4bb8-9c5e-27c84a62b74c.json new file mode 100644 index 00000000000000..81b046294617d9 --- /dev/null +++ b/change/@fluentui-react-window-provider-6899238f-933b-4bb8-9c5e-27c84a62b74c.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: lint errors and explicit exports", + "packageName": "@fluentui/react-window-provider", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} From 157cb0813a0be4fa860bea1a2d5fb8a2a0320208 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 02:17:18 +0000 Subject: [PATCH 16/44] add lint exception --- packages/react-window-provider/src/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/react-window-provider/src/index.ts b/packages/react-window-provider/src/index.ts index 79c324bb43a308..e132b1ab8d252d 100644 --- a/packages/react-window-provider/src/index.ts +++ b/packages/react-window-provider/src/index.ts @@ -1,4 +1,10 @@ -export { WindowContext, useWindow, useDocument, WindowProvider } from './WindowProvider'; +export { + // eslint-disable-next-line @fluentui/ban-context-export + WindowContext, + useWindow, + useDocument, + WindowProvider, +} from './WindowProvider'; export type { WindowProviderProps } from './WindowProvider'; import './version'; From 2a9a16f90ddf12ad00ab078e4b355ed2619cf94d Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 03:41:43 +0000 Subject: [PATCH 17/44] update eslint configs --- packages/eslint-plugin/src/configs/react-config.js | 1 + packages/eslint-plugin/src/utils/configHelpers.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/configs/react-config.js b/packages/eslint-plugin/src/configs/react-config.js index 606ad39ae7d33a..fed0b8662460f8 100644 --- a/packages/eslint-plugin/src/configs/react-config.js +++ b/packages/eslint-plugin/src/configs/react-config.js @@ -132,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/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}', From a32e1d4f50899a9bd108443e53e48d196be5ec80 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 03:42:07 +0000 Subject: [PATCH 18/44] lint for no-restricted-globals --- .../src/ProviderContext/ProviderContext.ts | 1 + .../react-utilities/etc/react-utilities.api.md | 2 +- .../src/hooks/useOnClickOutside.ts | 16 ++++++++++------ .../react-utilities/src/ssr/canUseDOM.ts | 8 +++++--- 4 files changed, 17 insertions(+), 10 deletions(-) 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/etc/react-utilities.api.md b/packages/react-components/react-utilities/etc/react-utilities.api.md index b18586a828add7..cc41ecbbac3966 100644 --- a/packages/react-components/react-utilities/etc/react-utilities.api.md +++ b/packages/react-components/react-utilities/etc/react-utilities.api.md @@ -354,7 +354,7 @@ export type UseOnClickOrScrollOutsideOptions = { }; // @internal -export const useOnClickOutside: (options: UseOnClickOrScrollOutsideOptions) => void; +export const useOnClickOutside: (options: UseOnClickOrScrollOutsideOptions, win?: Window) => void; // @internal export const useOnScrollOutside: (options: UseOnClickOrScrollOutsideOptions) => void; diff --git a/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts b/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts index 74b414a8b8ed58..edd7c365aeaf12 100644 --- a/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts +++ b/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts @@ -44,7 +44,11 @@ const DEFAULT_CONTAINS: UseOnClickOrScrollOutsideOptions['contains'] = (parent, * @internal * Utility to perform checks where a click/touch event was made outside a component */ -export const useOnClickOutside = (options: UseOnClickOrScrollOutsideOptions) => { +export const useOnClickOutside = ( + options: UseOnClickOrScrollOutsideOptions, + // eslint-disable-next-line no-restricted-globals + win: Window = window, +) => { const { refs, callback, element, disabled, disabledFocusOnIframe, contains = DEFAULT_CONTAINS } = options; const timeoutId = React.useRef(undefined); @@ -80,7 +84,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 +103,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 +113,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 | 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..529db3d9165032 100644 --- a/packages/react-components/react-utilities/src/ssr/canUseDOM.ts +++ b/packages/react-components/react-utilities/src/ssr/canUseDOM.ts @@ -2,12 +2,14 @@ * Verifies if an application can use DOM. */ export function canUseDOM(): boolean { + // eslint-disable-next-line no-restricted-globals + const win = window; return ( - typeof window !== 'undefined' && + typeof win !== 'undefined' && !!( - window.document && + win.document && // eslint-disable-next-line deprecation/deprecation - window.document.createElement + win.document.createElement ) ); } From 8919bb109e2076cdbaae15913e7bcee1e288102e Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 04:12:49 +0000 Subject: [PATCH 19/44] lint no-restricted-globals --- packages/utilities/etc/utilities.api.md | 10 +++++----- packages/utilities/src/AutoScroll.ts | 14 +++++++++----- packages/utilities/src/DelayedRender.tsx | 2 +- packages/utilities/src/EventGroup.ts | 17 ++++++++++++----- packages/utilities/src/FabricPerformance.ts | 2 +- packages/utilities/src/dom/canUseDOM.ts | 8 +++++--- packages/utilities/src/dom/getDocument.ts | 11 +++++++++-- packages/utilities/src/dom/getRect.ts | 16 ++++++++++------ packages/utilities/src/dom/getWindow.ts | 1 + packages/utilities/src/dom/raiseClick.ts | 12 ++++++++---- packages/utilities/src/mobileDetector.ts | 6 ++++-- packages/utilities/src/scroll.ts | 11 +++++++---- .../utilities/src/selection/Selection.test.ts | 2 +- 13 files changed, 73 insertions(+), 39 deletions(-) diff --git a/packages/utilities/etc/utilities.api.md b/packages/utilities/etc/utilities.api.md index 4224a2cf797f1b..02fa0e07e16e45 100644 --- a/packages/utilities/etc/utilities.api.md +++ b/packages/utilities/etc/utilities.api.md @@ -87,7 +87,7 @@ export const audioProperties: Record; // @public export class AutoScroll { - constructor(element: HTMLElement); + constructor(element: HTMLElement, win?: Window); // (undocumented) dispose(): void; } @@ -241,7 +241,7 @@ export class EventGroup { onAll(target: any, events: { [key: string]: (args?: any) => void; }, useCapture?: boolean): void; - static raise(target: any, eventName: string, eventArgs?: any, bubbleEvent?: boolean): boolean | undefined; + static raise(target: any, eventName: string, eventArgs?: any, bubbleEvent?: boolean, doc?: Document): boolean | undefined; raise(eventName: string, eventArgs?: any, bubbleEvent?: boolean): boolean | undefined; // (undocumented) static stopPropagation(event: any): void; @@ -375,7 +375,7 @@ export function getPreviousElement(rootElement: HTMLElement, currentElement: HTM export function getPropsWithDefaults(defaultProps: Partial, propsWithoutDefaults: TProps): TProps; // @public -export function getRect(element: HTMLElement | Window | null): IRectangle | undefined; +export function getRect(element: HTMLElement | Window | null, win?: Window): IRectangle | undefined; // @public @deprecated (undocumented) export function getResourceUrl(url: string): string; @@ -391,7 +391,7 @@ export function getRTLSafeKeyCode(key: number, theme?: { }): number; // @public -export function getScrollbarWidth(): number; +export function getScrollbarWidth(doc?: Document): number; export { getVirtualParent } @@ -1052,7 +1052,7 @@ export { portalContainsElement } export function precisionRound(value: number, precision: number, base?: number): number; // @public @deprecated -export function raiseClick(target: Element): void; +export function raiseClick(target: Element, doc?: Document): void; // @public export class Rectangle { diff --git a/packages/utilities/src/AutoScroll.ts b/packages/utilities/src/AutoScroll.ts index 478a892efa7a15..938a0dfe73b59b 100644 --- a/packages/utilities/src/AutoScroll.ts +++ b/packages/utilities/src/AutoScroll.ts @@ -26,7 +26,11 @@ export class AutoScroll { private _isVerticalScroll!: boolean; private _timeoutId?: number; - constructor(element: HTMLElement) { + constructor( + element: HTMLElement, + // eslint-disable-next-line no-restricted-globals + win: Window = window, + ) { this._events = new EventGroup(this); this._scrollableParent = findScrollableParent(element) as HTMLElement; @@ -34,13 +38,13 @@ export class AutoScroll { this._scrollRect = getRect(this._scrollableParent); // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (this._scrollableParent === (window as any)) { - this._scrollableParent = document.body; + if (this._scrollableParent === (win as any)) { + this._scrollableParent = win.document.body; } if (this._scrollableParent) { - this._events.on(window, 'mousemove', this._onMouseMove, true); - this._events.on(window, 'touchmove', this._onTouchMove, true); + this._events.on(win, 'mousemove', this._onMouseMove, true); + this._events.on(win, 'touchmove', this._onTouchMove, true); } } diff --git a/packages/utilities/src/DelayedRender.tsx b/packages/utilities/src/DelayedRender.tsx index 3787b6c8dbb590..29f1aa484f92e3 100644 --- a/packages/utilities/src/DelayedRender.tsx +++ b/packages/utilities/src/DelayedRender.tsx @@ -7,7 +7,6 @@ import { IReactProps } from './React.types'; * * @public */ -// eslint-disable-next-line deprecation/deprecation export interface IDelayedRenderProps extends IReactProps<{}> { /** * Number of milliseconds to delay rendering children. @@ -51,6 +50,7 @@ export class DelayedRender extends React.Component { this.setState({ isRendered: true, diff --git a/packages/utilities/src/EventGroup.ts b/packages/utilities/src/EventGroup.ts index 87927e95f6f11a..ebdbc84d8eedca 100644 --- a/packages/utilities/src/EventGroup.ts +++ b/packages/utilities/src/EventGroup.ts @@ -61,12 +61,19 @@ export class EventGroup { * which may lead to unexpected behavior if it differs from the defaults. * */ - public static raise(target: any, eventName: string, eventArgs?: any, bubbleEvent?: boolean): boolean | undefined { + public static raise( + target: any, + eventName: string, + eventArgs?: any, + bubbleEvent?: boolean, + // eslint-disable-next-line no-restricted-globals + doc: Document = document, + ): boolean | undefined { let retVal; if (EventGroup._isElement(target)) { - if (typeof document !== 'undefined' && document.createEvent) { - let ev = document.createEvent('HTMLEvents'); + if (typeof doc !== 'undefined' && doc.createEvent) { + let ev = doc.createEvent('HTMLEvents'); // eslint-disable-next-line deprecation/deprecation ev.initEvent(eventName, bubbleEvent || false, true); @@ -74,9 +81,9 @@ export class EventGroup { assign(ev, eventArgs); retVal = target.dispatchEvent(ev); - } else if (typeof document !== 'undefined' && (document as any).createEventObject) { + } else if (typeof doc !== 'undefined' && (doc as any).createEventObject) { // IE8 - let evObj = (document as any).createEventObject(eventArgs); + let evObj = (doc as any).createEventObject(eventArgs); // cannot set cancelBubble on evObj, fireEvent will overwrite it target.fireEvent('on' + eventName, evObj); } diff --git a/packages/utilities/src/FabricPerformance.ts b/packages/utilities/src/FabricPerformance.ts index d1c8179007dcdc..e131bc6074b399 100644 --- a/packages/utilities/src/FabricPerformance.ts +++ b/packages/utilities/src/FabricPerformance.ts @@ -67,7 +67,7 @@ export class FabricPerformance { measurement.totalDuration += duration; measurement.count++; measurement.all.push({ - duration: duration, + duration, timeStamp: end, }); FabricPerformance.summary[name] = measurement; diff --git a/packages/utilities/src/dom/canUseDOM.ts b/packages/utilities/src/dom/canUseDOM.ts index 01520b98ccc7a8..529db3d9165032 100644 --- a/packages/utilities/src/dom/canUseDOM.ts +++ b/packages/utilities/src/dom/canUseDOM.ts @@ -2,12 +2,14 @@ * Verifies if an application can use DOM. */ export function canUseDOM(): boolean { + // eslint-disable-next-line no-restricted-globals + const win = window; return ( - typeof window !== 'undefined' && + typeof win !== 'undefined' && !!( - window.document && + win.document && // eslint-disable-next-line deprecation/deprecation - window.document.createElement + win.document.createElement ) ); } diff --git a/packages/utilities/src/dom/getDocument.ts b/packages/utilities/src/dom/getDocument.ts index ae975f065e76fd..15300f89b052c7 100644 --- a/packages/utilities/src/dom/getDocument.ts +++ b/packages/utilities/src/dom/getDocument.ts @@ -8,11 +8,18 @@ import { canUseDOM } from './canUseDOM'; * @public */ export function getDocument(rootElement?: HTMLElement | null): Document | undefined { - if (!canUseDOM() || typeof document === 'undefined') { + if ( + !canUseDOM() || + // eslint-disable-next-line no-restricted-globals + typeof document === 'undefined' + ) { return undefined; } else { const el = rootElement as Element; - return el && el.ownerDocument ? el.ownerDocument : document; + return el && el.ownerDocument + ? el.ownerDocument + : // eslint-disable-next-line no-restricted-globals + document; } } diff --git a/packages/utilities/src/dom/getRect.ts b/packages/utilities/src/dom/getRect.ts index 63d24fd214859a..576046bdfc4ec2 100644 --- a/packages/utilities/src/dom/getRect.ts +++ b/packages/utilities/src/dom/getRect.ts @@ -5,17 +5,21 @@ import type { IRectangle } from '../IRectangle'; * * @public */ -export function getRect(element: HTMLElement | Window | null): IRectangle | undefined { +export function getRect( + element: HTMLElement | Window | null, + // eslint-disable-next-line no-restricted-globals + win: Window = window, +): IRectangle | undefined { let rect: IRectangle | undefined; if (element) { - if (element === window) { + if (element === win) { rect = { left: 0, top: 0, - width: window.innerWidth, - height: window.innerHeight, - right: window.innerWidth, - bottom: window.innerHeight, + width: win.innerWidth, + height: win.innerHeight, + right: win.innerWidth, + bottom: win.innerHeight, }; } else if ((element as { getBoundingClientRect?: unknown }).getBoundingClientRect) { rect = (element as HTMLElement).getBoundingClientRect(); diff --git a/packages/utilities/src/dom/getWindow.ts b/packages/utilities/src/dom/getWindow.ts index 8d37fb26009846..c4e41a9c393c5a 100644 --- a/packages/utilities/src/dom/getWindow.ts +++ b/packages/utilities/src/dom/getWindow.ts @@ -6,6 +6,7 @@ let _window: Window | undefined = undefined; // hits a memory leak, whereas aliasing it and calling "typeof _window" does not. // Caching the window value at the file scope lets us minimize the impact. try { + // eslint-disable-next-line no-restricted-globals _window = window; } catch (e) { /* no-op */ diff --git a/packages/utilities/src/dom/raiseClick.ts b/packages/utilities/src/dom/raiseClick.ts index d66c78a03e5157..b5323305a2b48c 100644 --- a/packages/utilities/src/dom/raiseClick.ts +++ b/packages/utilities/src/dom/raiseClick.ts @@ -1,21 +1,25 @@ /** Raises a click event. * @deprecated Moved to `FocusZone` component since it was the only place internally using this function. */ -export function raiseClick(target: Element): void { - const event = createNewEvent('MouseEvents'); +export function raiseClick( + target: Element, + // eslint-disable-next-line no-restricted-globals + doc: Document = document, +): void { + const event = createNewEvent('MouseEvents', doc); // eslint-disable-next-line deprecation/deprecation event.initEvent('click', true, true); target.dispatchEvent(event); } -function createNewEvent(eventName: string): Event { +function createNewEvent(eventName: string, doc: Document): Event { let event; if (typeof Event === 'function') { // Chrome, Opera, Firefox event = new Event(eventName); } else { // IE - event = document.createEvent('Event'); + event = doc.createEvent('Event'); // eslint-disable-next-line deprecation/deprecation event.initEvent(eventName, true, true); } diff --git a/packages/utilities/src/mobileDetector.ts b/packages/utilities/src/mobileDetector.ts index 4dfd584475e5ef..09efd1718d5bea 100644 --- a/packages/utilities/src/mobileDetector.ts +++ b/packages/utilities/src/mobileDetector.ts @@ -3,8 +3,10 @@ * Used to determine whether iOS-specific behavior should be applied. */ export const isIOS = (): boolean => { - if (!window || !window.navigator || !window.navigator.userAgent) { + // eslint-disable-next-line no-restricted-globals + const win = window; + if (!win || !win.navigator || !win.navigator.userAgent) { return false; } - return /iPad|iPhone|iPod/i.test(window.navigator.userAgent); + return /iPad|iPhone|iPod/i.test(win.navigator.userAgent); }; diff --git a/packages/utilities/src/scroll.ts b/packages/utilities/src/scroll.ts index 008b56bd76b2d9..51e729f6232be2 100644 --- a/packages/utilities/src/scroll.ts +++ b/packages/utilities/src/scroll.ts @@ -136,20 +136,23 @@ export function enableBodyScroll(): void { * * @public */ -export function getScrollbarWidth(): number { +export function getScrollbarWidth( + // eslint-disable-next-line no-restricted-globals + doc: Document = document, +): number { if (_scrollbarWidth === undefined) { - let scrollDiv: HTMLElement = document.createElement('div'); + let scrollDiv: HTMLElement = doc.createElement('div'); scrollDiv.style.setProperty('width', '100px'); scrollDiv.style.setProperty('height', '100px'); scrollDiv.style.setProperty('overflow', 'scroll'); scrollDiv.style.setProperty('position', 'absolute'); scrollDiv.style.setProperty('top', '-9999px'); - document.body.appendChild(scrollDiv); + doc.body.appendChild(scrollDiv); // Get the scrollbar width _scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; // Delete the DIV - document.body.removeChild(scrollDiv); + doc.body.removeChild(scrollDiv); } return _scrollbarWidth; diff --git a/packages/utilities/src/selection/Selection.test.ts b/packages/utilities/src/selection/Selection.test.ts index 3187fd7cc19f08..ac51e9796ea3ef 100644 --- a/packages/utilities/src/selection/Selection.test.ts +++ b/packages/utilities/src/selection/Selection.test.ts @@ -150,7 +150,7 @@ describe('Selection', () => { } const items: ICustomItem[] = [{ id: 'a' }, { id: 'b' }]; const selection = new Selection({ - onSelectionChanged: onSelectionChanged, + onSelectionChanged, getKey: (item: ICustomItem) => item.id, items, }); From 320e1fe08c068744936e87b095c523f0d0ad0348 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 04:15:20 +0000 Subject: [PATCH 20/44] lint no-restricted-globals --- .../global-context/src/global-context-selector.ts | 5 ++++- .../react-components/global-context/src/global-context.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) 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..1ad1982ebc2dc6 100644 --- a/packages/react-components/global-context/src/global-context-selector.ts +++ b/packages/react-components/global-context/src/global-context-selector.ts @@ -5,7 +5,10 @@ import { getMajorVersion } from './utils'; import { GlobalObject } from './types'; const isBrowser = canUseDOM(); -const globalObject: GlobalObject = isBrowser ? window : global; +const globalObject: GlobalObject = isBrowser + ? // eslint-disable-next-line no-restricted-globals + window + : global; // Identifier for the symbol, for easy idenfitifaction of symbols created by this util // Useful for clearning global object during SSR reloads diff --git a/packages/react-components/global-context/src/global-context.ts b/packages/react-components/global-context/src/global-context.ts index a994ddea8f0e49..ad117ce1b8f3ee 100644 --- a/packages/react-components/global-context/src/global-context.ts +++ b/packages/react-components/global-context/src/global-context.ts @@ -4,7 +4,10 @@ import { GlobalObject } from './types'; import { getMajorVersion } from './utils'; const isBrowser = canUseDOM(); -const globalObject: GlobalObject = isBrowser ? window : global; +const globalObject: GlobalObject = isBrowser + ? // eslint-disable-next-line no-restricted-globals + window + : global; // Identifier for the symbol, for easy idenfitifaction of symbols created by this util // Useful for clearning global object during SSR reloads From 8e48bf788033b208332a4ac297d3bac17eb5fe35 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 04:35:04 +0000 Subject: [PATCH 21/44] lint no-restricted-globals --- .../react-motion-preview/src/utils/dom-style.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 { From 679bbc9414106bdef62a3fd4669c8881c1c878aa Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 04:37:04 +0000 Subject: [PATCH 22/44] lint no-restricted-globals --- .../react-file-type-icons/src/getFileTypeIconProps.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/react-file-type-icons/src/getFileTypeIconProps.ts b/packages/react-file-type-icons/src/getFileTypeIconProps.ts index 48170857a98a36..001c1f3d97f34d 100644 --- a/packages/react-file-type-icons/src/getFileTypeIconProps.ts +++ b/packages/react-file-type-icons/src/getFileTypeIconProps.ts @@ -152,8 +152,13 @@ export function getFileTypeIconNameFromExtensionOrType( return iconBaseName || GENERIC_FILE; } -export function getFileTypeIconSuffix(size: FileTypeIconSize, imageFileType: ImageFileType = 'svg'): string { - let devicePixelRatio: number = window.devicePixelRatio; +export function getFileTypeIconSuffix( + size: FileTypeIconSize, + imageFileType: ImageFileType = 'svg', + // eslint-disable-next-line no-restricted-globals + win: Window = window, +): string { + let devicePixelRatio: number = win.devicePixelRatio; let devicePixelRatioSuffix = ''; // Default is 1x // SVGs scale well, so you can generally use the default image. From 36ddc9645d1e96ca7d2fe3fe80f476068cb303be Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 04:41:29 +0000 Subject: [PATCH 23/44] lint no-restricted-globals --- .../react-datepicker-compat/src/utils/dom.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 packages/react-components/react-datepicker-compat/src/utils/dom.ts diff --git a/packages/react-components/react-datepicker-compat/src/utils/dom.ts b/packages/react-components/react-datepicker-compat/src/utils/dom.ts new file mode 100644 index 00000000000000..7b49a9126296a9 --- /dev/null +++ b/packages/react-components/react-datepicker-compat/src/utils/dom.ts @@ -0,0 +1,18 @@ +import { canUseDOM } from '@fluentui/react-utilities'; + +export function getWindow(targetElement?: Element | null): Window | undefined { + if ( + !canUseDOM() || + // eslint-disable-next-line no-restricted-globals + typeof window === 'undefined' + ) { + return undefined; + } + + const el = targetElement as Element; + + return el && el.ownerDocument && el.ownerDocument.defaultView + ? el.ownerDocument.defaultView + : // eslint-disable-next-line no-restricted-globals + window; +} From ecdd3cd8b1ad1278d9330cf9438d3211689446a4 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 04:48:26 +0000 Subject: [PATCH 24/44] lint no-restricted-globals --- .../src/scenarios/FluentProviderWithTheme.tsx | 2 ++ apps/perf-test-react-components/src/scenarios/MakeStyles.tsx | 4 +++- apps/react-18-tests-v8/src/index.tsx | 5 ++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/perf-test-react-components/src/scenarios/FluentProviderWithTheme.tsx b/apps/perf-test-react-components/src/scenarios/FluentProviderWithTheme.tsx index 6c9a02c6a7112e..d371eff22d9a6c 100644 --- a/apps/perf-test-react-components/src/scenarios/FluentProviderWithTheme.tsx +++ b/apps/perf-test-react-components/src/scenarios/FluentProviderWithTheme.tsx @@ -5,9 +5,11 @@ import { teamsLightTheme } from '@fluentui/react-theme'; const LayoutShift: React.FunctionComponent = ({ children }) => { // eslint-disable-next-line no-restricted-properties React.useLayoutEffect(() => { + // eslint-disable-next-line no-restricted-globals if (document.body) { // Accessing the offsetWidth forces reflow (browser synchronously calculates style and layout. // This allows us to measure theme impact on the rendering performance. + // eslint-disable-next-line no-restricted-globals document.body.offsetWidth; } }); diff --git a/apps/perf-test-react-components/src/scenarios/MakeStyles.tsx b/apps/perf-test-react-components/src/scenarios/MakeStyles.tsx index 5375ef92490bf0..539dbafda703b3 100644 --- a/apps/perf-test-react-components/src/scenarios/MakeStyles.tsx +++ b/apps/perf-test-react-components/src/scenarios/MakeStyles.tsx @@ -1,7 +1,9 @@ import { mergeClasses, makeStyles, createDOMRenderer } from '@griffel/core'; import * as React from 'react'; -const renderer = createDOMRenderer(document); +const renderer = + // eslint-disable-next-line no-restricted-globals + createDOMRenderer(document); const useStyles = makeStyles({ view: { diff --git a/apps/react-18-tests-v8/src/index.tsx b/apps/react-18-tests-v8/src/index.tsx index 87a4f9be66eab4..1035695c1018bf 100644 --- a/apps/react-18-tests-v8/src/index.tsx +++ b/apps/react-18-tests-v8/src/index.tsx @@ -2,7 +2,10 @@ import * as React from 'react'; import { createRoot } from 'react-dom/client'; import { App } from './App'; -const root = createRoot(document.getElementById('root') as HTMLElement); +const root = createRoot( + // eslint-disable-next-line no-restricted-globals + document.getElementById('root') as HTMLElement, +); root.render( From c7dbb0d5eabf192b907afa8e6daa60a28e3dc1eb Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 04:58:41 +0000 Subject: [PATCH 25/44] lint no-restricted-globals --- packages/cra-template/template/src/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/cra-template/template/src/index.tsx b/packages/cra-template/template/src/index.tsx index 90113a51c98270..6d72dddd55759a 100644 --- a/packages/cra-template/template/src/index.tsx +++ b/packages/cra-template/template/src/index.tsx @@ -13,7 +13,11 @@ mergeStyles({ }, }); -ReactDOM.render(, document.getElementById('root')); +ReactDOM.render( + , + // eslint-disable-next-line no-restricted-globals + document.getElementById('root'), +); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) From fa8341f33f7c3cd488733682007752ffaca9bee4 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 05:38:05 +0000 Subject: [PATCH 26/44] lint no-restricted-globals --- packages/eslint-plugin/src/configs/react-legacy.js | 6 +++++- .../stories/AccessibilityScenarios/utils.tsx | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/configs/react-legacy.js b/packages/eslint-plugin/src/configs/react-legacy.js index c355c69c8836d1..5e51a3052faef4 100644 --- a/packages/eslint-plugin/src/configs/react-legacy.js +++ b/packages/eslint-plugin/src/configs/react-legacy.js @@ -15,7 +15,11 @@ module.exports = { overrides: [ { // Test overrides - files: [...configHelpers.testFiles], + files: [ + ...configHelpers.testFiles, + '**/packages/react-charting/**/*.{ts,tsx}', + '**/packages/react-experiments/**/*.{ts,tsx}', + ], rules: { 'no-restricted-globals': 'off', 'react/jsx-no-bind': 'off', 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]); From 922bcc41be5019844bec6ec18515800990297703 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 10 Oct 2023 07:02:05 +0000 Subject: [PATCH 27/44] wip --- .../Migration/FromV0/Components/IconCatalog/useDebounce.ts | 1 + apps/public-docsite-v9/src/DocsComponents/Toc.stories.tsx | 1 + apps/react-18-tests-v9/src/index.tsx | 5 ++++- packages/eslint-plugin/src/configs/react-legacy.js | 6 +----- packages/react-charting/.eslintrc.json | 5 ++++- packages/react-components/theme-designer/.eslintrc.json | 3 ++- packages/react-experiments/.eslintrc.json | 3 ++- packages/react-monaco-editor/.eslintrc.json | 3 ++- 8 files changed, 17 insertions(+), 10 deletions(-) diff --git a/apps/public-docsite-v9/src/Concepts/Migration/FromV0/Components/IconCatalog/useDebounce.ts b/apps/public-docsite-v9/src/Concepts/Migration/FromV0/Components/IconCatalog/useDebounce.ts index c00486012917cf..21854c7aade79e 100644 --- a/apps/public-docsite-v9/src/Concepts/Migration/FromV0/Components/IconCatalog/useDebounce.ts +++ b/apps/public-docsite-v9/src/Concepts/Migration/FromV0/Components/IconCatalog/useDebounce.ts @@ -6,6 +6,7 @@ export const useDebounce = (fn: (...args: unknown[]) => void, duration: number) return React.useCallback( (...args: unknown[]) => { clearTimeout(timeoutRef.current); + // eslint-disable-next-line no-restricted-globals timeoutRef.current = window.setTimeout(() => { fn(...args); }, duration); diff --git a/apps/public-docsite-v9/src/DocsComponents/Toc.stories.tsx b/apps/public-docsite-v9/src/DocsComponents/Toc.stories.tsx index 91c12bd7cd2cb2..5356d38fbf1f14 100644 --- a/apps/public-docsite-v9/src/DocsComponents/Toc.stories.tsx +++ b/apps/public-docsite-v9/src/DocsComponents/Toc.stories.tsx @@ -99,6 +99,7 @@ export const Toc = ({ stories }: { stories: TocItem[] }) => { ); stories.forEach(link => { + // eslint-disable-next-line no-restricted-globals const element = document.getElementById(nameToHash(link.name)); if (element) { observer.observe(element); diff --git a/apps/react-18-tests-v9/src/index.tsx b/apps/react-18-tests-v9/src/index.tsx index 87a4f9be66eab4..1035695c1018bf 100644 --- a/apps/react-18-tests-v9/src/index.tsx +++ b/apps/react-18-tests-v9/src/index.tsx @@ -2,7 +2,10 @@ import * as React from 'react'; import { createRoot } from 'react-dom/client'; import { App } from './App'; -const root = createRoot(document.getElementById('root') as HTMLElement); +const root = createRoot( + // eslint-disable-next-line no-restricted-globals + document.getElementById('root') as HTMLElement, +); root.render( diff --git a/packages/eslint-plugin/src/configs/react-legacy.js b/packages/eslint-plugin/src/configs/react-legacy.js index 5e51a3052faef4..b267a6610b4083 100644 --- a/packages/eslint-plugin/src/configs/react-legacy.js +++ b/packages/eslint-plugin/src/configs/react-legacy.js @@ -15,11 +15,7 @@ module.exports = { overrides: [ { // Test overrides - files: [ - ...configHelpers.testFiles, - '**/packages/react-charting/**/*.{ts,tsx}', - '**/packages/react-experiments/**/*.{ts,tsx}', - ], + files: [...configHelpers.testFiles, '**/*.stories.tsx'], rules: { 'no-restricted-globals': 'off', 'react/jsx-no-bind': 'off', diff --git a/packages/react-charting/.eslintrc.json b/packages/react-charting/.eslintrc.json index 20576f82da4223..478004905dea85 100644 --- a/packages/react-charting/.eslintrc.json +++ b/packages/react-charting/.eslintrc.json @@ -1,4 +1,7 @@ { "extends": ["plugin:@fluentui/eslint-plugin/react--legacy"], - "root": true + "root": true, + "rules": { + "no-restricted-globals": "off" + } } 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-experiments/.eslintrc.json b/packages/react-experiments/.eslintrc.json index 10a55ade409374..94cce37333d4c9 100644 --- a/packages/react-experiments/.eslintrc.json +++ b/packages/react-experiments/.eslintrc.json @@ -2,6 +2,7 @@ "extends": ["plugin:@fluentui/eslint-plugin/react--legacy"], "root": true, "rules": { - "@typescript-eslint/no-explicit-any": "off" + "@typescript-eslint/no-explicit-any": "off", + "no-restricted-globals": "off" } } diff --git a/packages/react-monaco-editor/.eslintrc.json b/packages/react-monaco-editor/.eslintrc.json index 58655c94a33bcd..2743cb1a1673d4 100644 --- a/packages/react-monaco-editor/.eslintrc.json +++ b/packages/react-monaco-editor/.eslintrc.json @@ -2,6 +2,7 @@ "extends": ["plugin:@fluentui/eslint-plugin/react--legacy"], "root": true, "rules": { - "import/no-webpack-loader-syntax": "off" // ok in this project + "import/no-webpack-loader-syntax": "off", // ok in this project + "no-restricted-globals": "off" } } From adc618a426275853c412a6ff3033895c46726141 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 11 Oct 2023 19:34:39 +0000 Subject: [PATCH 28/44] re-work based on tests --- .../src/elementContainsAttribute.ts | 7 +--- .../dom-utilities/src/findElementRecursive.ts | 5 ++- .../src/portalContainsElement.ts | 7 +--- .../etc/react-utilities.api.md | 2 +- .../react-utilities/package.json | 1 + .../src/hooks/useOnClickOutside.ts | 8 ++-- .../react-utilities/src/ssr/canUseDOM.ts | 11 +++--- .../src/getFileTypeIconProps.ts | 5 ++- .../src/components/KeytipLayer/KeytipTree.ts | 10 ++--- .../react/src/utilities/color/cssColor.ts | 11 +++--- .../src/utilities/color/getColorFromString.ts | 11 +++--- .../src/utilities/positioning/positioning.ts | 38 +++++++++---------- packages/utilities/src/AutoScroll.ts | 10 ++--- packages/utilities/src/EventGroup.ts | 13 ++++--- packages/utilities/src/dom/getRect.ts | 16 ++++---- packages/utilities/src/dom/raiseClick.ts | 11 +++--- packages/utilities/src/focus.ts | 17 +++++---- packages/utilities/src/scroll.ts | 12 +++--- 18 files changed, 91 insertions(+), 104 deletions(-) diff --git a/packages/dom-utilities/src/elementContainsAttribute.ts b/packages/dom-utilities/src/elementContainsAttribute.ts index f6968ea3edcf65..c89868a29e2734 100644 --- a/packages/dom-utilities/src/elementContainsAttribute.ts +++ b/packages/dom-utilities/src/elementContainsAttribute.ts @@ -6,12 +6,9 @@ import { findElementRecursive } from './findElementRecursive'; * @param attribute - the attribute to search for * @returns the value of the first instance found */ -export function elementContainsAttribute( - element: HTMLElement, - attribute: string, +export function elementContainsAttribute(element: HTMLElement, attribute: string, doc?: Document): string | null { // eslint-disable-next-line no-restricted-globals - doc: Document = document, -): string | null { + doc ??= document; const elementMatch = findElementRecursive( element, (testElement: HTMLElement) => testElement.hasAttribute(attribute), diff --git a/packages/dom-utilities/src/findElementRecursive.ts b/packages/dom-utilities/src/findElementRecursive.ts index d94f064882f2fe..afac46bc46f256 100644 --- a/packages/dom-utilities/src/findElementRecursive.ts +++ b/packages/dom-utilities/src/findElementRecursive.ts @@ -8,9 +8,10 @@ import { getParent } from './getParent'; export function findElementRecursive( element: HTMLElement | null, matchFunction: (element: HTMLElement) => boolean, - // eslint-disable-next-line no-restricted-globals - doc: Document = document, + doc?: Document, ): HTMLElement | null { + // eslint-disable-next-line no-restricted-globals + doc ??= document; if (!element || element === doc.body) { return null; } diff --git a/packages/dom-utilities/src/portalContainsElement.ts b/packages/dom-utilities/src/portalContainsElement.ts index 62fd2df8a70bb9..f43a0a2e5c5739 100644 --- a/packages/dom-utilities/src/portalContainsElement.ts +++ b/packages/dom-utilities/src/portalContainsElement.ts @@ -9,12 +9,9 @@ import { DATA_PORTAL_ATTRIBUTE } from './setPortalAttribute'; * @param parent - Optional parent perspective. Search for containing portal stops at parent * (or root if parent is undefined or invalid.) */ -export function portalContainsElement( - target: HTMLElement, - parent?: HTMLElement, +export function portalContainsElement(target: HTMLElement, parent?: HTMLElement, doc?: Document): boolean { // eslint-disable-next-line no-restricted-globals - doc: Document = document, -): boolean { + doc ??= document; const elementMatch = findElementRecursive( target, (testElement: HTMLElement) => parent === testElement || testElement.hasAttribute(DATA_PORTAL_ATTRIBUTE), diff --git a/packages/react-components/react-utilities/etc/react-utilities.api.md b/packages/react-components/react-utilities/etc/react-utilities.api.md index cc41ecbbac3966..b18586a828add7 100644 --- a/packages/react-components/react-utilities/etc/react-utilities.api.md +++ b/packages/react-components/react-utilities/etc/react-utilities.api.md @@ -354,7 +354,7 @@ export type UseOnClickOrScrollOutsideOptions = { }; // @internal -export const useOnClickOutside: (options: UseOnClickOrScrollOutsideOptions, win?: Window) => void; +export const useOnClickOutside: (options: UseOnClickOrScrollOutsideOptions) => void; // @internal export const useOnScrollOutside: (options: UseOnClickOrScrollOutsideOptions) => void; diff --git a/packages/react-components/react-utilities/package.json b/packages/react-components/react-utilities/package.json index 013aa0efb5c045..1002daf0b396a9 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.6", + "@fluentui/react-window-provider": "^2.2.15", "@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 edd7c365aeaf12..a539de58c38361 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 { useWindow } from '@fluentui/react-window-provider'; /** * @internal @@ -44,11 +45,8 @@ const DEFAULT_CONTAINS: UseOnClickOrScrollOutsideOptions['contains'] = (parent, * @internal * Utility to perform checks where a click/touch event was made outside a component */ -export const useOnClickOutside = ( - options: UseOnClickOrScrollOutsideOptions, - // eslint-disable-next-line no-restricted-globals - win: Window = window, -) => { +export const useOnClickOutside = (options: UseOnClickOrScrollOutsideOptions) => { + const win = useWindow(); const { refs, callback, element, disabled, disabledFocusOnIframe, contains = DEFAULT_CONTAINS } = options; const timeoutId = React.useRef(undefined); diff --git a/packages/react-components/react-utilities/src/ssr/canUseDOM.ts b/packages/react-components/react-utilities/src/ssr/canUseDOM.ts index 529db3d9165032..ee7d26eab76e3e 100644 --- a/packages/react-components/react-utilities/src/ssr/canUseDOM.ts +++ b/packages/react-components/react-utilities/src/ssr/canUseDOM.ts @@ -2,14 +2,13 @@ * Verifies if an application can use DOM. */ export function canUseDOM(): boolean { - // eslint-disable-next-line no-restricted-globals - const win = window; return ( - typeof win !== 'undefined' && + // eslint-disable-next-line no-restricted-globals + typeof window !== 'undefined' && !!( - win.document && - // eslint-disable-next-line deprecation/deprecation - win.document.createElement + window.document && // eslint-disable-line no-restricted-globals + // eslint-disable-next-line deprecation/deprecation, no-restricted-globals + window.document.createElement ) ); } diff --git a/packages/react-file-type-icons/src/getFileTypeIconProps.ts b/packages/react-file-type-icons/src/getFileTypeIconProps.ts index 001c1f3d97f34d..6b67935fce28c9 100644 --- a/packages/react-file-type-icons/src/getFileTypeIconProps.ts +++ b/packages/react-file-type-icons/src/getFileTypeIconProps.ts @@ -155,9 +155,10 @@ export function getFileTypeIconNameFromExtensionOrType( export function getFileTypeIconSuffix( size: FileTypeIconSize, imageFileType: ImageFileType = 'svg', - // eslint-disable-next-line no-restricted-globals - win: Window = window, + win?: Window, ): string { + // eslint-disable-next-line no-restricted-globals + win ??= window; let devicePixelRatio: number = win.devicePixelRatio; let devicePixelRatioSuffix = ''; // Default is 1x diff --git a/packages/react/src/components/KeytipLayer/KeytipTree.ts b/packages/react/src/components/KeytipLayer/KeytipTree.ts index 9dfc4c25e685b0..e1268041db4466 100644 --- a/packages/react/src/components/KeytipLayer/KeytipTree.ts +++ b/packages/react/src/components/KeytipLayer/KeytipTree.ts @@ -1,4 +1,4 @@ -import { find, isElementVisibleAndNotHidden, values } from '../../Utilities'; +import { find, getDocument, isElementVisibleAndNotHidden, values } from '../../Utilities'; import { ktpTargetFromSequences, mergeOverflows, sequencesToID } from '../../utilities/keytips/KeytipUtils'; import { KTP_LAYER_ID } from '../../utilities/keytips/KeytipConstants'; import type { IKeytipProps } from '../../Keytip'; @@ -128,9 +128,9 @@ export class KeytipTree { public getExactMatchedNode( keySequence: string, currentKeytip: IKeytipTreeNode, - // eslint-disable-next-line no-restricted-globals - doc: Document = document, + doc?: Document, ): IKeytipTreeNode | undefined { + const theDoc = doc ?? getDocument()!; const possibleNodes = this.getNodes(currentKeytip.children); const matchingNodes = possibleNodes.filter((node: IKeytipTreeNode) => { return this._getNodeSequence(node) === keySequence && !node.disabled; @@ -155,7 +155,7 @@ export class KeytipTree { const overflowSetSequence = node.overflowSetSequence; const fullKeySequences = overflowSetSequence ? mergeOverflows(keySequences, overflowSetSequence) : keySequences; const keytipTargetSelector = ktpTargetFromSequences(fullKeySequences); - const potentialTargetElements = doc.querySelectorAll(keytipTargetSelector); + const potentialTargetElements = theDoc.querySelectorAll(keytipTargetSelector); // If we have less nodes than the potential target elements, // we won't be able to map element to node, return the first node. @@ -167,7 +167,7 @@ export class KeytipTree { // Attempt to find the node that corresponds to the first visible/non-hidden element const matchingIndex = Array.from(potentialTargetElements).findIndex((element: HTMLElement) => - isElementVisibleAndNotHidden(element, doc.defaultView ?? undefined), + isElementVisibleAndNotHidden(element, theDoc.defaultView ?? undefined), ); if (matchingIndex !== -1) { return matchingNodes[matchingIndex]; diff --git a/packages/react/src/utilities/color/cssColor.ts b/packages/react/src/utilities/color/cssColor.ts index d47e278a8c5604..d45b3ccfd8a46d 100644 --- a/packages/react/src/utilities/color/cssColor.ts +++ b/packages/react/src/utilities/color/cssColor.ts @@ -1,3 +1,4 @@ +import { getDocument } from '@fluentui/utilities'; import { MAX_COLOR_ALPHA } from './consts'; import { hsl2rgb } from './hsl2rgb'; import type { IRGB } from './interfaces'; @@ -8,15 +9,13 @@ import type { IRGB } from './interfaces'; * Alpha in returned color defaults to 100. * Four and eight digit hex values (with alpha) are supported if the current browser supports them. */ -export function cssColor( - color?: string, - // eslint-disable-next-line no-restricted-globals - doc: Document = document, -): IRGB | undefined { +export function cssColor(color?: string, doc?: Document): IRGB | undefined { if (!color) { return undefined; } + const theDoc = doc ?? getDocument()!; + // Need to check the following valid color formats: RGB(A), HSL(A), hex, named color // First check for well formatted RGB(A), HSL(A), and hex formats at the start. @@ -28,7 +27,7 @@ export function cssColor( } // if the above fails, do the more expensive catch-all - return _browserCompute(color, doc); + return _browserCompute(color, theDoc); } /** diff --git a/packages/react/src/utilities/color/getColorFromString.ts b/packages/react/src/utilities/color/getColorFromString.ts index 47a90cf782d0c5..4167e274cecae6 100644 --- a/packages/react/src/utilities/color/getColorFromString.ts +++ b/packages/react/src/utilities/color/getColorFromString.ts @@ -1,3 +1,4 @@ +import { getDocument } from '@fluentui/utilities'; import { cssColor } from './cssColor'; import { getColorFromRGBA } from './getColorFromRGBA'; import type { IColor } from './interfaces'; @@ -10,12 +11,10 @@ import type { IColor } from './interfaces'; * Alpha defaults to 100 if not specified in `inputColor`. * Returns undefined if the color string is invalid/not recognized. */ -export function getColorFromString( - inputColor: string, - // eslint-disable-next-line no-restricted-globals - doc: Document = document, -): IColor | undefined { - const color = cssColor(inputColor, doc); +export function getColorFromString(inputColor: string, doc?: Document): IColor | undefined { + const theDoc = doc ?? getDocument()!; + + const color = cssColor(inputColor, theDoc); if (!color) { return; diff --git a/packages/react/src/utilities/positioning/positioning.ts b/packages/react/src/utilities/positioning/positioning.ts index 657ec0f149af4c..32e8020b1beb31 100644 --- a/packages/react/src/utilities/positioning/positioning.ts +++ b/packages/react/src/utilities/positioning/positioning.ts @@ -1,5 +1,5 @@ import { DirectionalHint } from '../../common/DirectionalHint'; -import { getScrollbarWidth, getRTL } from '../../Utilities'; +import { getScrollbarWidth, getRTL, getWindow } from '../../Utilities'; import { RectangleEdge } from './positioning.types'; import { Rectangle } from '../../Utilities'; import type { IRectangle, Point } from '../../Utilities'; @@ -864,15 +864,16 @@ function _finalizePositionData( } function _positionElement( - win: Window, props: IPositionProps, hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: IPositionedData, + win?: Window, ): IPositionedData { + const theWin = win ?? getWindow()!; const boundingRect: Rectangle = props.bounds ? _getRectangleFromIRect(props.bounds) - : new Rectangle(0, win.innerWidth - getScrollbarWidth(), 0, win.innerHeight); + : new Rectangle(0, theWin.innerWidth - getScrollbarWidth(), 0, theWin.innerHeight); const positionedElement: IElementPosition = _positionElementRelative( props, elementToPosition, @@ -883,20 +884,21 @@ function _positionElement( } function _positionCallout( - win: Window, props: ICalloutPositionProps, hostElement: HTMLElement, callout: HTMLElement, previousPositions?: ICalloutPositionedInfo, doNotFinalizeReturnEdge?: boolean, + win?: Window, ): ICalloutPositionedInfo { + const theWin = win ?? getWindow()!; const beakWidth: number = props.isBeakVisible ? props.beakWidth || 0 : 0; const gap: number = _calculateActualBeakWidthInPixels(beakWidth) / 2 + (props.gapSpace ? props.gapSpace : 0); const positionProps: IPositionProps = props; positionProps.gapSpace = gap; const boundingRect: Rectangle = props.bounds ? _getRectangleFromIRect(props.bounds) - : new Rectangle(0, win.innerWidth - getScrollbarWidth(), 0, win.innerHeight); + : new Rectangle(0, theWin.innerWidth - getScrollbarWidth(), 0, theWin.innerHeight); const positionedElement: IElementPositionInfo = _positionElementRelative( positionProps, callout, @@ -918,13 +920,14 @@ function _positionCallout( } function _positionCard( - win: Window, props: ICalloutPositionProps, hostElement: HTMLElement, callout: HTMLElement, previousPositions?: ICalloutPositionedInfo, + win?: Window, ): ICalloutPositionedInfo { - return _positionCallout(win, props, hostElement, callout, previousPositions, true); + const theWin = win ?? getWindow()!; + return _positionCallout(props, hostElement, callout, previousPositions, true, theWin); } // END PRIVATE FUNCTIONS @@ -948,10 +951,9 @@ export function positionElement( hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: IPositionedData, - // eslint-disable-next-line no-restricted-globals - win: Window = window, + win?: Window, ): IPositionedData { - return _positionElement(win, props, hostElement, elementToPosition, previousPositions); + return _positionElement(props, hostElement, elementToPosition, previousPositions, win); } export function positionCallout( @@ -959,10 +961,9 @@ export function positionCallout( hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: ICalloutPositionedInfo, - // eslint-disable-next-line no-restricted-globals - win: Window = window, + win?: Window, ): ICalloutPositionedInfo { - return _positionCallout(win, props, hostElement, elementToPosition, previousPositions); + return _positionCallout(props, hostElement, elementToPosition, previousPositions, undefined, win); } export function positionCard( @@ -970,10 +971,9 @@ export function positionCard( hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: ICalloutPositionedInfo, - // eslint-disable-next-line no-restricted-globals - win: Window = window, + win?: Window, ): ICalloutPositionedInfo { - return _positionCard(win, props, hostElement, elementToPosition, previousPositions); + return _positionCard(props, hostElement, elementToPosition, previousPositions, win); } /** @@ -988,16 +988,16 @@ export function getMaxHeight( gapSpace: number = 0, bounds?: IRectangle, coverTarget?: boolean, - // eslint-disable-next-line no-restricted-globals - win: Window = window, + win?: Window, ): number { + const theWin = win ?? getWindow()!; const mouseTarget: MouseEvent = target as MouseEvent; const elementTarget: Element = target as Element; const rectOrPointTarget: Point & Rectangle = target as Point & Rectangle; let targetRect: Rectangle; const boundingRectangle = bounds ? _getRectangleFromIRect(bounds) - : new Rectangle(0, win.innerWidth - getScrollbarWidth(), 0, win.innerHeight); + : new Rectangle(0, theWin.innerWidth - getScrollbarWidth(), 0, theWin.innerHeight); // eslint-disable-next-line deprecation/deprecation const left = rectOrPointTarget.left || rectOrPointTarget.x; diff --git a/packages/utilities/src/AutoScroll.ts b/packages/utilities/src/AutoScroll.ts index 938a0dfe73b59b..6104a4b7c8bfd6 100644 --- a/packages/utilities/src/AutoScroll.ts +++ b/packages/utilities/src/AutoScroll.ts @@ -2,6 +2,7 @@ import { EventGroup } from './EventGroup'; import { findScrollableParent } from './scroll'; import { getRect } from './dom/getRect'; import type { IRectangle } from './IRectangle'; +import { getWindow } from './dom'; declare function setTimeout(cb: Function, delay: number): number; @@ -26,11 +27,8 @@ export class AutoScroll { private _isVerticalScroll!: boolean; private _timeoutId?: number; - constructor( - element: HTMLElement, - // eslint-disable-next-line no-restricted-globals - win: Window = window, - ) { + constructor(element: HTMLElement, win?: Window) { + const theWin = win ?? getWindow()!; this._events = new EventGroup(this); this._scrollableParent = findScrollableParent(element) as HTMLElement; @@ -39,7 +37,7 @@ export class AutoScroll { // eslint-disable-next-line @typescript-eslint/no-explicit-any if (this._scrollableParent === (win as any)) { - this._scrollableParent = win.document.body; + this._scrollableParent = theWin.document.body; } if (this._scrollableParent) { diff --git a/packages/utilities/src/EventGroup.ts b/packages/utilities/src/EventGroup.ts index ebdbc84d8eedca..e7d9fbc52500e2 100644 --- a/packages/utilities/src/EventGroup.ts +++ b/packages/utilities/src/EventGroup.ts @@ -1,3 +1,4 @@ +import { getDocument } from './dom'; import { assign } from './object'; /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -66,14 +67,14 @@ export class EventGroup { eventName: string, eventArgs?: any, bubbleEvent?: boolean, - // eslint-disable-next-line no-restricted-globals - doc: Document = document, + doc?: Document, ): boolean | undefined { let retVal; + const theDoc = doc ?? getDocument()!; if (EventGroup._isElement(target)) { - if (typeof doc !== 'undefined' && doc.createEvent) { - let ev = doc.createEvent('HTMLEvents'); + if (typeof doc !== 'undefined' && theDoc.createEvent) { + let ev = theDoc.createEvent('HTMLEvents'); // eslint-disable-next-line deprecation/deprecation ev.initEvent(eventName, bubbleEvent || false, true); @@ -81,9 +82,9 @@ export class EventGroup { assign(ev, eventArgs); retVal = target.dispatchEvent(ev); - } else if (typeof doc !== 'undefined' && (doc as any).createEventObject) { + } else if (typeof theDoc !== 'undefined' && (theDoc as any).createEventObject) { // IE8 - let evObj = (doc as any).createEventObject(eventArgs); + let evObj = (theDoc as any).createEventObject(eventArgs); // cannot set cancelBubble on evObj, fireEvent will overwrite it target.fireEvent('on' + eventName, evObj); } diff --git a/packages/utilities/src/dom/getRect.ts b/packages/utilities/src/dom/getRect.ts index 576046bdfc4ec2..57ae8a85809f72 100644 --- a/packages/utilities/src/dom/getRect.ts +++ b/packages/utilities/src/dom/getRect.ts @@ -1,25 +1,23 @@ import type { IRectangle } from '../IRectangle'; +import { getWindow } from './getWindow'; /** * Helper to get bounding client rect. Passing in window will get the window size. * * @public */ -export function getRect( - element: HTMLElement | Window | null, - // eslint-disable-next-line no-restricted-globals - win: Window = window, -): IRectangle | undefined { +export function getRect(element: HTMLElement | Window | null, win?: Window): IRectangle | undefined { + const theWin = win ?? getWindow()!; let rect: IRectangle | undefined; if (element) { if (element === win) { rect = { left: 0, top: 0, - width: win.innerWidth, - height: win.innerHeight, - right: win.innerWidth, - bottom: win.innerHeight, + width: theWin.innerWidth, + height: theWin.innerHeight, + right: theWin.innerWidth, + bottom: theWin.innerHeight, }; } else if ((element as { getBoundingClientRect?: unknown }).getBoundingClientRect) { rect = (element as HTMLElement).getBoundingClientRect(); diff --git a/packages/utilities/src/dom/raiseClick.ts b/packages/utilities/src/dom/raiseClick.ts index b5323305a2b48c..fecf98ccca3b71 100644 --- a/packages/utilities/src/dom/raiseClick.ts +++ b/packages/utilities/src/dom/raiseClick.ts @@ -1,12 +1,11 @@ +import { getDocument } from './getDocument'; + /** Raises a click event. * @deprecated Moved to `FocusZone` component since it was the only place internally using this function. */ -export function raiseClick( - target: Element, - // eslint-disable-next-line no-restricted-globals - doc: Document = document, -): void { - const event = createNewEvent('MouseEvents', doc); +export function raiseClick(target: Element, doc?: Document): void { + const theDoc = doc ?? getDocument()!; + const event = createNewEvent('MouseEvents', theDoc); // eslint-disable-next-line deprecation/deprecation event.initEvent('click', true, true); target.dispatchEvent(event); diff --git a/packages/utilities/src/focus.ts b/packages/utilities/src/focus.ts index 889e5771d7caa6..bb661a2618ec36 100644 --- a/packages/utilities/src/focus.ts +++ b/packages/utilities/src/focus.ts @@ -382,13 +382,14 @@ export function isElementVisible(element: HTMLElement | undefined | null): boole * * @public */ -export function isElementVisibleAndNotHidden( - element: HTMLElement | undefined | null, +export function isElementVisibleAndNotHidden(element: HTMLElement | undefined | null, win?: Window): boolean { // eslint-disable-next-line no-restricted-globals - win: Window = window, -): boolean { + const theWin = win ?? getWindow()!; return ( - !!element && isElementVisible(element) && !element.hidden && win.getComputedStyle(element).visibility !== 'hidden' + !!element && + isElementVisible(element) && + !element.hidden && + theWin.getComputedStyle(element).visibility !== 'hidden' ); } @@ -474,10 +475,10 @@ export function doesElementContainFocus(element: HTMLElement): boolean { export function shouldWrapFocus( element: HTMLElement, noWrapDataAttribute: 'data-no-vertical-wrap' | 'data-no-horizontal-wrap', - // eslint-disable-next-line no-restricted-globals - doc: Document = document, + doc?: Document, ): boolean { - return elementContainsAttribute(element, noWrapDataAttribute, doc) === 'true' ? false : true; + const theDoc = doc ?? getDocument()!; + return elementContainsAttribute(element, noWrapDataAttribute, theDoc) === 'true' ? false : true; } let animationId: number | undefined = undefined; diff --git a/packages/utilities/src/scroll.ts b/packages/utilities/src/scroll.ts index 51e729f6232be2..3319ce06027975 100644 --- a/packages/utilities/src/scroll.ts +++ b/packages/utilities/src/scroll.ts @@ -136,23 +136,21 @@ export function enableBodyScroll(): void { * * @public */ -export function getScrollbarWidth( - // eslint-disable-next-line no-restricted-globals - doc: Document = document, -): number { +export function getScrollbarWidth(doc?: Document): number { if (_scrollbarWidth === undefined) { - let scrollDiv: HTMLElement = doc.createElement('div'); + const theDoc = doc ?? getDocument()!; + let scrollDiv: HTMLElement = theDoc.createElement('div'); scrollDiv.style.setProperty('width', '100px'); scrollDiv.style.setProperty('height', '100px'); scrollDiv.style.setProperty('overflow', 'scroll'); scrollDiv.style.setProperty('position', 'absolute'); scrollDiv.style.setProperty('top', '-9999px'); - doc.body.appendChild(scrollDiv); + theDoc.body.appendChild(scrollDiv); // Get the scrollbar width _scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; // Delete the DIV - doc.body.removeChild(scrollDiv); + theDoc.body.removeChild(scrollDiv); } return _scrollbarWidth; From 9ad91552af6dac326421bb57ef5bc0eda0ddcfcc Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 11 Oct 2023 19:58:40 +0000 Subject: [PATCH 29/44] change files --- ...-cra-template-e9bc2562-933b-49d3-b708-7115c08a4810.json | 7 +++++++ ...lobal-context-6f67cc92-8e93-4c13-9e33-cc0c6d64fb49.json | 7 +++++++ ...picker-compat-0fe329a8-786d-4a84-af00-4bb8afba2a3d.json | 7 +++++++ ...le-type-icons-05eaab0e-2b0a-4cf7-8b58-5da26471a17a.json | 7 +++++++ ...otion-preview-b9bda7bc-5381-412e-9808-2c0a86796435.json | 7 +++++++ ...ared-contexts-44f55996-a722-481d-bec7-a74dcaf5eeaa.json | 7 +++++++ ...act-utilities-0dc8f037-947b-456a-bd8e-f93489587224.json | 7 +++++++ 7 files changed, 49 insertions(+) create mode 100644 change/@fluentui-cra-template-e9bc2562-933b-49d3-b708-7115c08a4810.json create mode 100644 change/@fluentui-global-context-6f67cc92-8e93-4c13-9e33-cc0c6d64fb49.json create mode 100644 change/@fluentui-react-datepicker-compat-0fe329a8-786d-4a84-af00-4bb8afba2a3d.json create mode 100644 change/@fluentui-react-file-type-icons-05eaab0e-2b0a-4cf7-8b58-5da26471a17a.json create mode 100644 change/@fluentui-react-motion-preview-b9bda7bc-5381-412e-9808-2c0a86796435.json create mode 100644 change/@fluentui-react-shared-contexts-44f55996-a722-481d-bec7-a74dcaf5eeaa.json create mode 100644 change/@fluentui-react-utilities-0dc8f037-947b-456a-bd8e-f93489587224.json diff --git a/change/@fluentui-cra-template-e9bc2562-933b-49d3-b708-7115c08a4810.json b/change/@fluentui-cra-template-e9bc2562-933b-49d3-b708-7115c08a4810.json new file mode 100644 index 00000000000000..a0cb086c51e6a6 --- /dev/null +++ b/change/@fluentui-cra-template-e9bc2562-933b-49d3-b708-7115c08a4810.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disable lint for no-restricted-globals", + "packageName": "@fluentui/cra-template", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-global-context-6f67cc92-8e93-4c13-9e33-cc0c6d64fb49.json b/change/@fluentui-global-context-6f67cc92-8e93-4c13-9e33-cc0c6d64fb49.json new file mode 100644 index 00000000000000..5ddef01cf7e615 --- /dev/null +++ b/change/@fluentui-global-context-6f67cc92-8e93-4c13-9e33-cc0c6d64fb49.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "chore: disable lint for no-restricted-globals", + "packageName": "@fluentui/global-context", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-datepicker-compat-0fe329a8-786d-4a84-af00-4bb8afba2a3d.json b/change/@fluentui-react-datepicker-compat-0fe329a8-786d-4a84-af00-4bb8afba2a3d.json new file mode 100644 index 00000000000000..99877cd61e7663 --- /dev/null +++ b/change/@fluentui-react-datepicker-compat-0fe329a8-786d-4a84-af00-4bb8afba2a3d.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disable lint for no-restricted-globals", + "packageName": "@fluentui/react-datepicker-compat", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-file-type-icons-05eaab0e-2b0a-4cf7-8b58-5da26471a17a.json b/change/@fluentui-react-file-type-icons-05eaab0e-2b0a-4cf7-8b58-5da26471a17a.json new file mode 100644 index 00000000000000..464da2e0e0cb19 --- /dev/null +++ b/change/@fluentui-react-file-type-icons-05eaab0e-2b0a-4cf7-8b58-5da26471a17a.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: update `getFileTypeIconsSuffix()` to take optional parameter for `window` reference.", + "packageName": "@fluentui/react-file-type-icons", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-motion-preview-b9bda7bc-5381-412e-9808-2c0a86796435.json b/change/@fluentui-react-motion-preview-b9bda7bc-5381-412e-9808-2c0a86796435.json new file mode 100644 index 00000000000000..64b355b62a3c41 --- /dev/null +++ b/change/@fluentui-react-motion-preview-b9bda7bc-5381-412e-9808-2c0a86796435.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disable lint for no-retricted-globals", + "packageName": "@fluentui/react-motion-preview", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-shared-contexts-44f55996-a722-481d-bec7-a74dcaf5eeaa.json b/change/@fluentui-react-shared-contexts-44f55996-a722-481d-bec7-a74dcaf5eeaa.json new file mode 100644 index 00000000000000..c01002bdd714c4 --- /dev/null +++ b/change/@fluentui-react-shared-contexts-44f55996-a722-481d-bec7-a74dcaf5eeaa.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disable lint for no-restricted-globals", + "packageName": "@fluentui/react-shared-contexts", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-utilities-0dc8f037-947b-456a-bd8e-f93489587224.json b/change/@fluentui-react-utilities-0dc8f037-947b-456a-bd8e-f93489587224.json new file mode 100644 index 00000000000000..d43d0ca7181930 --- /dev/null +++ b/change/@fluentui-react-utilities-0dc8f037-947b-456a-bd8e-f93489587224.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: update to get `window` reference via `getWindow()`.", + "packageName": "@fluentui/react-utilities", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +} From 22cdc22cd5899e267941b7453d718d694e3e2ade Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 11 Oct 2023 21:21:32 +0000 Subject: [PATCH 30/44] cleanup from reviewing PR --- .../dom-utilities/src/elementContainsAttribute.ts | 2 -- packages/dom-utilities/src/portalContainsElement.ts | 2 -- packages/merge-styles/src/StyleOptionsState.ts | 10 +++++++--- packages/react/src/utilities/dom.ts | 12 ++++++++---- packages/utilities/src/dom/canUseDOM.ts | 11 +++++------ 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/dom-utilities/src/elementContainsAttribute.ts b/packages/dom-utilities/src/elementContainsAttribute.ts index c89868a29e2734..8a88beeb4a90d8 100644 --- a/packages/dom-utilities/src/elementContainsAttribute.ts +++ b/packages/dom-utilities/src/elementContainsAttribute.ts @@ -7,8 +7,6 @@ import { findElementRecursive } from './findElementRecursive'; * @returns the value of the first instance found */ export function elementContainsAttribute(element: HTMLElement, attribute: string, doc?: Document): string | null { - // eslint-disable-next-line no-restricted-globals - doc ??= document; const elementMatch = findElementRecursive( element, (testElement: HTMLElement) => testElement.hasAttribute(attribute), diff --git a/packages/dom-utilities/src/portalContainsElement.ts b/packages/dom-utilities/src/portalContainsElement.ts index f43a0a2e5c5739..979b364d3e1a7d 100644 --- a/packages/dom-utilities/src/portalContainsElement.ts +++ b/packages/dom-utilities/src/portalContainsElement.ts @@ -10,8 +10,6 @@ import { DATA_PORTAL_ATTRIBUTE } from './setPortalAttribute'; * (or root if parent is undefined or invalid.) */ export function portalContainsElement(target: HTMLElement, parent?: HTMLElement, doc?: Document): boolean { - // eslint-disable-next-line no-restricted-globals - doc ??= document; const elementMatch = findElementRecursive( target, (testElement: HTMLElement) => parent === testElement || testElement.hasAttribute(DATA_PORTAL_ATTRIBUTE), diff --git a/packages/merge-styles/src/StyleOptionsState.ts b/packages/merge-styles/src/StyleOptionsState.ts index 93a6359275cccc..d1eda55135b622 100644 --- a/packages/merge-styles/src/StyleOptionsState.ts +++ b/packages/merge-styles/src/StyleOptionsState.ts @@ -14,9 +14,13 @@ export function setRTL(isRTL: boolean): void { */ export function getRTL(): boolean { if (_rtl === undefined) { - // eslint-disable-next-line no-restricted-globals - const doc = document; - _rtl = typeof doc !== 'undefined' && !!doc.documentElement && doc.documentElement.getAttribute('dir') === 'rtl'; + _rtl = + // eslint-disable-next-line no-restricted-globals + typeof document !== 'undefined' && + // eslint-disable-next-line no-restricted-globals + !!document.documentElement && + // eslint-disable-next-line no-restricted-globals + document.documentElement.getAttribute('dir') === 'rtl'; } return _rtl; } diff --git a/packages/react/src/utilities/dom.ts b/packages/react/src/utilities/dom.ts index 58141a9f31b7cc..e47669d2bd4986 100644 --- a/packages/react/src/utilities/dom.ts +++ b/packages/react/src/utilities/dom.ts @@ -1,7 +1,8 @@ import { useDocument, useWindow, WindowProviderProps } from '@fluentui/react-window-provider'; /** - * Wrapper for `useDocument()` that falls back to `document` if the provider is not set. + * Get a reference to the `document` object. + * Use this in place of the global `document` in React function components. * @returns Document */ export const useDocumentEx = () => { @@ -10,7 +11,8 @@ export const useDocumentEx = () => { }; /** - * Wrapper for `useWindow()` that falls back to `window` if the provider is not set. + * Get a reference to the `window` object. + * Use this in place of the global `window` in React function components. * @returns Window */ export const useWindowEx = () => { @@ -19,7 +21,8 @@ export const useWindowEx = () => { }; /** - * Helper for reading class component WindowContext. Falls back to `document` if the provider is not set. + * Get a reference to the `document` object. + * Use this in place of the global `document` in React class components. * * @param ctx - Class component WindowContext * @returns Document @@ -30,7 +33,8 @@ export const getDocumentEx = (ctx: Pick | undefin }; /** - * Helper for reading class component WindowContext. Falls back to `window` if the provider is not set. + * Get a reference to the `window` object. + * Use this in place of the global `window` in React class components. * * @param ctx - Class component WindowContext * @returns Window diff --git a/packages/utilities/src/dom/canUseDOM.ts b/packages/utilities/src/dom/canUseDOM.ts index 529db3d9165032..ee7d26eab76e3e 100644 --- a/packages/utilities/src/dom/canUseDOM.ts +++ b/packages/utilities/src/dom/canUseDOM.ts @@ -2,14 +2,13 @@ * Verifies if an application can use DOM. */ export function canUseDOM(): boolean { - // eslint-disable-next-line no-restricted-globals - const win = window; return ( - typeof win !== 'undefined' && + // eslint-disable-next-line no-restricted-globals + typeof window !== 'undefined' && !!( - win.document && - // eslint-disable-next-line deprecation/deprecation - win.document.createElement + window.document && // eslint-disable-line no-restricted-globals + // eslint-disable-next-line deprecation/deprecation, no-restricted-globals + window.document.createElement ) ); } From 7ec3d3207263cc7713c378b83c2c72d9fa241baa Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 11 Oct 2023 21:22:09 +0000 Subject: [PATCH 31/44] add @fluentui/react-window-provider to deps --- apps/ssr-tests-v9/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/ssr-tests-v9/package.json b/apps/ssr-tests-v9/package.json index bf55eab6a4bae1..01dfa23ff870d9 100644 --- a/apps/ssr-tests-v9/package.json +++ b/apps/ssr-tests-v9/package.json @@ -19,7 +19,8 @@ }, "dependencies": { "@fluentui/react-components": "*", - "@fluentui/react-utilities": "*" + "@fluentui/react-utilities": "*", + "@fluentui/react-window-provider": "*" }, "devDependencies": { "@fluentui/eslint-plugin": "*", From b8046ef12e62fc7e924670f390fab1891f1ee1e8 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 11 Oct 2023 22:21:14 +0000 Subject: [PATCH 32/44] update useOnClickOutside to use the correct context --- packages/react-components/react-utilities/package.json | 2 +- .../react-utilities/src/hooks/useOnClickOutside.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/react-components/react-utilities/package.json b/packages/react-components/react-utilities/package.json index 1002daf0b396a9..70850315286a20 100644 --- a/packages/react-components/react-utilities/package.json +++ b/packages/react-components/react-utilities/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@fluentui/keyboard-keys": "^9.0.6", - "@fluentui/react-window-provider": "^2.2.15", + "@fluentui/react-shared-contexts": "^9.9.2", "@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 a539de58c38361..61a0d19b3e275b 100644 --- a/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts +++ b/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { useEventCallback } from './useEventCallback'; -import { useWindow } from '@fluentui/react-window-provider'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; /** * @internal @@ -46,7 +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 win = useWindow(); + const { targetDocument } = useFluent(); + const win = targetDocument?.defaultView; const { refs, callback, element, disabled, disabledFocusOnIframe, contains = DEFAULT_CONTAINS } = options; const timeoutId = React.useRef(undefined); From afa592370d1c43bdd24597637a0c118e9d40f1c0 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 11 Oct 2023 22:21:56 +0000 Subject: [PATCH 33/44] remove unnecessary dep --- apps/ssr-tests-v9/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/ssr-tests-v9/package.json b/apps/ssr-tests-v9/package.json index 01dfa23ff870d9..bf55eab6a4bae1 100644 --- a/apps/ssr-tests-v9/package.json +++ b/apps/ssr-tests-v9/package.json @@ -19,8 +19,7 @@ }, "dependencies": { "@fluentui/react-components": "*", - "@fluentui/react-utilities": "*", - "@fluentui/react-window-provider": "*" + "@fluentui/react-utilities": "*" }, "devDependencies": { "@fluentui/eslint-plugin": "*", From 1f1481492947bbd47e89fbee1878d63df82e298e Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Wed, 11 Oct 2023 23:19:30 +0000 Subject: [PATCH 34/44] expand type --- .../react-utilities/src/hooks/useOnClickOutside.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts b/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts index 61a0d19b3e275b..1ae1f9b3e5f1bc 100644 --- a/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts +++ b/packages/react-components/react-utilities/src/hooks/useOnClickOutside.ts @@ -118,7 +118,7 @@ export const useOnClickOutside = (options: UseOnClickOrScrollOutsideOptions) => }, [listener, element, disabled, handleMouseDown, win]); }; -const getWindowEvent = (target: Node | Window | undefined): 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 From 85b689f405de5153f4798f709f8eafdb08a60567 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 13 Oct 2023 15:14:57 +0000 Subject: [PATCH 35/44] update reference --- packages/utilities/src/EventGroup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utilities/src/EventGroup.ts b/packages/utilities/src/EventGroup.ts index e7d9fbc52500e2..3ea6e16961a466 100644 --- a/packages/utilities/src/EventGroup.ts +++ b/packages/utilities/src/EventGroup.ts @@ -73,7 +73,7 @@ export class EventGroup { const theDoc = doc ?? getDocument()!; if (EventGroup._isElement(target)) { - if (typeof doc !== 'undefined' && theDoc.createEvent) { + if (typeof theDoc !== 'undefined' && theDoc.createEvent) { let ev = theDoc.createEvent('HTMLEvents'); // eslint-disable-next-line deprecation/deprecation From 9750e0621e1f620ba18cc1cff0d2bdfeecaa42e4 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 13 Oct 2023 16:35:45 +0000 Subject: [PATCH 36/44] fix: Calendar gets `window` references from `FluentProvider` context. --- .../src/components/Calendar/Calendar.tsx | 39 ++++++++++--------- .../react-calendar-compat/src/utils/dom.ts | 11 ------ .../react-calendar-compat/src/utils/focus.ts | 9 ++--- .../react-calendar-compat/src/utils/index.ts | 1 - 4 files changed, 24 insertions(+), 36 deletions(-) delete mode 100644 packages/react-components/react-calendar-compat/src/utils/dom.ts 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 0d3cc43404a39e..106d7ba119ec9c 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'; @@ -76,20 +76,20 @@ function useVisibilityState({ showMonthPickerAsOverlay, }: CalendarProps) { /** State used to show/hide month picker */ + const showMonthPickerAsOverlayState = useShowMonthPickerAsOverloay({ + 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 = () => { @@ -104,14 +104,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]); @@ -231,10 +233,12 @@ export const Calendar: React.FunctionComponent = React.forwardRef navigateDay(date); }; - const onHeaderSelect = getShowMonthPickerAsOverlay({ + const showMonthPickerAsOverlay = useShowMonthPickerAsOverloay({ isDayPickerVisible: isDayPickerVisibleProp, showMonthPickerAsOverlay: showMonthPickerAsOverlayProp, - }) + }); + + const onHeaderSelect = showMonthPickerAsOverlay ? (): void => { toggleDayMonthPickerVisibility(); @@ -297,10 +301,6 @@ export const Calendar: React.FunctionComponent = React.forwardRef break; } }; - const showMonthPickerAsOverlay = getShowMonthPickerAsOverlay({ - isDayPickerVisible: isDayPickerVisibleProp, - showMonthPickerAsOverlay: showMonthPickerAsOverlayProp, - }); const monthPickerOnly = !showMonthPickerAsOverlay && !isDayPickerVisible; @@ -401,7 +401,8 @@ export const Calendar: React.FunctionComponent = React.forwardRef ); Calendar.displayName = 'Calendar'; -function getShowMonthPickerAsOverlay({ isDayPickerVisible, showMonthPickerAsOverlay }: CalendarProps) { - const win = getWindow(); +const useShowMonthPickerAsOverloay = ({ 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'; From 538a08c5647afdf31c65ddab321fb2d636aaa5bf Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 13 Oct 2023 16:58:24 +0000 Subject: [PATCH 37/44] feat: DatePicker gets `window` references from `FluentProvider` context. --- .../src/components/Calendar/Calendar.tsx | 408 ++++++++++++++++++ .../react-datepicker-compat/src/utils/dom.ts | 18 - .../src/utils/focus.ts | 32 ++ .../src/utils/index.ts | 6 + 4 files changed, 446 insertions(+), 18 deletions(-) create mode 100644 packages/react-components/react-datepicker-compat/src/components/Calendar/Calendar.tsx delete mode 100644 packages/react-components/react-datepicker-compat/src/utils/dom.ts create mode 100644 packages/react-components/react-datepicker-compat/src/utils/focus.ts create mode 100644 packages/react-components/react-datepicker-compat/src/utils/index.ts diff --git a/packages/react-components/react-datepicker-compat/src/components/Calendar/Calendar.tsx b/packages/react-components/react-datepicker-compat/src/components/Calendar/Calendar.tsx new file mode 100644 index 00000000000000..106d7ba119ec9c --- /dev/null +++ b/packages/react-components/react-datepicker-compat/src/components/Calendar/Calendar.tsx @@ -0,0 +1,408 @@ +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, + DateRangeType, + DayOfWeek, + DEFAULT_CALENDAR_STRINGS, + DEFAULT_DATE_FORMATTING, + FirstWeekOfYear, + focusAsync, +} from '../../utils'; +import { CalendarDay } from '../CalendarDay/CalendarDay'; +import { CalendarMonth } from '../CalendarMonth/CalendarMonth'; +import { useCalendarStyles_unstable } from './useCalendarStyles.styles'; +import type { ICalendarDay } from '../CalendarDay/CalendarDay.types'; +import type { ICalendarMonth } from '../CalendarMonth/CalendarMonth.types'; +import type { CalendarProps } from './Calendar.types'; + +const MIN_SIZE_FORCE_OVERLAY = 440; + +const defaultWorkWeekDays: DayOfWeek[] = [ + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, +]; + +function useDateState({ value, today = new Date(), onSelectDate }: CalendarProps) { + /** The currently selected date in the calendar */ + const [selectedDate, setSelectedDate] = useControllableState({ + defaultState: today, + initialState: today, + state: value, + }); + + /** The currently focused date in the day picker, but not necessarily selected */ + const [navigatedDay = today, setNavigatedDay] = React.useState(value); + + /** The currently focused date in the month picker, but not necessarily selected */ + const [navigatedMonth = today, setNavigatedMonth] = React.useState(value); + + /** If using a controlled value, when that value changes, navigate to that date */ + const [lastSelectedDate = today, setLastSelectedDate] = React.useState(value); + if (value && lastSelectedDate.valueOf() !== value.valueOf()) { + setNavigatedDay(value); + setNavigatedMonth(value); + setLastSelectedDate(value); + } + + const navigateMonth = (date: Date) => { + setNavigatedMonth(date); + }; + + const navigateDay = (date: Date) => { + setNavigatedMonth(date); + setNavigatedDay(date); + }; + + const onDateSelected = (date: Date, selectedDateRangeArray?: Date[]) => { + setNavigatedMonth(date); + setNavigatedDay(date); + setSelectedDate(date); + onSelectDate?.(date, selectedDateRangeArray); + }; + + return [selectedDate, navigatedDay, navigatedMonth, onDateSelected, navigateDay, navigateMonth] as const; +} + +function useVisibilityState({ + isDayPickerVisible: isDayPickerVisibleProp, + isMonthPickerVisible: isMonthPickerVisibleProp, + showMonthPickerAsOverlay, +}: CalendarProps) { + /** State used to show/hide month picker */ + const showMonthPickerAsOverlayState = useShowMonthPickerAsOverloay({ + isDayPickerVisible: isDayPickerVisibleProp, + showMonthPickerAsOverlay, + }); + const [isMonthPickerVisible, setIsMonthPickerVisible] = useControllableState({ + defaultState: false, + initialState: true, + state: showMonthPickerAsOverlayState ? undefined : isMonthPickerVisibleProp, + }); + /** State used to show/hide day picker */ + const [isDayPickerVisible, setIsDayPickerVisible] = useControllableState({ + defaultState: true, + initialState: true, + state: showMonthPickerAsOverlayState ? undefined : isDayPickerVisibleProp, + }); + + const toggleDayMonthPickerVisibility = () => { + setIsMonthPickerVisible(!isMonthPickerVisible); + setIsDayPickerVisible(!isDayPickerVisible); + }; + + return [isMonthPickerVisible, isDayPickerVisible, toggleDayMonthPickerVisibility] as const; +} + +function useFocusLogic({ componentRef }: CalendarProps, isDayPickerVisible: boolean, isMonthPickerVisible: boolean) { + 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, win); + } else if (isMonthPickerVisible && monthPicker.current) { + focusAsync(monthPicker.current, win); + } + }, [isDayPickerVisible, isMonthPickerVisible, win]); + + React.useImperativeHandle(componentRef, () => ({ focus }), [focus]); + + React.useEffect(() => { + if (focusOnUpdate.current) { + focus(); + focusOnUpdate.current = false; + } + }); + + const focusOnNextUpdate = () => { + focusOnUpdate.current = true; + }; + + return [dayPicker, monthPicker, focusOnNextUpdate] as const; +} + +/** + * @internal + */ +export const Calendar: React.FunctionComponent = React.forwardRef( + (props, forwardedRef) => { + const { + allFocusable = false, + calendarDayProps, + calendarMonthProps, + className, + componentRef, + dateRangeType = DateRangeType.Day, + dateTimeFormatter = DEFAULT_DATE_FORMATTING, + firstDayOfWeek = DayOfWeek.Sunday, + firstWeekOfYear = FirstWeekOfYear.FirstDay, + highlightCurrentMonth = false, + highlightSelectedMonth = false, + id, + isDayPickerVisible: isDayPickerVisibleProp = true, + isMonthPickerVisible: isMonthPickerVisibleProp = true, + maxDate, + minDate, + onDismiss, + onSelectDate, + restrictedDates, + showCloseButton = false, + showGoToToday = true, + showMonthPickerAsOverlay: showMonthPickerAsOverlayProp = false, + showSixWeeksByDefault = false, + showWeekNumbers = false, + strings = DEFAULT_CALENDAR_STRINGS, + today = new Date(), + value, + workWeekDays = defaultWorkWeekDays, + } = props; + + const [selectedDate, navigatedDay, navigatedMonth, onDateSelected, navigateDay, navigateMonth] = useDateState({ + onSelectDate, + value, + today, + }); + const [isMonthPickerVisible, isDayPickerVisible, toggleDayMonthPickerVisibility] = useVisibilityState({ + isDayPickerVisible: isDayPickerVisibleProp, + isMonthPickerVisible: isMonthPickerVisibleProp, + showMonthPickerAsOverlay: showMonthPickerAsOverlayProp, + }); + const [dayPicker, monthPicker, focusOnNextUpdate] = useFocusLogic( + { componentRef }, + isDayPickerVisible, + isMonthPickerVisible, + ); + + const renderGoToTodayButton = () => { + let goTodayEnabled = showGoToToday; + + if (goTodayEnabled && today) { + goTodayEnabled = + navigatedDay.getFullYear() !== today.getFullYear() || + navigatedDay.getMonth() !== today.getMonth() || + navigatedMonth.getFullYear() !== today.getFullYear() || + navigatedMonth.getMonth() !== today.getMonth(); + } + + return ( + showGoToToday && ( + + ) + ); + }; + + const onNavigateDayDate = (date: Date, focusOnNavigatedDay: boolean): void => { + navigateDay(date); + if (focusOnNavigatedDay) { + focusOnNextUpdate(); + } + }; + + const onNavigateMonthDate = (date: Date, focusOnNavigatedDay: boolean): void => { + if (focusOnNavigatedDay) { + focusOnNextUpdate(); + } + + if (!focusOnNavigatedDay) { + navigateMonth(date); + return; + } + + if (monthPickerOnly) { + onDateSelected(date); + } + + navigateDay(date); + }; + + const showMonthPickerAsOverlay = useShowMonthPickerAsOverloay({ + isDayPickerVisible: isDayPickerVisibleProp, + showMonthPickerAsOverlay: showMonthPickerAsOverlayProp, + }); + + const onHeaderSelect = showMonthPickerAsOverlay + ? (): void => { + toggleDayMonthPickerVisibility(); + + focusOnNextUpdate(); + } + : undefined; + + const onGotoToday = (): void => { + navigateDay(today!); + focusOnNextUpdate(); + }; + + const onButtonKeyDown = (callback: () => void): ((ev: React.KeyboardEvent) => void) => { + return (ev: React.KeyboardEvent) => { + switch (ev.key) { + case Enter: + case Space: + callback(); + break; + } + }; + }; + + const onDatePickerPopupKeyDown = (ev: React.KeyboardEvent): void => { + switch (ev.key) { + case Enter: + ev.preventDefault(); + break; + + case Backspace: + ev.preventDefault(); + break; + + case Escape: + ev.stopPropagation(); + onDismiss?.(); + break; + + case PageUp: + if (ev.ctrlKey) { + // go to next year + navigateDay(addYears(navigatedDay, 1)); + } else { + // go to next month + navigateDay(addMonths(navigatedDay, 1)); + } + ev.preventDefault(); + break; + case PageDown: + if (ev.ctrlKey) { + // go to previous year + navigateDay(addYears(navigatedDay, -1)); + } else { + // go to previous month + navigateDay(addMonths(navigatedDay, -1)); + } + ev.preventDefault(); + break; + default: + break; + } + }; + + const monthPickerOnly = !showMonthPickerAsOverlay && !isDayPickerVisible; + + const classes = useCalendarStyles_unstable({ + className, + isDayPickerVisible, + isMonthPickerVisible, + showWeekNumbers, + }); + + let todayDateString: string = ''; + let selectedDateString: string = ''; + if (dateTimeFormatter && strings!.todayDateFormatString) { + todayDateString = strings!.todayDateFormatString.replace( + '{0}', + dateTimeFormatter.formatMonthDayYear(today, strings!), + ); + } + if (dateTimeFormatter && strings!.selectedDateFormatString) { + const dateStringFormatter = monthPickerOnly + ? dateTimeFormatter.formatMonthYear + : dateTimeFormatter.formatMonthDayYear; + selectedDateString = strings!.selectedDateFormatString.replace( + '{0}', + dateStringFormatter(selectedDate, strings!), + ); + } + const selectionAndTodayString = selectedDateString + ', ' + todayDateString; + + return ( +
+
+ {selectedDateString} +
+ {isDayPickerVisible && ( + + )} + {isDayPickerVisible && isMonthPickerVisible &&
} + {isMonthPickerVisible ? ( +
+ + {renderGoToTodayButton()} +
+ ) : ( + renderGoToTodayButton() + )} +
+ ); + }, +); +Calendar.displayName = 'Calendar'; + +const useShowMonthPickerAsOverloay = ({ 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-datepicker-compat/src/utils/dom.ts b/packages/react-components/react-datepicker-compat/src/utils/dom.ts deleted file mode 100644 index 7b49a9126296a9..00000000000000 --- a/packages/react-components/react-datepicker-compat/src/utils/dom.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { canUseDOM } from '@fluentui/react-utilities'; - -export function getWindow(targetElement?: Element | null): Window | undefined { - if ( - !canUseDOM() || - // eslint-disable-next-line no-restricted-globals - typeof window === 'undefined' - ) { - return undefined; - } - - const el = targetElement as Element; - - return el && el.ownerDocument && el.ownerDocument.defaultView - ? el.ownerDocument.defaultView - : // eslint-disable-next-line no-restricted-globals - window; -} diff --git a/packages/react-components/react-datepicker-compat/src/utils/focus.ts b/packages/react-components/react-datepicker-compat/src/utils/focus.ts new file mode 100644 index 00000000000000..ce48f80e48029b --- /dev/null +++ b/packages/react-components/react-datepicker-compat/src/utils/focus.ts @@ -0,0 +1,32 @@ +let targetToFocusOnNextRepaint: HTMLElement | { focus: () => void } | null | undefined = undefined; + +/** + * Sets focus to an element asynchronously. The focus will be set at the next browser repaint, + * meaning it won't cause any extra recalculations. If more than one focusAsync is called during one frame, + * 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, + 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) { + targetToFocusOnNextRepaint = element; + return; + } + + targetToFocusOnNextRepaint = 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(() => { + targetToFocusOnNextRepaint && targetToFocusOnNextRepaint.focus(); + + // We are done focusing for this frame, so reset the queued focus element + targetToFocusOnNextRepaint = undefined; + }); + } + } +} diff --git a/packages/react-components/react-datepicker-compat/src/utils/index.ts b/packages/react-components/react-datepicker-compat/src/utils/index.ts new file mode 100644 index 00000000000000..cb8c73d5cadcb6 --- /dev/null +++ b/packages/react-components/react-datepicker-compat/src/utils/index.ts @@ -0,0 +1,6 @@ +export * from './animations'; +export * from './constants'; +export * from './dateFormatting'; +export * from './dateGrid'; +export * from './dateMath'; +export * from './focus'; From bb5d2144d144ce9bfceaf49a0c0566d5687b7e91 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 13 Oct 2023 17:32:41 +0000 Subject: [PATCH 38/44] sync dependency version --- packages/react-components/react-utilities/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-utilities/package.json b/packages/react-components/react-utilities/package.json index 70850315286a20..73d6cbf8169bcb 100644 --- a/packages/react-components/react-utilities/package.json +++ b/packages/react-components/react-utilities/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@fluentui/keyboard-keys": "^9.0.6", - "@fluentui/react-shared-contexts": "^9.9.2", + "@fluentui/react-shared-contexts": "^9.10.0", "@swc/helpers": "^0.5.1" }, "peerDependencies": { From 438a3b72abcfff7e48ab0684fc8433b05d50b614 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 13 Oct 2023 21:06:49 +0000 Subject: [PATCH 39/44] fix window/document usage --- .../MarqueeSelection/MarqueeSelection.base.tsx | 2 +- .../react/src/utilities/decorators/withViewport.tsx | 5 +++-- packages/utilities/src/AutoScroll.ts | 10 +++++----- packages/utilities/src/dom/getRect.ts | 7 +++++-- packages/utilities/src/focus.ts | 4 ++-- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/react/src/components/MarqueeSelection/MarqueeSelection.base.tsx b/packages/react/src/components/MarqueeSelection/MarqueeSelection.base.tsx index 8929e7d1e25444..1681c6987f5daa 100644 --- a/packages/react/src/components/MarqueeSelection/MarqueeSelection.base.tsx +++ b/packages/react/src/components/MarqueeSelection/MarqueeSelection.base.tsx @@ -176,7 +176,7 @@ export class MarqueeSelectionBase extends React.Component( private _updateViewport = (withForceUpdate?: boolean) => { const { viewport } = this.state; const viewportElement = this._root.current; + const win = getWindow(viewportElement); const scrollElement = findScrollableParent(viewportElement) as HTMLElement; - const scrollRect = getRect(scrollElement); - const clientRect = getRect(viewportElement); + const scrollRect = getRect(scrollElement, win); + const clientRect = getRect(viewportElement, win); const updateComponent = () => { if (withForceUpdate && this._composedComponentInstance) { this._composedComponentInstance.forceUpdate(); diff --git a/packages/utilities/src/AutoScroll.ts b/packages/utilities/src/AutoScroll.ts index 6104a4b7c8bfd6..523ac8ed6dd4fd 100644 --- a/packages/utilities/src/AutoScroll.ts +++ b/packages/utilities/src/AutoScroll.ts @@ -28,21 +28,21 @@ export class AutoScroll { private _timeoutId?: number; constructor(element: HTMLElement, win?: Window) { - const theWin = win ?? getWindow()!; + const theWin = win ?? getWindow(element)!; this._events = new EventGroup(this); this._scrollableParent = findScrollableParent(element) as HTMLElement; this._incrementScroll = this._incrementScroll.bind(this); - this._scrollRect = getRect(this._scrollableParent); + this._scrollRect = getRect(this._scrollableParent, win); // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (this._scrollableParent === (win as any)) { + if (this._scrollableParent === (theWin as any)) { this._scrollableParent = theWin.document.body; } if (this._scrollableParent) { - this._events.on(win, 'mousemove', this._onMouseMove, true); - this._events.on(win, 'touchmove', this._onTouchMove, true); + this._events.on(theWin, 'mousemove', this._onMouseMove, true); + this._events.on(theWin, 'touchmove', this._onTouchMove, true); } } diff --git a/packages/utilities/src/dom/getRect.ts b/packages/utilities/src/dom/getRect.ts index 57ae8a85809f72..e4d00483766d34 100644 --- a/packages/utilities/src/dom/getRect.ts +++ b/packages/utilities/src/dom/getRect.ts @@ -7,10 +7,13 @@ import { getWindow } from './getWindow'; * @public */ export function getRect(element: HTMLElement | Window | null, win?: Window): IRectangle | undefined { - const theWin = win ?? getWindow()!; + const theWin = + win ?? (!element || (element && element.hasOwnProperty('devicePixelRatio'))) + ? getWindow() + : getWindow(element as HTMLElement)!; let rect: IRectangle | undefined; if (element) { - if (element === win) { + if (element === theWin) { rect = { left: 0, top: 0, diff --git a/packages/utilities/src/focus.ts b/packages/utilities/src/focus.ts index bb661a2618ec36..1275b00377f404 100644 --- a/packages/utilities/src/focus.ts +++ b/packages/utilities/src/focus.ts @@ -458,8 +458,8 @@ export function isElementFocusSubZone(element?: HTMLElement): boolean { * @public */ export function doesElementContainFocus(element: HTMLElement): boolean { - let document = getDocument(element); - let currentActiveElement: HTMLElement | undefined = document && (document.activeElement as HTMLElement); + let doc = getDocument(element); + let currentActiveElement: HTMLElement | undefined = doc && (doc.activeElement as HTMLElement); if (currentActiveElement && elementContains(element, currentActiveElement)) { return true; } From 0fe943eba745aeaf57bbaa100569887eb33bca62 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 13 Oct 2023 22:12:45 +0000 Subject: [PATCH 40/44] disable 'no-restricted-globals' lint rule for react-docsite-components --- packages/react-docsite-components/.eslintrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-docsite-components/.eslintrc.json b/packages/react-docsite-components/.eslintrc.json index a6ecfa7038e6be..ec2a316fd41b8f 100644 --- a/packages/react-docsite-components/.eslintrc.json +++ b/packages/react-docsite-components/.eslintrc.json @@ -2,6 +2,7 @@ "extends": ["plugin:@fluentui/eslint-plugin/react--legacy"], "root": true, "rules": { - "deprecation/deprecation": "off" + "deprecation/deprecation": "off", + "no-restricted-globals": "off" } } From 959e8e20d80831f9b8d1e6113889468d6c521b32 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 13 Oct 2023 23:06:44 +0000 Subject: [PATCH 41/44] disable 'no-restricted-globals' lint rule for apps --- apps/perf-test-react-components/.eslintrc.json | 3 ++- apps/perf-test/.eslintrc.json | 3 ++- apps/pr-deploy-site/.eslintrc.json | 1 + apps/public-docsite-resources/.eslintrc.json | 3 ++- apps/public-docsite-v9/.eslintrc.json | 3 ++- apps/public-docsite/.eslintrc.json | 1 + apps/react-18-tests-v8/.eslintrc.json | 4 +++- apps/react-18-tests-v9/.eslintrc.json | 4 +++- apps/recipes-react-components/.eslintrc.json | 3 ++- apps/ssr-tests-v9/.eslintrc.json | 3 ++- apps/stress-test/.eslintrc.json | 3 ++- apps/theming-designer/.eslintrc.json | 1 + apps/vr-tests-react-components/.eslintrc.json | 3 ++- apps/vr-tests/.eslintrc.json | 3 ++- 14 files changed, 27 insertions(+), 11 deletions(-) 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/perf-test/.eslintrc.json b/apps/perf-test/.eslintrc.json index 6c6d649f517582..b77a03e1dea94e 100644 --- a/apps/perf-test/.eslintrc.json +++ b/apps/perf-test/.eslintrc.json @@ -2,6 +2,7 @@ "extends": ["plugin:@fluentui/eslint-plugin/react"], "root": true, "rules": { - "no-console": "off" + "no-console": "off", + "no-restricted-globals": "off" } } diff --git a/apps/pr-deploy-site/.eslintrc.json b/apps/pr-deploy-site/.eslintrc.json index ac81a128fc7689..aa07c25a34204a 100644 --- a/apps/pr-deploy-site/.eslintrc.json +++ b/apps/pr-deploy-site/.eslintrc.json @@ -12,6 +12,7 @@ // turn off conflicting or unwanted rules from normal set "curly": "off", "no-var": "off", + "no-restricted-globals": "off", "vars-on-top": "off", "prefer-arrow-callback": "off" } diff --git a/apps/public-docsite-resources/.eslintrc.json b/apps/public-docsite-resources/.eslintrc.json index 8e090ba63188fa..b9cc959897e9b2 100644 --- a/apps/public-docsite-resources/.eslintrc.json +++ b/apps/public-docsite-resources/.eslintrc.json @@ -5,6 +5,7 @@ "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/explicit-member-accessibility": "off", "@typescript-eslint/member-ordering": "off", - "deprecation/deprecation": "off" + "deprecation/deprecation": "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/public-docsite/.eslintrc.json b/apps/public-docsite/.eslintrc.json index 80f89945c1a8dc..d617947c59dffa 100644 --- a/apps/public-docsite/.eslintrc.json +++ b/apps/public-docsite/.eslintrc.json @@ -5,6 +5,7 @@ "@typescript-eslint/no-explicit-any": "off", "deprecation/deprecation": "off", "import/no-webpack-loader-syntax": "off", // ok in this project + "no-restricted-globals": "off", "prefer-const": "off", "react/jsx-no-bind": "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/theming-designer/.eslintrc.json b/apps/theming-designer/.eslintrc.json index f7aed51167d6cb..0f42e8d9a881b3 100644 --- a/apps/theming-designer/.eslintrc.json +++ b/apps/theming-designer/.eslintrc.json @@ -4,6 +4,7 @@ "rules": { "@typescript-eslint/no-explicit-any": "off", "deprecation/deprecation": "off", + "no-restricted-globals": "off", "prefer-const": "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/apps/vr-tests/.eslintrc.json b/apps/vr-tests/.eslintrc.json index b3f394f7cbaca5..54ac641d7b3bfc 100644 --- a/apps/vr-tests/.eslintrc.json +++ b/apps/vr-tests/.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" } } From bd4be813c8607afabf6a029f111dd76783533f73 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Fri, 13 Oct 2023 23:39:06 +0000 Subject: [PATCH 42/44] disable 'no-restricted-globals' lint rule for react-examples --- packages/react-examples/.eslintrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-examples/.eslintrc.json b/packages/react-examples/.eslintrc.json index d4c9b90997c5ee..4a59b1205ddc66 100644 --- a/packages/react-examples/.eslintrc.json +++ b/packages/react-examples/.eslintrc.json @@ -4,6 +4,7 @@ "rules": { "import/no-webpack-loader-syntax": "off", "@typescript-eslint/no-explicit-any": "off", - "no-alert": "off" + "no-alert": "off", + "no-restricted-globals": "off" } } From 8749bc8550a90ee99f056de84bee71a2cefc15c8 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 17 Oct 2023 01:12:37 +0000 Subject: [PATCH 43/44] updates to handle --- .../components/Coachmark/Coachmark.base.tsx | 12 ++++----- .../PositioningContainer.tsx | 2 +- .../ColorRectangle/ColorRectangle.base.tsx | 2 +- .../src/components/ComboBox/ComboBox.tsx | 4 +-- .../DetailsList/DetailsList.base.tsx | 2 +- .../DocumentCard/DocumentCard.base.tsx | 2 +- .../src/components/Dropdown/Dropdown.base.tsx | 4 +-- .../FocusTrapZone/FocusTrapZone.tsx | 6 ++--- .../KeytipLayer/KeytipLayer.base.tsx | 2 +- .../MarqueeSelection.base.tsx | 2 +- .../react/src/components/Nav/Nav.base.tsx | 2 +- .../OverflowSet/OverflowSet.base.tsx | 2 +- .../react/src/components/Panel/Panel.base.tsx | 6 ++--- .../ScrollablePane/ScrollablePane.base.tsx | 2 +- .../BaseSelectedItemsList.tsx | 2 +- .../react/src/components/Slider/useSlider.ts | 9 ++++--- .../SwatchColorPicker.base.tsx | 2 +- .../src/components/pickers/BasePicker.tsx | 6 ++--- .../utilities/DraggableZone/DraggableZone.tsx | 6 ++--- packages/react/src/utilities/dom.ts | 26 +++++++++++++------ 20 files changed, 56 insertions(+), 45 deletions(-) diff --git a/packages/react/src/components/Coachmark/Coachmark.base.tsx b/packages/react/src/components/Coachmark/Coachmark.base.tsx index e349b247bc49ab..bd685fb6ebba3e 100644 --- a/packages/react/src/components/Coachmark/Coachmark.base.tsx +++ b/packages/react/src/components/Coachmark/Coachmark.base.tsx @@ -289,7 +289,7 @@ function useProximityHandlers( timeoutIds.push( setTimeout((): void => { setTargetElementRect(); - setBounds(getBounds(win, props.isPositionForced, props.positioningContainerProps)); + setBounds(getBounds(props.isPositionForced, props.positioningContainerProps, win)); }, 100), ); }); @@ -424,7 +424,7 @@ export const CoachmarkBase: React.FunctionComponent = React.for const [beakPositioningProps, transformOrigin] = useBeakPosition(props, targetAlignment, targetPosition); const [isMeasuring, entityInnerHostRect] = useEntityHostMeasurements(props, entityInnerHostElementRef); const [bounds, setBounds] = React.useState( - getBounds(win, props.isPositionForced, props.positioningContainerProps), + getBounds(props.isPositionForced, props.positioningContainerProps, win), ); const alertText = useAriaAlert(props); const entityHost = useAutoFocus(props); @@ -435,7 +435,7 @@ export const CoachmarkBase: React.FunctionComponent = React.for useDeprecationWarning(props); React.useEffect(() => { - setBounds(getBounds(win, props.isPositionForced, props.positioningContainerProps)); + setBounds(getBounds(props.isPositionForced, props.positioningContainerProps, win)); }, [props.isPositionForced, props.positioningContainerProps, win]); const { @@ -541,9 +541,9 @@ export const CoachmarkBase: React.FunctionComponent = React.for CoachmarkBase.displayName = COMPONENT_NAME; function getBounds( - win: Window, isPositionForced?: boolean, positioningContainerProps?: IPositioningContainerProps, + win?: Window, ): IRectangle | undefined { if (isPositionForced) { // If directionalHint direction is the top or bottom auto edge, then we want to set the left/right bounds @@ -557,8 +557,8 @@ function getBounds( left: 0, top: -Infinity, bottom: Infinity, - right: win.innerWidth, - width: win.innerWidth, + right: win?.innerWidth ?? 0, + width: win?.innerWidth ?? 0, height: Infinity, }; } else { diff --git a/packages/react/src/components/Coachmark/PositioningContainer/PositioningContainer.tsx b/packages/react/src/components/Coachmark/PositioningContainer/PositioningContainer.tsx index 54750747253754..759f6990affde2 100644 --- a/packages/react/src/components/Coachmark/PositioningContainer/PositioningContainer.tsx +++ b/packages/react/src/components/Coachmark/PositioningContainer/PositioningContainer.tsx @@ -93,7 +93,7 @@ function usePositionState( // or don't check anything else if the target is a Point or Rectangle if ( (!(target as Element).getBoundingClientRect && !(target as MouseEvent).preventDefault) || - doc.body.contains(target as Node) + doc?.body.contains(target as Node) ) { currentProps!.gapSpace = offsetFromTarget; const newPositions: IPositionedData = positionElement( diff --git a/packages/react/src/components/ColorPicker/ColorRectangle/ColorRectangle.base.tsx b/packages/react/src/components/ColorPicker/ColorRectangle/ColorRectangle.base.tsx index 07ccd0de8d5b60..d8ef8bbed0d9c0 100644 --- a/packages/react/src/components/ColorPicker/ColorRectangle/ColorRectangle.base.tsx +++ b/packages/react/src/components/ColorPicker/ColorRectangle/ColorRectangle.base.tsx @@ -186,7 +186,7 @@ export class ColorRectangleBase } private _onMouseDown = (ev: React.MouseEvent): void => { - const win = getWindowEx(this.context); + const win = getWindowEx(this.context)!; // Can only be called on the client this._disposables.push( on(win, 'mousemove', this._onMouseMove as (ev: MouseEvent) => void, true), on(win, 'mouseup', this._disposeListeners, true), diff --git a/packages/react/src/components/ComboBox/ComboBox.tsx b/packages/react/src/components/ComboBox/ComboBox.tsx index 1e608df5144e9d..8d2e4e15d63127 100644 --- a/packages/react/src/components/ComboBox/ComboBox.tsx +++ b/packages/react/src/components/ComboBox/ComboBox.tsx @@ -383,7 +383,7 @@ class ComboBoxInternal extends React.Component 0 && this.state.focusedItemIndex !== -1 && - !elementContains(this._root.current, doc.activeElement as HTMLElement, false) + !elementContains(this._root.current, doc?.activeElement as HTMLElement, false) ) { // Item set has changed and previously-focused item is gone. // Set focus to item at index of previously-focused item if it is in range, diff --git a/packages/react/src/components/DocumentCard/DocumentCard.base.tsx b/packages/react/src/components/DocumentCard/DocumentCard.base.tsx index 29fab300731206..78bbf79439ba1e 100644 --- a/packages/react/src/components/DocumentCard/DocumentCard.base.tsx +++ b/packages/react/src/components/DocumentCard/DocumentCard.base.tsx @@ -113,7 +113,7 @@ export class DocumentCardBase extends React.Component i private _onAction = (ev: React.SyntheticEvent): void => { const { onClick, onClickHref, onClickTarget } = this.props; - const win = getWindowEx(this.context); + const win = getWindowEx(this.context)!; // can only be called on the client if (onClick) { onClick(ev); diff --git a/packages/react/src/components/Dropdown/Dropdown.base.tsx b/packages/react/src/components/Dropdown/Dropdown.base.tsx index 32b419851a6965..27c4675521c1b7 100644 --- a/packages/react/src/components/Dropdown/Dropdown.base.tsx +++ b/packages/react/src/components/Dropdown/Dropdown.base.tsx @@ -941,7 +941,7 @@ class DropdownInternal extends React.Component { - const win = getWindowEx(this.context); + const win = getWindowEx(this.context)!; // can only be called on the client if (!this._isScrollIdle && this._scrollIdleTimeoutId !== undefined) { win.clearTimeout(this._scrollIdleTimeoutId); this._scrollIdleTimeoutId = undefined; @@ -964,7 +964,7 @@ class DropdownInternal extends React.Component): void { - const doc = getDocumentEx(this.context); + const doc = getDocumentEx(this.context)!; // can only be called on the client const targetElement = ev.currentTarget as HTMLElement; this._gotMouseMove = true; diff --git a/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx b/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx index db9e9693fb0003..668fa0caaae79c 100644 --- a/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx +++ b/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx @@ -265,17 +265,17 @@ export const FocusTrapZone: React.FunctionComponent & { const disposables: Array<() => void> = []; if (forceFocusInsideTrap) { - disposables.push(on(win, 'focus', forceFocusOrClickInTrap, true)); + disposables.push(on(win!, 'focus', forceFocusOrClickInTrap, true)); } if (!isClickableOutsideFocusTrap) { - disposables.push(on(win, 'click', forceFocusOrClickInTrap, true)); + disposables.push(on(win!, 'click', forceFocusOrClickInTrap, true)); } return () => { disposables.forEach(dispose => dispose()); }; // eslint-disable-next-line react-hooks/exhaustive-deps -- should only run when these two props change - }, [forceFocusInsideTrap, isClickableOutsideFocusTrap]); + }, [forceFocusInsideTrap, isClickableOutsideFocusTrap, win]); // On prop change or first render, focus the FTZ and update focusStack if appropriate React.useEffect(() => { diff --git a/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx b/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx index fc0e94b05aecbb..aa3ee9e9d7d75b 100644 --- a/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx +++ b/packages/react/src/components/KeytipLayer/KeytipLayer.base.tsx @@ -392,7 +392,7 @@ export class KeytipLayerBase extends React.Component implements IN // resolve is not supported for ssr return false; } else { - const doc = getDocumentEx(this.context); + const doc = getDocumentEx(this.context)!; // there is an SSR check above so this is safe // If selectedKey is undefined in props and state, then check URL _urlResolver = _urlResolver || doc.createElement('a'); diff --git a/packages/react/src/components/OverflowSet/OverflowSet.base.tsx b/packages/react/src/components/OverflowSet/OverflowSet.base.tsx index 91efe6a63e14d0..fb94c8fafac49c 100644 --- a/packages/react/src/components/OverflowSet/OverflowSet.base.tsx +++ b/packages/react/src/components/OverflowSet/OverflowSet.base.tsx @@ -28,7 +28,7 @@ const useComponentRef = (props: IOverflowSetProps, divContainer: React.RefObject } if (divContainer.current && elementContains(divContainer.current, childElement)) { childElement.focus(); - focusSucceeded = doc.activeElement === childElement; + focusSucceeded = doc?.activeElement === childElement; } return focusSucceeded; }, diff --git a/packages/react/src/components/Panel/Panel.base.tsx b/packages/react/src/components/Panel/Panel.base.tsx index a6b0ed35b6a29a..329e90467153e8 100644 --- a/packages/react/src/components/Panel/Panel.base.tsx +++ b/packages/react/src/components/Panel/Panel.base.tsx @@ -117,7 +117,7 @@ export class PanelBase extends React.Component impleme this._events.on(win, 'resize', this._updateFooterPosition); if (this._shouldListenForOuterClick(this.props)) { - this._events.on(doc.body, 'mousedown', this._dismissOnOuterClick, true); + this._events.on(doc?.body, 'mousedown', this._dismissOnOuterClick, true); } if (this.props.isOpen) { @@ -140,9 +140,9 @@ export class PanelBase extends React.Component impleme const doc = getDocumentEx(this.context); if (shouldListenOnOuterClick && !previousShouldListenOnOuterClick) { - this._events.on(doc.body, 'mousedown', this._dismissOnOuterClick, true); + this._events.on(doc?.body, 'mousedown', this._dismissOnOuterClick, true); } else if (!shouldListenOnOuterClick && previousShouldListenOnOuterClick) { - this._events.off(doc.body, 'mousedown', this._dismissOnOuterClick, true); + this._events.off(doc?.body, 'mousedown', this._dismissOnOuterClick, true); } } diff --git a/packages/react/src/components/ScrollablePane/ScrollablePane.base.tsx b/packages/react/src/components/ScrollablePane/ScrollablePane.base.tsx index 9a3a0450db491f..e110b0ec164507 100644 --- a/packages/react/src/components/ScrollablePane/ScrollablePane.base.tsx +++ b/packages/react/src/components/ScrollablePane/ScrollablePane.base.tsx @@ -97,7 +97,7 @@ export class ScrollablePaneBase }); this.notifySubscribers(); - if ('MutationObserver' in win) { + if (win && 'MutationObserver' in win) { this._mutationObserver = new MutationObserver(mutation => { // Function to check if mutation is occuring in stickyAbove or stickyBelow function checkIfMutationIsSticky(mutationRecord: MutationRecord): boolean { diff --git a/packages/react/src/components/SelectedItemsList/BaseSelectedItemsList.tsx b/packages/react/src/components/SelectedItemsList/BaseSelectedItemsList.tsx index ec4ceee549c750..73d8e44fb32c4f 100644 --- a/packages/react/src/components/SelectedItemsList/BaseSelectedItemsList.tsx +++ b/packages/react/src/components/SelectedItemsList/BaseSelectedItemsList.tsx @@ -217,7 +217,7 @@ export class BaseSelectedItemsList> if (this.props.onCopyItems) { const copyText = (this.props.onCopyItems as any)(items); - const doc = getDocumentEx(this.context); + const doc = getDocumentEx(this.context)!; // equivalent to previous behavior of directly using `document` const copyInput = doc.createElement('input') as HTMLInputElement; doc.body.appendChild(copyInput); diff --git a/packages/react/src/components/Slider/useSlider.ts b/packages/react/src/components/Slider/useSlider.ts index 4f2c0684845694..bdb30715087204 100644 --- a/packages/react/src/components/Slider/useSlider.ts +++ b/packages/react/src/components/Slider/useSlider.ts @@ -305,15 +305,16 @@ export const useSlider = (props: ISliderProps, ref: React.ForwardedRef void, true), - on(win, 'mouseup', onMouseUpOrTouchEnd, true), + on(win!, 'mousemove', onMouseMoveOrTouchMove as (ev: Event) => void, true), + on(win!, 'mouseup', onMouseUpOrTouchEnd, true), ); } else if (event.type === 'touchstart') { disposables.current.push( - on(win, 'touchmove', onMouseMoveOrTouchMove as (ev: Event) => void, true), - on(win, 'touchend', onMouseUpOrTouchEnd, true), + on(win!, 'touchmove', onMouseMoveOrTouchMove as (ev: Event) => void, true), + on(win!, 'touchend', onMouseUpOrTouchEnd, true), ); } onMouseMoveOrTouchMove(event, true); diff --git a/packages/react/src/components/SwatchColorPicker/SwatchColorPicker.base.tsx b/packages/react/src/components/SwatchColorPicker/SwatchColorPicker.base.tsx index 10ba7e2e2aa635..d2aaaa6ba45532 100644 --- a/packages/react/src/components/SwatchColorPicker/SwatchColorPicker.base.tsx +++ b/packages/react/src/components/SwatchColorPicker/SwatchColorPicker.base.tsx @@ -184,7 +184,7 @@ export const SwatchColorPickerBase: React.FunctionComponent> } else { this.setState({ suggestionsVisible: - this.input.current! && this.input.current!.inputElement === getDocumentEx(this.context).activeElement, + this.input.current! && this.input.current!.inputElement === getDocumentEx(this.context)?.activeElement, }); } @@ -603,7 +603,7 @@ export class BasePicker> // even when it's not. Using document.activeElement is another way // for us to be able to get what the relatedTarget without relying // on the event - relatedTarget = getDocumentEx(this.context).activeElement; + relatedTarget = getDocumentEx(this.context)!.activeElement; } if (relatedTarget && !elementContains(this.root.current!, relatedTarget as HTMLElement)) { this.setState({ isFocused: false }); @@ -1036,7 +1036,7 @@ export class BasePicker> const areSuggestionsVisible = this.input.current !== undefined && this.input.current !== null && - this.input.current.inputElement === getDocumentEx(this.context).activeElement && + this.input.current.inputElement === getDocumentEx(this.context)?.activeElement && this.input.current.value !== ''; return areSuggestionsVisible; diff --git a/packages/react/src/utilities/DraggableZone/DraggableZone.tsx b/packages/react/src/utilities/DraggableZone/DraggableZone.tsx index bca483a0d55ccd..7aefa561449cb2 100644 --- a/packages/react/src/utilities/DraggableZone/DraggableZone.tsx +++ b/packages/react/src/utilities/DraggableZone/DraggableZone.tsx @@ -159,8 +159,8 @@ export class DraggableZone extends React.Component { // eslint-disable-next-line no-restricted-globals - return useDocument() ?? document; + return useDocument() ?? typeof document !== 'undefined' ? document : undefined; }; /** * Get a reference to the `window` object. * Use this in place of the global `window` in React function components. - * @returns Window + * @returns Window | undefined */ export const useWindowEx = () => { // eslint-disable-next-line no-restricted-globals - return useWindow() ?? window; + return useWindow() ?? typeof window !== 'undefined' ? window : undefined; }; /** @@ -25,11 +35,11 @@ export const useWindowEx = () => { * Use this in place of the global `document` in React class components. * * @param ctx - Class component WindowContext - * @returns Document + * @returns Document | undefined */ export const getDocumentEx = (ctx: Pick | undefined) => { // eslint-disable-next-line no-restricted-globals - return ctx?.window?.document ?? document; + return ctx?.window?.document ?? typeof document !== 'undefined' ? document : undefined; }; /** @@ -37,9 +47,9 @@ export const getDocumentEx = (ctx: Pick | undefin * Use this in place of the global `window` in React class components. * * @param ctx - Class component WindowContext - * @returns Window + * @returns Window | undefined */ export const getWindowEx = (ctx: Pick | undefined) => { // eslint-disable-next-line no-restricted-globals - return ctx?.window ?? window; + return ctx?.window ?? typeof window !== 'undefined' ? window : undefined; }; From fe4085fd6be5cc1bc2618d8e857b6a71fad8cb55 Mon Sep 17 00:00:00 2001 From: Sean Monahan Date: Tue, 31 Oct 2023 01:55:48 +0000 Subject: [PATCH 44/44] change files --- ...lendar-compat-c72f4681-3f40-49bc-9184-195307bcda06.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-calendar-compat-c72f4681-3f40-49bc-9184-195307bcda06.json diff --git a/change/@fluentui-react-calendar-compat-c72f4681-3f40-49bc-9184-195307bcda06.json b/change/@fluentui-react-calendar-compat-c72f4681-3f40-49bc-9184-195307bcda06.json new file mode 100644 index 00000000000000..b7ce9c8ebf8843 --- /dev/null +++ b/change/@fluentui-react-calendar-compat-c72f4681-3f40-49bc-9184-195307bcda06.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: use globals from context", + "packageName": "@fluentui/react-calendar-compat", + "email": "seanmonahan@microsoft.com", + "dependentChangeType": "patch" +}