Disallow `window` and `document` access for `@fluentui/react` and related packages. (#30063)
This commit is contained in:
Родитель
c43a582b1e
Коммит
5858fe0525
|
@ -2,6 +2,7 @@
|
|||
"extends": ["plugin:@fluentui/eslint-plugin/react"],
|
||||
"root": true,
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
"no-console": "off",
|
||||
"no-restricted-globals": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
"curly": "off",
|
||||
"no-var": "off",
|
||||
"vars-on-top": "off",
|
||||
"prefer-arrow-callback": "off"
|
||||
"prefer-arrow-callback": "off",
|
||||
"no-restricted-globals": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"deprecation/deprecation": "off",
|
||||
"import/no-webpack-loader-syntax": "off", // ok in this project
|
||||
"prefer-const": "off",
|
||||
"react/jsx-no-bind": "off"
|
||||
"react/jsx-no-bind": "off",
|
||||
"no-restricted-globals": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"deprecation/deprecation": "off",
|
||||
"prefer-const": "off"
|
||||
"prefer-const": "off",
|
||||
"no-restricted-globals": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "patch",
|
||||
"comment": "chore: disallow document and window access",
|
||||
"packageName": "@fluentui/dom-utilities",
|
||||
"email": "seanmonahan@microsoft.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "patch",
|
||||
"comment": "chore: add restricted globals for @fluentui/react and related packages",
|
||||
"packageName": "@fluentui/eslint-plugin",
|
||||
"email": "seanmonahan@microsoft.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "patch",
|
||||
"comment": "chore: disallow document and window access",
|
||||
"packageName": "@fluentui/merge-styles",
|
||||
"email": "seanmonahan@microsoft.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "patch",
|
||||
"comment": "chore: disallow document and window access",
|
||||
"packageName": "@fluentui/react",
|
||||
"email": "seanmonahan@microsoft.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "patch",
|
||||
"comment": "chore: disallow document and window access",
|
||||
"packageName": "@fluentui/react-file-type-icons",
|
||||
"email": "seanmonahan@microsoft.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "patch",
|
||||
"comment": "chore: disallow document and window access",
|
||||
"packageName": "@fluentui/react-focus",
|
||||
"email": "seanmonahan@microsoft.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "patch",
|
||||
"comment": "chore: clean up exports",
|
||||
"packageName": "@fluentui/react-window-provider",
|
||||
"email": "seanmonahan@microsoft.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "patch",
|
||||
"comment": "chore: disallow document and window access",
|
||||
"packageName": "@fluentui/utilities",
|
||||
"email": "seanmonahan@microsoft.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -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)
|
||||
|
||||
```
|
||||
|
|
|
@ -6,8 +6,12 @@ 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, doc?: Document): string | null {
|
||||
const elementMatch = findElementRecursive(
|
||||
element,
|
||||
(testElement: HTMLElement) => testElement.hasAttribute(attribute),
|
||||
doc,
|
||||
);
|
||||
|
||||
return elementMatch && elementMatch.getAttribute(attribute);
|
||||
}
|
||||
|
|
|
@ -8,8 +8,11 @@ import { getParent } from './getParent';
|
|||
export function findElementRecursive(
|
||||
element: HTMLElement | null,
|
||||
matchFunction: (element: HTMLElement) => boolean,
|
||||
doc?: Document,
|
||||
): HTMLElement | null {
|
||||
if (!element || element === document.body) {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
doc ??= document;
|
||||
if (!element || element === doc.body) {
|
||||
return null;
|
||||
}
|
||||
return matchFunction(element) ? element : findElementRecursive(getParent(element), matchFunction);
|
||||
|
|
|
@ -9,10 +9,11 @@ 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, doc?: Document): boolean {
|
||||
const elementMatch = findElementRecursive(
|
||||
target,
|
||||
(testElement: HTMLElement) => parent === testElement || testElement.hasAttribute(DATA_PORTAL_ATTRIBUTE),
|
||||
doc,
|
||||
);
|
||||
return elementMatch !== null && elementMatch.hasAttribute(DATA_PORTAL_ATTRIBUTE);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @ts-check
|
||||
|
||||
const configHelpers = require('../utils/configHelpers');
|
||||
const path = require('path');
|
||||
const { reactLegacy: restrictedGlobals } = require('./restricted-globals');
|
||||
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
|
@ -9,7 +10,16 @@ module.exports = {
|
|||
rules: {
|
||||
'jsdoc/check-tag-names': 'off',
|
||||
'@griffel/no-shorthands': 'off',
|
||||
'no-restricted-globals': 'off',
|
||||
'no-restricted-globals': restrictedGlobals,
|
||||
},
|
||||
overrides: [],
|
||||
overrides: [
|
||||
{
|
||||
// Test overrides
|
||||
files: [...configHelpers.testFiles, '**/*.stories.tsx'],
|
||||
rules: {
|
||||
'no-restricted-globals': 'off',
|
||||
'react/jsx-no-bind': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -239,6 +239,18 @@
|
|||
// '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',
|
||||
{
|
||||
|
@ -258,5 +270,6 @@ const react = [
|
|||
];
|
||||
|
||||
module.exports = {
|
||||
reactLegacy,
|
||||
react,
|
||||
};
|
||||
|
|
|
@ -15,8 +15,11 @@ export function setRTL(isRTL: boolean): void {
|
|||
export function getRTL(): boolean {
|
||||
if (_rtl === undefined) {
|
||||
_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;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/* eslint no-restricted-globals: 0 */
|
||||
// globals in stylesheets will be addressed as part of shadow DOM work.
|
||||
// See: https://github.com/microsoft/fluentui/issues/28058
|
||||
import { IStyle } from './IStyle';
|
||||
|
||||
export const InjectionMode = {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"extends": ["plugin:@fluentui/eslint-plugin/react--legacy"],
|
||||
"root": true
|
||||
"root": true,
|
||||
"rules": {
|
||||
"no-restricted-globals": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"extends": ["plugin:@fluentui/eslint-plugin/react--legacy"],
|
||||
"root": true,
|
||||
"rules": {
|
||||
"deprecation/deprecation": "off"
|
||||
"deprecation/deprecation": "off",
|
||||
"no-restricted-globals": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,8 +160,14 @@ 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',
|
||||
win?: Window,
|
||||
): string {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
win ??= window;
|
||||
let devicePixelRatio: number = win.devicePixelRatio;
|
||||
let devicePixelRatioSuffix = ''; // Default is 1x
|
||||
|
||||
// SVGs scale well, so you can generally use the default image.
|
||||
|
|
|
@ -59,12 +59,14 @@ function raiseClickFromKeyboardEvent(target: Element, ev?: React.KeyboardEvent<H
|
|||
cancelable: ev?.cancelable,
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
event = document.createEvent('MouseEvents');
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
event.initMouseEvent(
|
||||
'click',
|
||||
ev ? ev.bubbles : false,
|
||||
ev ? ev.cancelable : false,
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
window, // not using getWindow() since this can only be run client side
|
||||
0, // detail
|
||||
0, // screen x
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
export * from './WindowProvider';
|
||||
export {
|
||||
// eslint-disable-next-line @fluentui/ban-context-export
|
||||
WindowContext,
|
||||
useWindow,
|
||||
useDocument,
|
||||
WindowProvider,
|
||||
} from './WindowProvider';
|
||||
export type { WindowProviderProps } from './WindowProvider';
|
||||
|
||||
import './version';
|
||||
|
|
|
@ -691,6 +691,8 @@ export class BasePicker<T, P extends IBasePickerProps<T>> extends React_2.Compon
|
|||
// (undocumented)
|
||||
componentWillUnmount(): void;
|
||||
// (undocumented)
|
||||
static contextType: React_2.Context<WindowProviderProps>;
|
||||
// (undocumented)
|
||||
protected currentPromise: PromiseLike<any> | undefined;
|
||||
// (undocumented)
|
||||
dismissSuggestions: (ev?: any) => void;
|
||||
|
@ -797,6 +799,8 @@ export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>
|
|||
// (undocumented)
|
||||
componentDidUpdate(oldProps: P, oldState: IBaseSelectedItemsListState<IObjectWithKey>): void;
|
||||
// (undocumented)
|
||||
static contextType: React_2.Context<WindowProviderProps>;
|
||||
// (undocumented)
|
||||
protected copyItems(items: T[]): void;
|
||||
// (undocumented)
|
||||
static getDerivedStateFromProps(newProps: IBaseSelectedItemsListProps<any>): {
|
||||
|
@ -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<IDetailsListProps, IDetai
|
|||
// (undocumented)
|
||||
componentWillUnmount(): void;
|
||||
// (undocumented)
|
||||
static contextType: React_2.Context<WindowProviderProps>;
|
||||
// (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: () => {
|
||||
|
@ -8126,6 +8132,8 @@ export interface IScrollablePaneContext {
|
|||
notifySubscribers: (sort?: boolean) => void;
|
||||
syncScrollSticky: (sticky: Sticky) => void;
|
||||
};
|
||||
// (undocumented)
|
||||
window: Window | undefined;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -9772,6 +9780,8 @@ export class KeytipLayerBase extends React_2.Component<IKeytipLayerProps, IKeyti
|
|||
// (undocumented)
|
||||
componentWillUnmount(): void;
|
||||
// (undocumented)
|
||||
static contextType: React_2.Context<WindowProviderProps>;
|
||||
// (undocumented)
|
||||
static defaultProps: IKeytipLayerProps;
|
||||
// (undocumented)
|
||||
getCurrentSequence(): string;
|
||||
|
@ -9880,6 +9890,8 @@ export class List<T = any> extends React_2.Component<IListProps<T>, IListState<T
|
|||
// (undocumented)
|
||||
componentWillUnmount(): void;
|
||||
// (undocumented)
|
||||
static contextType: React_2.Context<WindowProviderProps>;
|
||||
// (undocumented)
|
||||
static defaultProps: {
|
||||
startIndex: number;
|
||||
onRenderCell: (item: any, index: number, containsFocus: boolean) => JSX.Element;
|
||||
|
@ -10043,6 +10055,8 @@ export const Nav: React_2.FunctionComponent<INavProps>;
|
|||
export class NavBase extends React_2.Component<INavProps, INavState> implements INav {
|
||||
constructor(props: INavProps);
|
||||
// (undocumented)
|
||||
static contextType: React_2.Context<WindowProviderProps>;
|
||||
// (undocumented)
|
||||
static defaultProps: INavProps;
|
||||
focus(forceIntoFirstElement?: boolean): boolean;
|
||||
// (undocumented)
|
||||
|
@ -10135,6 +10149,8 @@ export class PanelBase extends React_2.Component<IPanelProps, IPanelState> imple
|
|||
// (undocumented)
|
||||
componentWillUnmount(): void;
|
||||
// (undocumented)
|
||||
static contextType: React_2.Context<WindowProviderProps>;
|
||||
// (undocumented)
|
||||
static defaultProps: IPanelProps;
|
||||
// (undocumented)
|
||||
dismiss: (ev?: React_2.SyntheticEvent<HTMLElement> | KeyboardEvent) => void;
|
||||
|
@ -10398,13 +10414,13 @@ export enum Position {
|
|||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function positionCallout(props: IPositionProps, hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: ICalloutPositionedInfo, shouldScroll?: boolean, minimumScrollResizeHeight?: number): ICalloutPositionedInfo;
|
||||
export function positionCallout(props: IPositionProps, hostElement: HTMLElement, elementToPosition: HTMLElement, previousPositions?: ICalloutPositionedInfo, shouldScroll?: boolean, minimumScrollResizeHeight?: number, 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<IPositioningContainerProps>;
|
||||
|
@ -10585,6 +10601,8 @@ export class ScrollablePaneBase extends React_2.Component<IScrollablePaneProps,
|
|||
// (undocumented)
|
||||
get contentContainer(): HTMLDivElement | null;
|
||||
// (undocumented)
|
||||
static contextType: React_2.Context<WindowProviderProps>;
|
||||
// (undocumented)
|
||||
forceLayoutUpdate(): void;
|
||||
// (undocumented)
|
||||
getScrollPosition: () => number;
|
||||
|
@ -11351,6 +11369,8 @@ export class TooltipHostBase extends React_2.Component<ITooltipHostProps, IToolt
|
|||
// (undocumented)
|
||||
componentWillUnmount(): void;
|
||||
// (undocumented)
|
||||
static contextType: React_2.Context<WindowProviderProps>;
|
||||
// (undocumented)
|
||||
static defaultProps: {
|
||||
delay: TooltipDelay;
|
||||
};
|
||||
|
|
|
@ -21,7 +21,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 { useWindow } from '@fluentui/react-window-provider';
|
||||
import { useWindowEx } from '../../utilities/dom';
|
||||
|
||||
const COMPONENT_NAME = 'CalloutContentBase';
|
||||
|
||||
|
@ -208,7 +208,7 @@ function usePositions(
|
|||
preferScrollResizePositioning,
|
||||
} = props;
|
||||
|
||||
const win = useWindow();
|
||||
const win = useWindowEx();
|
||||
const localRef = React.useRef<HTMLDivElement | null>();
|
||||
let popupStyles: CSSStyleDeclaration | undefined;
|
||||
if (localRef.current !== popupRef.current) {
|
||||
|
@ -243,8 +243,16 @@ 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, shouldScroll);
|
||||
? positionCard(currentProps, hostElement.current, dupeCalloutElement, previousPositions, win)
|
||||
: positionCallout(
|
||||
currentProps,
|
||||
hostElement.current,
|
||||
dupeCalloutElement,
|
||||
previousPositions,
|
||||
shouldScroll,
|
||||
undefined,
|
||||
win,
|
||||
);
|
||||
|
||||
// clean up duplicate calloutElement
|
||||
calloutElement.parentElement?.removeChild(dupeCalloutElement);
|
||||
|
@ -295,6 +303,7 @@ function usePositions(
|
|||
hideOverflow,
|
||||
preferScrollResizePositioning,
|
||||
popupOverflowY,
|
||||
win,
|
||||
]);
|
||||
|
||||
return positions;
|
||||
|
|
|
@ -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<IChoiceGroupStyleProps, IChoiceGroupStyles>();
|
||||
|
||||
|
@ -36,9 +37,10 @@ const focusSelectedOption = (
|
|||
keyChecked: IChoiceGroupProps['selectedKey'],
|
||||
id: string,
|
||||
focusProviders?: React.RefObject<HTMLElement>[],
|
||||
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,7 @@ const useComponentRef = (
|
|||
componentRef?: IRefObject<IChoiceGroup>,
|
||||
focusProviders?: React.RefObject<HTMLElement>[],
|
||||
) => {
|
||||
const doc = useDocumentEx();
|
||||
React.useImperativeHandle(
|
||||
componentRef,
|
||||
() => ({
|
||||
|
@ -64,10 +67,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],
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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<ICoachmarkStyleProps, ICoachmarkStyles>();
|
||||
|
||||
|
@ -253,6 +254,8 @@ function useProximityHandlers(
|
|||
|
||||
/** The target element the mouse would be in proximity to */
|
||||
const targetElementRect = React.useRef<DOMRect>();
|
||||
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(props.isPositionForced, props.positioningContainerProps, win));
|
||||
}, 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<ICoachmarkProps> = React.for
|
|||
>((propsWithoutDefaults, forwardedRef) => {
|
||||
const props = getPropsWithDefaults(DEFAULT_PROPS, propsWithoutDefaults);
|
||||
|
||||
const win = useWindowEx();
|
||||
const entityInnerHostElementRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const translateAnimationContainer = React.useRef<HTMLDivElement | null>(null);
|
||||
|
||||
|
@ -420,7 +424,7 @@ export const CoachmarkBase: React.FunctionComponent<ICoachmarkProps> = React.for
|
|||
const [beakPositioningProps, transformOrigin] = useBeakPosition(props, targetAlignment, targetPosition);
|
||||
const [isMeasuring, entityInnerHostRect] = useEntityHostMeasurements(props, entityInnerHostElementRef);
|
||||
const [bounds, setBounds] = React.useState<IRectangle | undefined>(
|
||||
getBounds(props.isPositionForced, props.positioningContainerProps),
|
||||
getBounds(props.isPositionForced, props.positioningContainerProps, win),
|
||||
);
|
||||
const alertText = useAriaAlert(props);
|
||||
const entityHost = useAutoFocus(props);
|
||||
|
@ -431,8 +435,8 @@ export const CoachmarkBase: React.FunctionComponent<ICoachmarkProps> = React.for
|
|||
useDeprecationWarning(props);
|
||||
|
||||
React.useEffect(() => {
|
||||
setBounds(getBounds(props.isPositionForced, props.positioningContainerProps));
|
||||
}, [props.isPositionForced, props.positioningContainerProps]);
|
||||
setBounds(getBounds(props.isPositionForced, props.positioningContainerProps, win));
|
||||
}, [props.isPositionForced, props.positioningContainerProps, win]);
|
||||
|
||||
const {
|
||||
beaconColorOne,
|
||||
|
@ -539,6 +543,7 @@ CoachmarkBase.displayName = COMPONENT_NAME;
|
|||
function getBounds(
|
||||
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
|
||||
|
@ -552,8 +557,8 @@ function getBounds(
|
|||
left: 0,
|
||||
top: -Infinity,
|
||||
bottom: Infinity,
|
||||
right: window.innerWidth,
|
||||
width: window.innerWidth,
|
||||
right: win?.innerWidth ?? 0,
|
||||
width: win?.innerWidth ?? 0,
|
||||
height: Infinity,
|
||||
};
|
||||
} else {
|
||||
|
|
|
@ -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<number | undefined>();
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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<IColorRectangleProps, IColorRectangleState>
|
||||
implements IColorRectangle
|
||||
{
|
||||
public static contextType = WindowContext;
|
||||
public static defaultProps: Partial<IColorRectangleProps> = {
|
||||
minSize: 220,
|
||||
ariaLabel: 'Saturation and brightness',
|
||||
|
@ -183,9 +186,10 @@ export class ColorRectangleBase
|
|||
}
|
||||
|
||||
private _onMouseDown = (ev: React.MouseEvent): void => {
|
||||
const win = getWindowEx(this.context)!; // Can only be called on the client
|
||||
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);
|
||||
|
|
|
@ -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<IComboBoxInternalProps, IComboBoxState> implements IComboBox {
|
||||
public static contextType = WindowContext;
|
||||
|
||||
/** The input aspect of the combo box */
|
||||
private _autofill = React.createRef<IAutofill>();
|
||||
|
||||
|
@ -368,6 +372,7 @@ class ComboBoxInternal extends React.Component<IComboBoxInternalProps, IComboBox
|
|||
this._async.setTimeout(() => 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<IComboBoxInternalProps, IComboBox
|
|||
!isOpen &&
|
||||
this._focusInputAfterClose &&
|
||||
this._autofill.current &&
|
||||
document.activeElement !== this._autofill.current.inputElement))
|
||||
doc?.activeElement !== this._autofill.current.inputElement))
|
||||
) {
|
||||
this.focus(undefined /*shouldOpenOnFocus*/, true /*useFocusAsync*/);
|
||||
}
|
||||
|
@ -1221,6 +1226,7 @@ class ComboBoxInternal extends React.Component<IComboBoxInternalProps, IComboBox
|
|||
*/
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
private _onBlur = (event: React.FocusEvent<HTMLElement | Autofill | BaseButton | Button>): 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
|
||||
|
@ -1231,7 +1237,7 @@ class ComboBoxInternal extends React.Component<IComboBoxInternalProps, IComboBox
|
|||
// 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 as Element;
|
||||
relatedTarget = doc?.activeElement as Element;
|
||||
}
|
||||
|
||||
if (relatedTarget) {
|
||||
|
@ -1239,7 +1245,7 @@ class ComboBoxInternal extends React.Component<IComboBoxInternalProps, IComboBox
|
|||
const isBlurFromComboBoxMenu = this._comboBoxMenu.current?.contains(relatedTarget as HTMLElement);
|
||||
const isBlurFromComboBoxMenuAncestor =
|
||||
this._comboBoxMenu.current &&
|
||||
findElementRecursive(this._comboBoxMenu.current, (element: HTMLElement) => element === relatedTarget);
|
||||
findElementRecursive(this._comboBoxMenu.current, (element: HTMLElement) => element === relatedTarget, doc);
|
||||
|
||||
if (isBlurFromComboBoxTitle || isBlurFromComboBoxMenu || isBlurFromComboBoxMenuAncestor) {
|
||||
if (
|
||||
|
|
|
@ -57,6 +57,8 @@ import type { IFocusZone, IFocusZoneProps } from '../../FocusZone';
|
|||
import type { IObjectWithKey, ISelection } from '../../Selection';
|
||||
import type { IGroupedList, IGroupDividerProps, IGroupRenderProps, IGroup } from '../../GroupedList';
|
||||
import type { IListProps } from '../../List';
|
||||
import { WindowContext } from '@fluentui/react-window-provider';
|
||||
import { getDocumentEx } from '../../utilities/dom';
|
||||
|
||||
const getClassNames = classNamesFunction<IDetailsListStyleProps, IDetailsListStyles>();
|
||||
const COMPONENT_NAME = 'DetailsList';
|
||||
|
@ -779,6 +781,8 @@ export class DetailsListBase extends React.Component<IDetailsListProps, IDetails
|
|||
useFastIcons: true,
|
||||
};
|
||||
|
||||
public static contextType = WindowContext;
|
||||
|
||||
// References
|
||||
private _async: Async;
|
||||
private _root = React.createRef<HTMLDivElement>();
|
||||
|
@ -934,6 +938,8 @@ export class DetailsListBase extends React.Component<IDetailsListProps, IDetails
|
|||
public componentDidUpdate(prevProps: IDetailsListProps, prevState: IDetailsListState) {
|
||||
this._notifyColumnsResized();
|
||||
|
||||
const doc = getDocumentEx(this.context);
|
||||
|
||||
if (this._initialFocusedIndex !== undefined) {
|
||||
const item = this.props.items[this._initialFocusedIndex];
|
||||
if (item) {
|
||||
|
@ -949,7 +955,7 @@ export class DetailsListBase extends React.Component<IDetailsListProps, IDetails
|
|||
this.props.items !== prevProps.items &&
|
||||
this.props.items.length > 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,
|
||||
|
|
|
@ -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<IDocumentCardStyleProps, IDocumentCardStyles>();
|
||||
|
||||
|
@ -31,6 +33,8 @@ export class DocumentCardBase extends React.Component<IDocumentCardProps, any> i
|
|||
type: DocumentCardType.normal,
|
||||
};
|
||||
|
||||
public static contextType = WindowContext;
|
||||
|
||||
private _rootElement = React.createRef<HTMLDivElement>();
|
||||
private _classNames: IProcessedStyleSet<IDocumentCardStyles>;
|
||||
|
||||
|
@ -109,14 +113,16 @@ export class DocumentCardBase extends React.Component<IDocumentCardProps, any> i
|
|||
private _onAction = (ev: React.SyntheticEvent<HTMLElement>): void => {
|
||||
const { onClick, onClickHref, onClickTarget } = this.props;
|
||||
|
||||
const win = getWindowEx(this.context)!; // can only be called on the client
|
||||
|
||||
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();
|
||||
|
|
|
@ -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<IDocumentCardTitleStyleProps, IDocumentCardTitleStyles>();
|
||||
|
||||
|
@ -23,6 +25,8 @@ const TRUNCATION_VERTICAL_OVERFLOW_THRESHOLD = 5;
|
|||
* {@docCategory DocumentCard}
|
||||
*/
|
||||
export class DocumentCardTitleBase extends React.Component<IDocumentCardTitleProps, IDocumentCardTitleState> {
|
||||
public static contextType = WindowContext;
|
||||
|
||||
private _titleElement = React.createRef<HTMLDivElement>();
|
||||
private _classNames: IProcessedStyleSet<IDocumentCardTitleStyles>;
|
||||
private _async: Async;
|
||||
|
@ -53,12 +57,13 @@ export class DocumentCardTitleBase extends React.Component<IDocumentCardTitlePro
|
|||
}
|
||||
|
||||
if (prevProps.shouldTruncate !== this.props.shouldTruncate) {
|
||||
const win = getWindowEx(this.context);
|
||||
if (this.props.shouldTruncate) {
|
||||
this._truncateTitle();
|
||||
this._async.requestAnimationFrame(this._shrinkTitle);
|
||||
this._events.on(window, 'resize', this._updateTruncation);
|
||||
this._events.on(win, 'resize', this._updateTruncation);
|
||||
} else {
|
||||
this._events.off(window, 'resize', this._updateTruncation);
|
||||
this._events.off(win, 'resize', this._updateTruncation);
|
||||
}
|
||||
} else if (this._needMeasurement) {
|
||||
this._async.requestAnimationFrame(() => {
|
||||
|
@ -71,7 +76,8 @@ export class DocumentCardTitleBase extends React.Component<IDocumentCardTitlePro
|
|||
public componentDidMount(): void {
|
||||
if (this.props.shouldTruncate) {
|
||||
this._truncateTitle();
|
||||
this._events.on(window, 'resize', this._updateTruncation);
|
||||
const win = getWindowEx(this.context);
|
||||
this._events.on(win, 'resize', this._updateTruncation);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,8 @@ import type { IWithResponsiveModeState } from '../../ResponsiveMode';
|
|||
import type { ISelectableDroppableTextProps } from '../../SelectableOption';
|
||||
import type { ICheckboxStyleProps, ICheckboxStyles } from '../../Checkbox';
|
||||
import { IFocusTrapZoneProps } from '../FocusTrapZone/FocusTrapZone.types';
|
||||
import { WindowContext } from '@fluentui/react-window-provider';
|
||||
import { getDocumentEx, getWindowEx } from '../../utilities/dom';
|
||||
|
||||
const COMPONENT_NAME = 'Dropdown';
|
||||
const getClassNames = classNamesFunction<IDropdownStyleProps, IDropdownStyles>();
|
||||
|
@ -187,6 +189,8 @@ class DropdownInternal extends React.Component<IDropdownInternalProps, IDropdown
|
|||
options: [] as IDropdownOption[],
|
||||
};
|
||||
|
||||
public static contextType = WindowContext;
|
||||
|
||||
private _host = React.createRef<HTMLDivElement>();
|
||||
private _focusZone = React.createRef<FocusZone>();
|
||||
private _dropDown = React.createRef<HTMLDivElement>();
|
||||
|
@ -937,14 +941,15 @@ class DropdownInternal extends React.Component<IDropdownInternalProps, IDropdown
|
|||
* for updating focus are not interacting during scroll
|
||||
*/
|
||||
private _onScroll = (): void => {
|
||||
const win = getWindowEx(this.context)!; // can only be called on the client
|
||||
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<IDropdownInternalProps, IDropdown
|
|||
}
|
||||
|
||||
private _onItemMouseMove(item: any, ev: React.MouseEvent<HTMLElement>): void {
|
||||
const doc = getDocumentEx(this.context)!; // can only be called on the client
|
||||
const targetElement = ev.currentTarget as HTMLElement;
|
||||
this._gotMouseMove = true;
|
||||
|
||||
if (!this._isScrollIdle || document.activeElement === targetElement) {
|
||||
if (!this._isScrollIdle || doc.activeElement === targetElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<IFocusTrapZoneProps> & {
|
|||
const lastBumper = React.useRef<HTMLDivElement>(null);
|
||||
const mergedRootRef = useMergedRefs(root, ref) as React.Ref<HTMLDivElement>;
|
||||
const doc = useDocument();
|
||||
const win = useWindowEx()!;
|
||||
|
||||
const isFirstRender = usePrevious(false) ?? true;
|
||||
|
||||
|
@ -263,17 +265,17 @@ export const FocusTrapZone: React.FunctionComponent<IFocusTrapZoneProps> & {
|
|||
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 () => {
|
||||
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(() => {
|
||||
|
|
|
@ -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<IKeytipLayerProps, IKeytipL
|
|||
content: '',
|
||||
};
|
||||
|
||||
public static contextType = WindowContext;
|
||||
|
||||
private _events: EventGroup;
|
||||
private _async: Async;
|
||||
|
||||
|
@ -133,13 +137,14 @@ export class KeytipLayerBase extends React.Component<IKeytipLayerProps, IKeytipL
|
|||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
const win = getWindowEx(this.context);
|
||||
// Add window listeners
|
||||
this._events.on(window, 'mouseup', this._onDismiss, true /* useCapture */);
|
||||
this._events.on(window, 'pointerup', this._onDismiss, true /* useCapture */);
|
||||
this._events.on(window, 'resize', this._onDismiss);
|
||||
this._events.on(window, 'keydown', this._onKeyDown, true /* useCapture */);
|
||||
this._events.on(window, 'keypress', this._onKeyPress, true /* useCapture */);
|
||||
this._events.on(window, 'scroll', this._onDismiss, true /* useCapture */);
|
||||
this._events.on(win, 'mouseup', this._onDismiss, true /* useCapture */);
|
||||
this._events.on(win, 'pointerup', this._onDismiss, true /* useCapture */);
|
||||
this._events.on(win, 'resize', this._onDismiss);
|
||||
this._events.on(win, 'keydown', this._onKeyDown, true /* useCapture */);
|
||||
this._events.on(win, 'keypress', this._onKeyPress, true /* useCapture */);
|
||||
this._events.on(win, 'scroll', this._onDismiss, true /* useCapture */);
|
||||
|
||||
// Add keytip listeners
|
||||
this._events.on(this._keytipManager, KeytipEvents.ENTER_KEYTIP_MODE, this._enterKeytipMode);
|
||||
|
@ -384,13 +389,15 @@ export class KeytipLayerBase extends React.Component<IKeytipLayerProps, IKeytipL
|
|||
}
|
||||
|
||||
private _isKeytipInstanceTargetVisible = (keySequences: string[], instanceCount: number): boolean => {
|
||||
const doc = getDocumentEx(this.context);
|
||||
const win = getWindowEx(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
|
||||
return matchingElements.length > 1 && instanceCount <= matchingElements.length
|
||||
? isElementVisibleAndNotHidden(matchingElements[instanceCount - 1] as HTMLElement)
|
||||
? isElementVisibleAndNotHidden(matchingElements[instanceCount - 1] as HTMLElement, win ?? undefined)
|
||||
: instanceCount === 1;
|
||||
};
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
@ -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,
|
||||
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;
|
||||
|
@ -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 = 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.
|
||||
|
@ -161,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, theDoc.defaultView ?? undefined),
|
||||
);
|
||||
if (matchingIndex !== -1) {
|
||||
return matchingNodes[matchingIndex];
|
||||
|
|
|
@ -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<T = any> extends React.Component<IListProps<T>, IListState<T>>
|
|||
renderedWindowsBehind: DEFAULT_RENDERED_WINDOWS_BEHIND,
|
||||
};
|
||||
|
||||
public static contextType = WindowContext;
|
||||
|
||||
private _root = React.createRef<HTMLDivElement>();
|
||||
private _surface = React.createRef<HTMLDivElement>();
|
||||
private _pageRefs: Record<string, unknown> = {};
|
||||
|
@ -347,7 +351,9 @@ export class List<T = any> extends React.Component<IListProps<T>, IListState<T>>
|
|||
this.setState({ ...this._updatePages(this.props, this.state), hasMounted: true });
|
||||
this._measureVersion++;
|
||||
|
||||
this._events.on(window, 'resize', this._onAsyncResizeDebounced);
|
||||
const win = getWindowEx(this.context);
|
||||
|
||||
this._events.on(win, 'resize', this._onAsyncResizeDebounced);
|
||||
if (this._root.current) {
|
||||
this._events.on(this._root.current, 'focus', this._onFocus, true);
|
||||
}
|
||||
|
|
|
@ -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<IMarqueeSelectionStyleProps, IMarqueeSelectionStyles>();
|
||||
|
||||
|
@ -41,6 +43,8 @@ export class MarqueeSelectionBase extends React.Component<IMarqueeSelectionProps
|
|||
isEnabled: true,
|
||||
};
|
||||
|
||||
public static contextType = WindowContext;
|
||||
|
||||
private _async: Async;
|
||||
private _events: EventGroup;
|
||||
private _root = React.createRef<HTMLDivElement>();
|
||||
|
@ -71,8 +75,11 @@ export class MarqueeSelectionBase extends React.Component<IMarqueeSelectionProps
|
|||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
const win = getWindowEx(this.context);
|
||||
const doc = getDocumentEx(this.context);
|
||||
|
||||
this._scrollableParent = findScrollableParent(this._root.current) as HTMLElement;
|
||||
this._scrollableSurface = this._scrollableParent === (window as any) ? document.body : this._scrollableParent;
|
||||
this._scrollableSurface = this._scrollableParent === (win as any) ? doc?.body : this._scrollableParent;
|
||||
// When scroll events come from window, we need to read scrollTop values from the body.
|
||||
|
||||
const hitTarget = this.props.isDraggingConstrainedToRoot ? this._root.current : this._scrollableSurface;
|
||||
|
@ -163,13 +170,14 @@ export class MarqueeSelectionBase extends React.Component<IMarqueeSelectionProps
|
|||
(!onShouldStartSelection || onShouldStartSelection(ev))
|
||||
) {
|
||||
if (this._scrollableSurface && ev.button === 0 && this._root.current) {
|
||||
const win = getWindowEx(this.context);
|
||||
this._selectedIndicies = {};
|
||||
this._preservedIndicies = undefined;
|
||||
this._events.on(window, 'mousemove', this._onAsyncMouseMove, true);
|
||||
this._events.on(win, 'mousemove', this._onAsyncMouseMove, true);
|
||||
this._events.on(this._scrollableParent, 'scroll', this._onAsyncMouseMove);
|
||||
this._events.on(window, 'click', this._onMouseUp, true);
|
||||
this._events.on(win, 'click', this._onMouseUp, true);
|
||||
|
||||
this._autoScroll = new AutoScroll(this._root.current);
|
||||
this._autoScroll = new AutoScroll(this._root.current, win);
|
||||
this._scrollTop = this._scrollableSurface.scrollTop;
|
||||
this._scrollLeft = this._scrollableSurface.scrollLeft;
|
||||
this._rootRect = this._root.current.getBoundingClientRect();
|
||||
|
@ -277,7 +285,8 @@ export class MarqueeSelectionBase extends React.Component<IMarqueeSelectionProps
|
|||
}
|
||||
|
||||
private _onMouseUp(ev: MouseEvent): void {
|
||||
this._events.off(window);
|
||||
const win = getWindowEx(this.context);
|
||||
this._events.off(win);
|
||||
this._events.off(this._scrollableParent, 'scroll');
|
||||
|
||||
if (this._autoScroll) {
|
||||
|
|
|
@ -15,6 +15,8 @@ import type {
|
|||
INavStyles,
|
||||
IRenderGroupHeaderProps,
|
||||
} from './Nav.types';
|
||||
import { WindowContext } from '@fluentui/react-window-provider';
|
||||
import { getDocumentEx } from '../../utilities/dom';
|
||||
|
||||
// The number pixels per indentation level for Nav links.
|
||||
const _indentationSize = 14;
|
||||
|
@ -43,6 +45,8 @@ export class NavBase extends React.Component<INavProps, INavState> implements IN
|
|||
groups: null,
|
||||
};
|
||||
|
||||
public static contextType = WindowContext;
|
||||
|
||||
private _focusZone = React.createRef<IFocusZone>();
|
||||
constructor(props: INavProps) {
|
||||
super(props);
|
||||
|
@ -360,8 +364,9 @@ export class NavBase extends React.Component<INavProps, INavState> implements IN
|
|||
// resolve is not supported for ssr
|
||||
return false;
|
||||
} else {
|
||||
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 || document.createElement('a');
|
||||
_urlResolver = _urlResolver || doc.createElement('a');
|
||||
|
||||
_urlResolver.href = link.url || '';
|
||||
const target: string = _urlResolver.href;
|
||||
|
|
|
@ -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<IOverflowSetStyleProps, IOverflowSetStyles>();
|
||||
const COMPONENT_NAME = 'OverflowSet';
|
||||
|
||||
const useComponentRef = (props: IOverflowSetProps, divContainer: React.RefObject<HTMLDivElement>) => {
|
||||
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],
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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<IPanelStyleProps, IPanelStyles>();
|
||||
const COMPONENT_NAME = 'Panel';
|
||||
|
@ -48,6 +50,8 @@ export class PanelBase extends React.Component<IPanelProps, IPanelState> impleme
|
|||
type: PanelType.smallFixedFar,
|
||||
};
|
||||
|
||||
public static contextType = WindowContext;
|
||||
|
||||
private _async: Async;
|
||||
private _events: EventGroup;
|
||||
private _panel = React.createRef<HTMLDivElement>();
|
||||
|
@ -107,11 +111,13 @@ export class PanelBase extends React.Component<IPanelProps, IPanelState> 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<IPanelProps, IPanelState> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<IScrollablePaneProps, IScrollablePaneState>
|
||||
implements IScrollablePane
|
||||
{
|
||||
public static contextType = WindowContext;
|
||||
|
||||
private _root = React.createRef<HTMLDivElement>();
|
||||
private _stickyAboveRef = React.createRef<HTMLDivElement>();
|
||||
private _stickyBelowRef = React.createRef<HTMLDivElement>();
|
||||
|
@ -78,9 +82,10 @@ export class ScrollablePaneBase
|
|||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const win = getWindowEx(this.context);
|
||||
const { initialScrollPosition } = this.props;
|
||||
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 (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 {
|
||||
|
@ -354,6 +359,7 @@ export class ScrollablePaneBase
|
|||
notifySubscribers: this.notifySubscribers,
|
||||
syncScrollSticky: this.syncScrollSticky,
|
||||
},
|
||||
window: getWindowEx(this.context),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -130,6 +130,10 @@ export interface IScrollablePaneContext {
|
|||
notifySubscribers: (sort?: boolean) => void;
|
||||
syncScrollSticky: (sticky: Sticky) => void;
|
||||
};
|
||||
window: Window | undefined;
|
||||
}
|
||||
|
||||
export const ScrollablePaneContext = React.createContext<IScrollablePaneContext>({ scrollablePane: undefined });
|
||||
export const ScrollablePaneContext = React.createContext<IScrollablePaneContext>({
|
||||
scrollablePane: undefined,
|
||||
window: undefined,
|
||||
});
|
||||
|
|
|
@ -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<T> {
|
||||
items: T[];
|
||||
|
@ -16,6 +18,8 @@ export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>
|
|||
extends React.Component<P, IBaseSelectedItemsListState<T>>
|
||||
implements IBaseSelectedItemsList<T>
|
||||
{
|
||||
public static contextType = WindowContext;
|
||||
|
||||
protected root: HTMLElement;
|
||||
private _defaultSelection: Selection;
|
||||
|
||||
|
@ -213,22 +217,23 @@ export class BaseSelectedItemsList<T, P extends IBaseSelectedItemsListProps<T>>
|
|||
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)!; // equivalent to previous behavior of directly using `document`
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<HTMLDivEl
|
|||
const disposables = React.useRef<(() => void)[]>([]);
|
||||
const { setTimeout, clearTimeout } = useSetTimeout();
|
||||
const sliderLine = React.useRef<HTMLDivElement>(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
|
||||
|
@ -303,15 +305,16 @@ export const useSlider = (props: ISliderProps, ref: React.ForwardedRef<HTMLDivEl
|
|||
newValue - internalState.latestLowerValue <= internalState.latestValue - newValue;
|
||||
}
|
||||
|
||||
// safe to use `win!` since it can only be called on the client
|
||||
if (event.type === 'mousedown') {
|
||||
disposables.current.push(
|
||||
on(window, 'mousemove', onMouseMoveOrTouchMove as (ev: Event) => 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);
|
||||
|
|
|
@ -263,6 +263,8 @@ export class Sticky extends React.Component<IStickyProps, IStickyState> {
|
|||
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<IStickyProps, IStickyState> {
|
|||
}
|
||||
|
||||
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<IStickyProps, IStickyState> {
|
|||
}
|
||||
|
||||
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<IStickyProps, IStickyState> {
|
|||
curr = curr.parentElement;
|
||||
}
|
||||
}
|
||||
return window.getComputedStyle(curr).getPropertyValue('background-color');
|
||||
return win.getComputedStyle(curr).getPropertyValue('background-color');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ISwatchColorPickerPr
|
|||
>((props, ref) => {
|
||||
const defaultId = useId('swatchColorPicker');
|
||||
const id = props.id || defaultId;
|
||||
const doc = useDocumentEx();
|
||||
|
||||
const internalState = useConst<ISwatchColorPickerInternalState>({
|
||||
isNavigationIdle: true,
|
||||
|
@ -161,13 +163,13 @@ export const SwatchColorPickerBase: React.FunctionComponent<ISwatchColorPickerPr
|
|||
const targetElement = ev.currentTarget as HTMLElement;
|
||||
|
||||
// If navigation is idle and the targetElement is the focused element bail out
|
||||
if (internalState.isNavigationIdle && !(document && targetElement === (document.activeElement as HTMLElement))) {
|
||||
if (internalState.isNavigationIdle && !(doc && targetElement === (doc.activeElement as HTMLElement))) {
|
||||
targetElement.focus();
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[focusOnHover, internalState, disabled],
|
||||
[focusOnHover, internalState, disabled, doc],
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -182,7 +184,7 @@ export const SwatchColorPickerBase: React.FunctionComponent<ISwatchColorPickerPr
|
|||
}
|
||||
|
||||
// Get the elements that math the given selector
|
||||
const elements = document.querySelectorAll(parentSelector);
|
||||
const elements = doc?.querySelectorAll(parentSelector) ?? [];
|
||||
|
||||
// iterate over the elements return to make sure it is a parent of the target and focus it
|
||||
for (let index = 0; index < elements.length; index += 1) {
|
||||
|
@ -206,7 +208,7 @@ export const SwatchColorPickerBase: React.FunctionComponent<ISwatchColorPickerPr
|
|||
}
|
||||
}
|
||||
},
|
||||
[disabled, focusOnHover, internalState, mouseLeaveParentSelector],
|
||||
[disabled, focusOnHover, internalState, mouseLeaveParentSelector, doc],
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,7 +8,12 @@ export const getStyles = (props: ITooltipStyleProps): ITooltipStyles => {
|
|||
// 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: [
|
||||
|
|
|
@ -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,7 @@ export class TooltipHostBase extends React.Component<ITooltipHostProps, ITooltip
|
|||
delay: TooltipDelay.medium,
|
||||
};
|
||||
|
||||
public static contextType = WindowContext;
|
||||
private static _currentVisibleTooltip: ITooltipHost | undefined;
|
||||
|
||||
// The wrapping div that gets the hover events
|
||||
|
@ -192,7 +195,7 @@ export class TooltipHostBase extends React.Component<ITooltipHostProps, ITooltip
|
|||
// checking if the blurred element is still the document's activeElement,
|
||||
// and ignoring when it next gets focus back.
|
||||
// See https://github.com/microsoft/fluentui/issues/13541
|
||||
this._ignoreNextFocusEvent = document?.activeElement === ev.target;
|
||||
this._ignoreNextFocusEvent = getDocumentEx(this.context)?.activeElement === ev.target;
|
||||
|
||||
this._dismissTimerId = this._async.setTimeout(() => {
|
||||
this._hideTooltip();
|
||||
|
@ -202,6 +205,7 @@ export class TooltipHostBase extends React.Component<ITooltipHostProps, ITooltip
|
|||
// Show Tooltip
|
||||
private _onTooltipMouseEnter = (ev: any): void => {
|
||||
const { overflowMode, delay } = this.props;
|
||||
const doc = getDocumentEx(this.context);
|
||||
|
||||
if (TooltipHostBase._currentVisibleTooltip && TooltipHostBase._currentVisibleTooltip !== this) {
|
||||
TooltipHostBase._currentVisibleTooltip.dismiss();
|
||||
|
@ -215,7 +219,7 @@ export class TooltipHostBase extends React.Component<ITooltipHostProps, ITooltip
|
|||
}
|
||||
}
|
||||
|
||||
if (ev.target && portalContainsElement(ev.target as HTMLElement, this._getTargetElement())) {
|
||||
if (ev.target && portalContainsElement(ev.target as HTMLElement, this._getTargetElement(), doc)) {
|
||||
// Do not show tooltip when target is inside a portal relative to TooltipHost.
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -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,8 @@ export class BasePicker<T, P extends IBasePickerProps<T>>
|
|||
extends React.Component<P, IBasePickerState<T>>
|
||||
implements IBasePicker<T>
|
||||
{
|
||||
public static contextType = WindowContext;
|
||||
|
||||
// Refs
|
||||
protected root = React.createRef<HTMLDivElement>();
|
||||
protected input = React.createRef<IAutofill>();
|
||||
|
@ -505,7 +509,8 @@ export class BasePicker<T, P extends IBasePickerProps<T>>
|
|||
});
|
||||
} 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 +604,7 @@ export class BasePicker<T, P extends IBasePickerProps<T>>
|
|||
// 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 +1037,7 @@ export class BasePicker<T, P extends IBasePickerProps<T>>
|
|||
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;
|
||||
|
|
|
@ -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<T> = React.MouseEvent<T> & React.TouchEvent<T> & Event;
|
||||
|
||||
export class DraggableZone extends React.Component<IDraggableZoneProps, IDraggableZoneState> {
|
||||
public static contextType = WindowContext;
|
||||
|
||||
private _touchId?: number;
|
||||
private _currentEventType = eventMapping.mouse;
|
||||
private _events: (() => void)[] = [];
|
||||
|
@ -153,9 +157,10 @@ export class DraggableZone extends React.Component<IDraggableZoneProps, IDraggab
|
|||
|
||||
// hook up the appropriate mouse/touch events to the body to ensure
|
||||
// smooth dragging
|
||||
const doc = getDocumentEx(this.context)!;
|
||||
this._events = [
|
||||
on(document.body, this._currentEventType.move, this._onDrag, true /* use capture phase */),
|
||||
on(document.body, this._currentEventType.stop, this._onDragStop, true /* use capture phase */),
|
||||
on(doc.body, this._currentEventType.move, this._onDrag, true /* use capture phase */),
|
||||
on(doc.body, this._currentEventType.stop, this._onDragStop, true /* use capture phase */),
|
||||
];
|
||||
};
|
||||
|
||||
|
@ -259,7 +264,7 @@ export class DraggableZone extends React.Component<IDraggableZoneProps, IDraggab
|
|||
* Returns if an element (or any of the element's parents) match the given selector
|
||||
*/
|
||||
private _matchesSelector(element: HTMLElement | null, selector: string): boolean {
|
||||
if (!element || element === document.body) {
|
||||
if (!element || element === getDocumentEx(this.context)?.body) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,11 +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): 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.
|
||||
|
@ -24,7 +27,7 @@ export function cssColor(color?: string): IRGB | undefined {
|
|||
}
|
||||
|
||||
// if the above fails, do the more expensive catch-all
|
||||
return _browserCompute(color);
|
||||
return _browserCompute(color, theDoc);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,12 +36,12 @@ export function cssColor(color?: string): IRGB | undefined {
|
|||
* This works by attaching an element to the DOM, which may fail in server-side rendering
|
||||
* or with headless browsers.
|
||||
*/
|
||||
function _browserCompute(str: string): IRGB | undefined {
|
||||
if (typeof document === 'undefined') {
|
||||
function _browserCompute(str: string, doc: Document): IRGB | undefined {
|
||||
if (typeof doc === 'undefined') {
|
||||
// don't throw an error when used server-side
|
||||
return undefined;
|
||||
}
|
||||
const elem = document.createElement('div');
|
||||
const elem = doc.createElement('div');
|
||||
elem.style.backgroundColor = str;
|
||||
// This element must be attached to the DOM for getComputedStyle() to have a value
|
||||
elem.style.position = 'absolute';
|
||||
|
@ -46,10 +49,10 @@ function _browserCompute(str: string): IRGB | undefined {
|
|||
elem.style.left = '-9999px';
|
||||
elem.style.height = '1px';
|
||||
elem.style.width = '1px';
|
||||
document.body.appendChild(elem);
|
||||
const eComputedStyle = getComputedStyle(elem);
|
||||
doc.body.appendChild(elem);
|
||||
const eComputedStyle = doc.defaultView?.getComputedStyle(elem);
|
||||
const computedColor = eComputedStyle && eComputedStyle.backgroundColor;
|
||||
document.body.removeChild(elem);
|
||||
doc.body.removeChild(elem);
|
||||
// computedColor is always an RGB(A) string, except for invalid colors in IE/Edge which return 'transparent'
|
||||
|
||||
// browsers return one of these if the color string is invalid,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { getDocument } from '@fluentui/utilities';
|
||||
import { cssColor } from './cssColor';
|
||||
import { getColorFromRGBA } from './getColorFromRGBA';
|
||||
import type { IColor } from './interfaces';
|
||||
|
@ -10,8 +11,9 @@ 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): IColor | undefined {
|
||||
const color = cssColor(inputColor);
|
||||
export function getColorFromString(inputColor: string, doc?: Document): IColor | undefined {
|
||||
const theDoc = doc ?? getDocument()!;
|
||||
const color = cssColor(inputColor, theDoc);
|
||||
|
||||
if (!color) {
|
||||
return;
|
||||
|
|
|
@ -195,9 +195,10 @@ export function withViewport<TProps extends { viewport?: IViewport }, TState>(
|
|||
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();
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import { useDocument, useWindow, WindowProviderProps } from '@fluentui/react-window-provider';
|
||||
|
||||
/**
|
||||
* NOTE: the check for `window`/`document` is a bit verbose and perhaps
|
||||
* overkill but it ensures the prior assumbed behavior of directly
|
||||
* calling `window`/`document` is preserved.
|
||||
*
|
||||
* It is possible to set `window` to undefined on `WindowProvider` so
|
||||
* we'll fallback to directly accessing the global in that (hopefully unlikely)
|
||||
* case.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get a reference to the `document` object.
|
||||
* Use this in place of the global `document` in React function components.
|
||||
* @returns Document | undefined
|
||||
*/
|
||||
export const useDocumentEx = () => {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
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 | undefined
|
||||
*/
|
||||
export const useWindowEx = () => {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
return useWindow() ?? typeof window !== 'undefined' ? window : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 | undefined
|
||||
*/
|
||||
export const getDocumentEx = (ctx: Pick<WindowProviderProps, 'window'> | undefined) => {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
return ctx?.window?.document ?? typeof document !== 'undefined' ? document : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 | undefined
|
||||
*/
|
||||
export const getWindowEx = (ctx: Pick<WindowProviderProps, 'window'> | undefined) => {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
return ctx?.window ?? typeof window !== 'undefined' ? window : undefined;
|
||||
};
|
|
@ -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';
|
||||
|
@ -917,10 +917,12 @@ function _positionElement(
|
|||
hostElement: HTMLElement,
|
||||
elementToPosition: HTMLElement,
|
||||
previousPositions?: IPositionedData,
|
||||
win?: Window,
|
||||
): IPositionedData {
|
||||
const theWin = win ?? getWindow()!;
|
||||
const boundingRect: Rectangle = props.bounds
|
||||
? _getRectangleFromIRect(props.bounds)
|
||||
: new Rectangle(0, window.innerWidth - getScrollbarWidth(), 0, window.innerHeight);
|
||||
: new Rectangle(0, theWin.innerWidth - getScrollbarWidth(), 0, theWin.innerHeight);
|
||||
const positionedElement: IElementPosition = _positionElementRelative(
|
||||
props,
|
||||
elementToPosition,
|
||||
|
@ -942,14 +944,16 @@ function _positionCallout(
|
|||
shouldScroll = false,
|
||||
minimumScrollResizeHeight?: number,
|
||||
doNotFinalizeReturnEdge?: boolean,
|
||||
win?: Window,
|
||||
): ICalloutPositionedInfo {
|
||||
const theWin = win ?? getWindow()!;
|
||||
const beakWidth: number = props.isBeakVisible ? props.beakWidth || 0 : 0;
|
||||
const gap = _calculateGapSpace(props.isBeakVisible, props.beakWidth, props.gapSpace);
|
||||
const positionProps: IPositionProps = props;
|
||||
positionProps.gapSpace = gap;
|
||||
const boundingRect: Rectangle = props.bounds
|
||||
? _getRectangleFromIRect(props.bounds)
|
||||
: new Rectangle(0, window.innerWidth - getScrollbarWidth(), 0, window.innerHeight);
|
||||
: new Rectangle(0, theWin.innerWidth - getScrollbarWidth(), 0, theWin.innerHeight);
|
||||
|
||||
const positionedElement: IElementPositionInfo = _positionElementRelative(
|
||||
positionProps,
|
||||
|
@ -978,8 +982,10 @@ function _positionCard(
|
|||
hostElement: HTMLElement,
|
||||
callout: HTMLElement,
|
||||
previousPositions?: ICalloutPositionedInfo,
|
||||
win?: Window,
|
||||
): ICalloutPositionedInfo {
|
||||
return _positionCallout(props, hostElement, callout, previousPositions, false, undefined, true);
|
||||
const theWin = win ?? getWindow()!;
|
||||
return _positionCallout(props, hostElement, callout, previousPositions, false, undefined, true, theWin);
|
||||
}
|
||||
|
||||
function _getRectangleFromTarget(target: Element | MouseEvent | Point | Rectangle): Rectangle {
|
||||
|
@ -1028,8 +1034,9 @@ export function positionElement(
|
|||
hostElement: HTMLElement,
|
||||
elementToPosition: HTMLElement,
|
||||
previousPositions?: IPositionedData,
|
||||
win?: Window,
|
||||
): IPositionedData {
|
||||
return _positionElement(props, hostElement, elementToPosition, previousPositions);
|
||||
return _positionElement(props, hostElement, elementToPosition, previousPositions, win);
|
||||
}
|
||||
|
||||
export function positionCallout(
|
||||
|
@ -1039,6 +1046,7 @@ export function positionCallout(
|
|||
previousPositions?: ICalloutPositionedInfo,
|
||||
shouldScroll?: boolean,
|
||||
minimumScrollResizeHeight?: number,
|
||||
win?: Window,
|
||||
): ICalloutPositionedInfo {
|
||||
return _positionCallout(
|
||||
props,
|
||||
|
@ -1047,6 +1055,8 @@ export function positionCallout(
|
|||
previousPositions,
|
||||
shouldScroll,
|
||||
minimumScrollResizeHeight,
|
||||
undefined,
|
||||
win,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1055,8 +1065,9 @@ export function positionCard(
|
|||
hostElement: HTMLElement,
|
||||
elementToPosition: HTMLElement,
|
||||
previousPositions?: ICalloutPositionedInfo,
|
||||
win?: Window,
|
||||
): ICalloutPositionedInfo {
|
||||
return _positionCard(props, hostElement, elementToPosition, previousPositions);
|
||||
return _positionCard(props, hostElement, elementToPosition, previousPositions, win);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1071,11 +1082,13 @@ export function getMaxHeight(
|
|||
gapSpace: number = 0,
|
||||
bounds?: IRectangle,
|
||||
coverTarget?: boolean,
|
||||
win?: Window,
|
||||
): number {
|
||||
const theWin = win ?? getWindow()!;
|
||||
const targetRect = _getRectangleFromTarget(target);
|
||||
const boundingRectangle = bounds
|
||||
? _getRectangleFromIRect(bounds)
|
||||
: new Rectangle(0, window.innerWidth - getScrollbarWidth(), 0, window.innerHeight);
|
||||
: new Rectangle(0, theWin.innerWidth - getScrollbarWidth(), 0, theWin.innerHeight);
|
||||
|
||||
return _getMaxHeightFromTargetRectangle(targetRect, targetEdge, gapSpace, boundingRectangle, coverTarget);
|
||||
}
|
||||
|
|
|
@ -200,12 +200,13 @@ export class SelectionZone extends React.Component<ISelectionZoneProps, ISelecti
|
|||
|
||||
public componentDidMount(): void {
|
||||
const win = getWindow(this._root.current);
|
||||
const doc = win?.document;
|
||||
|
||||
// Track the latest modifier keys globally.
|
||||
this._events.on(win, 'keydown, keyup', this._updateModifiers, true);
|
||||
this._events.on(document, 'click', this._findScrollParentAndTryClearOnEmptyClick);
|
||||
this._events.on(document.body, 'touchstart', this._onTouchStartCapture, true);
|
||||
this._events.on(document.body, 'touchend', this._onTouchStartCapture, true);
|
||||
this._events.on(doc, 'click', this._findScrollParentAndTryClearOnEmptyClick);
|
||||
this._events.on(doc?.body, 'touchstart', this._onTouchStartCapture, true);
|
||||
this._events.on(doc?.body, 'touchend', this._onTouchStartCapture, true);
|
||||
|
||||
// Subscribe to the selection to keep modal state updated.
|
||||
this._events.on(this.props.selection, 'change', this._onSelectionChange);
|
||||
|
@ -274,8 +275,10 @@ export class SelectionZone extends React.Component<ISelectionZoneProps, ISelecti
|
|||
|
||||
private _onMouseDownCapture = (ev: React.MouseEvent<HTMLElement>): 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<ISelectionZoneProps, ISelecti
|
|||
* so this is less likely to cause layout thrashing then doing it in mount.
|
||||
*/
|
||||
private _findScrollParentAndTryClearOnEmptyClick(ev: MouseEvent) {
|
||||
const win = getWindow(this._root.current);
|
||||
const doc = win?.document;
|
||||
const scrollParent = findScrollableParent(this._root.current) as HTMLElement;
|
||||
// unbind this handler and replace binding with a binding on the actual scrollable parent
|
||||
this._events.off(document, 'click', this._findScrollParentAndTryClearOnEmptyClick);
|
||||
this._events.off(doc, 'click', this._findScrollParentAndTryClearOnEmptyClick);
|
||||
this._events.on(scrollParent, 'click', this._tryClearOnEmptyClick);
|
||||
|
||||
// If we clicked inside the scrollable parent, call through to the handler on this click.
|
||||
|
|
|
@ -87,7 +87,7 @@ export const audioProperties: Record<string, number>;
|
|||
|
||||
// @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<TProps extends {}>(defaultProps: Partial<TProps>, 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 }
|
||||
|
||||
|
@ -811,7 +811,7 @@ export function isElementTabbable(element: HTMLElement, checkTabIndex?: boolean)
|
|||
export function isElementVisible(element: HTMLElement | undefined | null): boolean;
|
||||
|
||||
// @public
|
||||
export function isElementVisibleAndNotHidden(element: HTMLElement | undefined | null): boolean;
|
||||
export function isElementVisibleAndNotHidden(element: HTMLElement | undefined | null, win?: Window): boolean;
|
||||
|
||||
// Warning: (ae-internal-missing-underscore) The name "ISerializableObject" should be prefixed with an underscore because the declaration is marked as @internal
|
||||
//
|
||||
|
@ -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 {
|
||||
|
@ -1228,7 +1228,7 @@ export function setWarningCallback(warningCallback?: (message: string) => void):
|
|||
export function shallowCompare<TA extends any, TB extends any>(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<TComponentProps extends IPropsWithStyles<TStyleProps, TStyleSet>, TStyleProps, TStyleSet extends IStyleSet<TStyleSet>>(Component: React_2.ComponentClass<TComponentProps> | React_2.FunctionComponent<TComponentProps>, baseStyles: IStyleFunctionOrObject<TStyleProps, TStyleSet>, getProps?: (props: TComponentProps) => Partial<TComponentProps>, customizable?: ICustomizableProps, pure?: boolean): React_2.FunctionComponent<TComponentProps>;
|
||||
|
|
|
@ -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,21 +27,22 @@ export class AutoScroll {
|
|||
private _isVerticalScroll!: boolean;
|
||||
private _timeoutId?: number;
|
||||
|
||||
constructor(element: HTMLElement) {
|
||||
constructor(element: HTMLElement, win?: Window) {
|
||||
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, theWin);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (this._scrollableParent === (window as any)) {
|
||||
this._scrollableParent = document.body;
|
||||
if (this._scrollableParent === (theWin as any)) {
|
||||
this._scrollableParent = theWin.document.body;
|
||||
}
|
||||
|
||||
if (this._scrollableParent) {
|
||||
this._events.on(window, 'mousemove', this._onMouseMove, true);
|
||||
this._events.on(window, 'touchmove', this._onTouchMove, true);
|
||||
this._events.on(theWin, 'mousemove', this._onMouseMove, true);
|
||||
this._events.on(theWin, 'touchmove', this._onTouchMove, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<IDelayedRenderProps, IDelayed
|
|||
|
||||
public componentDidMount(): void {
|
||||
let { delay } = this.props;
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
this._timeoutId = window.setTimeout(() => {
|
||||
this.setState({
|
||||
isRendered: true,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { getDocument } from './dom';
|
||||
import { assign } from './object';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
@ -61,12 +62,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,
|
||||
doc?: Document,
|
||||
): boolean | undefined {
|
||||
let retVal;
|
||||
const theDoc = doc ?? getDocument()!;
|
||||
|
||||
if (EventGroup._isElement(target)) {
|
||||
if (typeof document !== 'undefined' && document.createEvent) {
|
||||
let ev = document.createEvent('HTMLEvents');
|
||||
if (typeof theDoc !== 'undefined' && theDoc.createEvent) {
|
||||
let ev = theDoc.createEvent('HTMLEvents');
|
||||
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
ev.initEvent(eventName, bubbleEvent || false, true);
|
||||
|
@ -74,9 +82,9 @@ export class EventGroup {
|
|||
assign(ev, eventArgs);
|
||||
|
||||
retVal = target.dispatchEvent(ev);
|
||||
} else if (typeof document !== 'undefined' && (document as any).createEventObject) {
|
||||
} else if (typeof theDoc !== 'undefined' && (theDoc as any).createEventObject) {
|
||||
// IE8
|
||||
let evObj = (document 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);
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ export class FabricPerformance {
|
|||
measurement.totalDuration += duration;
|
||||
measurement.count++;
|
||||
measurement.all.push({
|
||||
duration: duration,
|
||||
duration,
|
||||
timeStamp: end,
|
||||
});
|
||||
FabricPerformance.summary[name] = measurement;
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
*/
|
||||
export function canUseDOM(): boolean {
|
||||
return (
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
typeof window !== 'undefined' &&
|
||||
!!(
|
||||
window.document &&
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
window.document.createElement
|
||||
// eslint-disable-next-line no-restricted-globals, deprecation/deprecation
|
||||
(window.document && window.document.createElement)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,11 +8,13 @@ import { canUseDOM } from './canUseDOM';
|
|||
* @public
|
||||
*/
|
||||
export function getDocument(rootElement?: HTMLElement | null): Document | undefined {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
if (!canUseDOM() || typeof document === 'undefined') {
|
||||
return undefined;
|
||||
} else {
|
||||
const el = rootElement as Element;
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
return el && el.ownerDocument ? el.ownerDocument : document;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
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): IRectangle | undefined {
|
||||
export function getRect(element: HTMLElement | Window | null, win?: Window): IRectangle | undefined {
|
||||
const theWin =
|
||||
win ?? (!element || (element && element.hasOwnProperty('devicePixelRatio')))
|
||||
? getWindow()
|
||||
: getWindow(element as HTMLElement)!;
|
||||
let rect: IRectangle | undefined;
|
||||
if (element) {
|
||||
if (element === window) {
|
||||
if (element === theWin) {
|
||||
rect = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
right: window.innerWidth,
|
||||
bottom: window.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();
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
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): void {
|
||||
const event = createNewEvent('MouseEvents');
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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, win?: Window): boolean {
|
||||
const theWin = win ?? getWindow()!;
|
||||
return (
|
||||
!!element &&
|
||||
isElementVisible(element) &&
|
||||
!element.hidden &&
|
||||
window.getComputedStyle(element).visibility !== 'hidden'
|
||||
theWin.getComputedStyle(element).visibility !== 'hidden'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -456,8 +457,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;
|
||||
}
|
||||
|
@ -473,8 +474,10 @@ export function doesElementContainFocus(element: HTMLElement): boolean {
|
|||
export function shouldWrapFocus(
|
||||
element: HTMLElement,
|
||||
noWrapDataAttribute: 'data-no-vertical-wrap' | 'data-no-horizontal-wrap',
|
||||
doc?: Document,
|
||||
): boolean {
|
||||
return elementContainsAttribute(element, noWrapDataAttribute) === 'true' ? false : true;
|
||||
const theDoc = doc ?? getDocument()!;
|
||||
return elementContainsAttribute(element, noWrapDataAttribute, theDoc) === 'true' ? false : true;
|
||||
}
|
||||
|
||||
let animationId: number | undefined = undefined;
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
* Used to determine whether iOS-specific behavior should be applied.
|
||||
*/
|
||||
export const isIOS = (): boolean => {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
if (!window || !window.navigator || !window.navigator.userAgent) {
|
||||
return false;
|
||||
}
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
return /iPad|iPhone|iPod/i.test(window.navigator.userAgent);
|
||||
};
|
||||
|
|
|
@ -136,20 +136,21 @@ export function enableBodyScroll(): void {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
export function getScrollbarWidth(): number {
|
||||
export function getScrollbarWidth(doc?: Document): number {
|
||||
if (_scrollbarWidth === undefined) {
|
||||
let scrollDiv: HTMLElement = document.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');
|
||||
document.body.appendChild(scrollDiv);
|
||||
theDoc.body.appendChild(scrollDiv);
|
||||
// Get the scrollbar width
|
||||
_scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
|
||||
|
||||
// Delete the DIV
|
||||
document.body.removeChild(scrollDiv);
|
||||
theDoc.body.removeChild(scrollDiv);
|
||||
}
|
||||
|
||||
return _scrollbarWidth;
|
||||
|
|
|
@ -150,7 +150,7 @@ describe('Selection', () => {
|
|||
}
|
||||
const items: ICustomItem[] = [{ id: 'a' }, { id: 'b' }];
|
||||
const selection = new Selection<ICustomItem>({
|
||||
onSelectionChanged: onSelectionChanged,
|
||||
onSelectionChanged,
|
||||
getKey: (item: ICustomItem) => item.id,
|
||||
items,
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче