From c303002e72ef1578f37d20c71c9b2e3a576c41e4 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Tue, 21 May 2024 22:29:41 +0200
Subject: [PATCH 01/29] Add EuiWindowProvider for EuiPortal and EuiWindowEvent
---
packages/eui/src/components/portal/portal.tsx | 13 +++++++---
packages/eui/src/services/index.ts | 6 +++++
.../src/services/window_event/window_event.ts | 9 +++++--
.../src/services/window_provider/context.ts | 17 +++++++++++++
.../eui/src/services/window_provider/hooks.ts | 15 +++++++++++
.../eui/src/services/window_provider/index.ts | 12 +++++++++
.../src/services/window_provider/provider.tsx | 25 +++++++++++++++++++
7 files changed, 91 insertions(+), 6 deletions(-)
create mode 100644 packages/eui/src/services/window_provider/context.ts
create mode 100644 packages/eui/src/services/window_provider/hooks.ts
create mode 100644 packages/eui/src/services/window_provider/index.ts
create mode 100644 packages/eui/src/services/window_provider/provider.tsx
diff --git a/packages/eui/src/components/portal/portal.tsx b/packages/eui/src/components/portal/portal.tsx
index f4f24ba9d21..7a49268d7a4 100644
--- a/packages/eui/src/components/portal/portal.tsx
+++ b/packages/eui/src/components/portal/portal.tsx
@@ -19,7 +19,7 @@ import React, {
} from 'react';
import { createPortal } from 'react-dom';
-import { EuiNestedThemeContext } from '../../services';
+import { EuiNestedThemeContext, useEuiWindow } from '../../services';
import { usePropsWithComponentDefaults } from '../provider/component_defaults';
const INSERT_POSITIONS = ['after', 'before'] as const;
@@ -43,11 +43,16 @@ export interface EuiPortalProps {
* Optional ref callback
*/
portalRef?: (ref: HTMLDivElement | null) => void;
+ /**
+ * Window object
+ */
+ currentWindow?: Window;
}
export const EuiPortal: FunctionComponent = (props) => {
const propsWithDefaults = usePropsWithComponentDefaults('EuiPortal', props);
- return ;
+ const currentWindow = useEuiWindow();
+ return ;
};
interface EuiPortalState {
@@ -67,14 +72,14 @@ export class EuiPortalClass extends Component {
}
componentDidMount() {
- const { insert } = this.props;
+ const { insert, currentWindow } = this.props;
const portalNode = document.createElement('div');
portalNode.dataset.euiportal = 'true';
if (insert == null) {
// no insertion defined, append to body
- document.body.appendChild(portalNode);
+ (currentWindow ?? window).document.body.appendChild(portalNode);
} else {
// inserting before or after an element
const { sibling, position } = insert;
diff --git a/packages/eui/src/services/index.ts b/packages/eui/src/services/index.ts
index 4816f7235bf..32ff2731dc3 100644
--- a/packages/eui/src/services/index.ts
+++ b/packages/eui/src/services/index.ts
@@ -103,3 +103,9 @@ export {
} from './transition';
export { EuiWindowEvent } from './window_event';
export { keys };
+export {
+ EuiWindowContext,
+ EuiWindowProvider,
+ useEuiWindow,
+} from './window_provider';
+export type { EuiWindowContextValue } from './window_provider';
\ No newline at end of file
diff --git a/packages/eui/src/services/window_event/window_event.ts b/packages/eui/src/services/window_event/window_event.ts
index 5c5488689a3..a70ff711426 100644
--- a/packages/eui/src/services/window_event/window_event.ts
+++ b/packages/eui/src/services/window_event/window_event.ts
@@ -7,6 +7,7 @@
*/
import { Component } from 'react';
+import { EuiWindowContext, EuiWindowContextValue } from '../window_provider';
type EventNames = keyof WindowEventMap;
@@ -16,6 +17,8 @@ interface Props {
}
export class EuiWindowEvent extends Component> {
+ static contextType = EuiWindowContext;
+
componentDidMount() {
this.addEvent(this.props);
}
@@ -35,11 +38,13 @@ export class EuiWindowEvent extends Component> {
}
addEvent({ event, handler }: Props) {
- window.addEventListener(event, handler);
+ const currentWindow = (this.context as EuiWindowContextValue).window;
+ currentWindow.addEventListener(event, handler);
}
removeEvent({ event, handler }: Props) {
- window.removeEventListener(event, handler);
+ const currentWindow = (this.context as EuiWindowContextValue).window;
+ currentWindow.removeEventListener(event, handler);
}
render() {
diff --git a/packages/eui/src/services/window_provider/context.ts b/packages/eui/src/services/window_provider/context.ts
new file mode 100644
index 00000000000..6cc7099a873
--- /dev/null
+++ b/packages/eui/src/services/window_provider/context.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { createContext } from 'react';
+
+export interface EuiWindowContextValue {
+ window: Window;
+}
+
+export const EuiWindowContext = createContext({
+ window: window,
+});
diff --git a/packages/eui/src/services/window_provider/hooks.ts b/packages/eui/src/services/window_provider/hooks.ts
new file mode 100644
index 00000000000..a39c5d711f8
--- /dev/null
+++ b/packages/eui/src/services/window_provider/hooks.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { useContext } from 'react';
+import { EuiWindowContext } from './context';
+
+export function useEuiWindow() {
+ const context = useContext(EuiWindowContext);
+ return context.window;
+}
diff --git a/packages/eui/src/services/window_provider/index.ts b/packages/eui/src/services/window_provider/index.ts
new file mode 100644
index 00000000000..96958d34504
--- /dev/null
+++ b/packages/eui/src/services/window_provider/index.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { EuiWindowProvider } from './provider';
+export type { EuiWindowContextValue } from './context';
+export { EuiWindowContext } from './context';
+export { useEuiWindow } from './hooks';
diff --git a/packages/eui/src/services/window_provider/provider.tsx b/packages/eui/src/services/window_provider/provider.tsx
new file mode 100644
index 00000000000..ad6e77a53b5
--- /dev/null
+++ b/packages/eui/src/services/window_provider/provider.tsx
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { PropsWithChildren } from 'react';
+import { EuiWindowContext } from './context';
+
+export interface EuiWindowProviderProps extends PropsWithChildren {
+ window: Window;
+}
+
+export function EuiWindowProvider({
+ window,
+ children,
+}: EuiWindowProviderProps) {
+ return (
+
+ {children}
+
+ );
+}
From 77f5704828f93afeefd9d865d36b7ef92f4b0e3c Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Tue, 21 May 2024 22:35:02 +0200
Subject: [PATCH 02/29] Formatting
---
packages/eui/src/components/portal/portal.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/eui/src/components/portal/portal.tsx b/packages/eui/src/components/portal/portal.tsx
index 7a49268d7a4..aea8ea1cebb 100644
--- a/packages/eui/src/components/portal/portal.tsx
+++ b/packages/eui/src/components/portal/portal.tsx
@@ -52,7 +52,9 @@ export interface EuiPortalProps {
export const EuiPortal: FunctionComponent = (props) => {
const propsWithDefaults = usePropsWithComponentDefaults('EuiPortal', props);
const currentWindow = useEuiWindow();
- return ;
+ return (
+
+ );
};
interface EuiPortalState {
From 7d1d3919dcefa345ccb05f3458174fb1aebd5803 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Tue, 21 May 2024 22:35:47 +0200
Subject: [PATCH 03/29] Formatting
---
packages/eui/src/services/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/eui/src/services/index.ts b/packages/eui/src/services/index.ts
index 32ff2731dc3..6bac1a74a55 100644
--- a/packages/eui/src/services/index.ts
+++ b/packages/eui/src/services/index.ts
@@ -108,4 +108,4 @@ export {
EuiWindowProvider,
useEuiWindow,
} from './window_provider';
-export type { EuiWindowContextValue } from './window_provider';
\ No newline at end of file
+export type { EuiWindowContextValue } from './window_provider';
From cf06fb6e43f9db9e55b2d6753f0bd1b25eb09ec0 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Wed, 22 May 2024 12:56:01 +0200
Subject: [PATCH 04/29] Add Flyout, FocusTrap, OutsideClickDetector,
findElement support for window provider
---
packages/eui/i18ntokens.json | 24 +++++++++----------
packages/eui/src/components/flyout/flyout.tsx | 14 ++++++-----
.../src/components/focus_trap/focus_trap.tsx | 18 +++++++++-----
.../outside_click_detector.tsx | 15 ++++++++----
packages/eui/src/services/findElement.ts | 7 ++++--
.../src/services/window_provider/provider.tsx | 5 ++--
6 files changed, 51 insertions(+), 32 deletions(-)
diff --git a/packages/eui/i18ntokens.json b/packages/eui/i18ntokens.json
index 8b31c1af198..0e0ce9e4488 100644
--- a/packages/eui/i18ntokens.json
+++ b/packages/eui/i18ntokens.json
@@ -4685,14 +4685,14 @@
"highlighting": "string",
"loc": {
"start": {
- "line": 336,
+ "line": 338,
"column": 14,
- "index": 10903
+ "index": 11045
},
"end": {
- "line": 339,
+ "line": 341,
"column": 16,
- "index": 11118
+ "index": 11260
}
},
"filepath": "src/components/flyout/flyout.tsx"
@@ -4703,14 +4703,14 @@
"highlighting": "string",
"loc": {
"start": {
- "line": 341,
+ "line": 343,
"column": 14,
- "index": 11151
+ "index": 11293
},
"end": {
- "line": 344,
+ "line": 346,
"column": 16,
- "index": 11329
+ "index": 11471
}
},
"filepath": "src/components/flyout/flyout.tsx"
@@ -4721,14 +4721,14 @@
"highlighting": "string",
"loc": {
"start": {
- "line": 347,
+ "line": 349,
"column": 14,
- "index": 11406
+ "index": 11548
},
"end": {
- "line": 350,
+ "line": 352,
"column": 16,
- "index": 11599
+ "index": 11741
}
},
"filepath": "src/components/flyout/flyout.tsx"
diff --git a/packages/eui/src/components/flyout/flyout.tsx b/packages/eui/src/components/flyout/flyout.tsx
index 813961178e8..90840048273 100644
--- a/packages/eui/src/components/flyout/flyout.tsx
+++ b/packages/eui/src/components/flyout/flyout.tsx
@@ -30,6 +30,7 @@ import {
useIsWithinMinBreakpoint,
useEuiMemoizedStyles,
useGeneratedHtmlId,
+ useEuiWindow,
} from '../../services';
import { logicalStyle } from '../../global_styling';
@@ -202,6 +203,7 @@ export const EuiFlyout = forwardRef(
) => {
const Element = as || defaultElement;
const maskRef = useRef(null);
+ const currentWindow = useEuiWindow();
const windowIsLargeEnoughToPush =
useIsWithinMinBreakpoint(pushMinBreakpoint);
@@ -225,9 +227,9 @@ export const EuiFlyout = forwardRef(
const paddingSide =
side === 'left' ? 'paddingInlineStart' : 'paddingInlineEnd';
- document.body.style[paddingSide] = `${width}px`;
+ currentWindow.document.body.style[paddingSide] = `${width}px`;
return () => {
- document.body.style[paddingSide] = '';
+ currentWindow.document.body.style[paddingSide] = '';
};
}
}, [isPushed, side, width]);
@@ -236,10 +238,10 @@ export const EuiFlyout = forwardRef(
* This class doesn't actually do anything by EUI, but is nice to add for consumers (JIC)
*/
useEffect(() => {
- document.body.classList.add('euiBody--hasFlyout');
+ currentWindow.document.body.classList.add('euiBody--hasFlyout');
return () => {
// Remove the hasFlyout class when the flyout is unmounted
- document.body.classList.remove('euiBody--hasFlyout');
+ currentWindow.document.body.classList.remove('euiBody--hasFlyout');
};
}, []);
@@ -290,12 +292,12 @@ export const EuiFlyout = forwardRef(
* If not disabled, automatically add fixed EuiHeaders as shards
* to EuiFlyout focus traps, to prevent focus fighting
*/
- const flyoutToggle = useRef(document.activeElement);
+ const flyoutToggle = useRef(currentWindow.document.activeElement);
const [fixedHeaders, setFixedHeaders] = useState([]);
useEffect(() => {
if (includeFixedHeadersInFocusTrap) {
- const fixedHeaderEls = document.querySelectorAll(
+ const fixedHeaderEls = currentWindow.document.querySelectorAll(
'.euiHeader[data-fixed-header]'
);
setFixedHeaders(Array.from(fixedHeaderEls));
diff --git a/packages/eui/src/components/focus_trap/focus_trap.tsx b/packages/eui/src/components/focus_trap/focus_trap.tsx
index e71a892de1e..dd37abdc5a0 100644
--- a/packages/eui/src/components/focus_trap/focus_trap.tsx
+++ b/packages/eui/src/components/focus_trap/focus_trap.tsx
@@ -12,7 +12,7 @@ import { ReactFocusOnProps } from 'react-focus-on/dist/es5/types';
import { RemoveScrollBar } from 'react-remove-scroll-bar';
import { CommonProps } from '../common';
-import { findElementBySelectorOrRef, ElementTarget } from '../../services';
+import { findElementBySelectorOrRef, ElementTarget, EuiWindowContext, EuiWindowContextValue } from '../../services';
import { usePropsWithComponentDefaults } from '../provider/component_defaults';
export type FocusTarget = ElementTarget;
@@ -105,6 +105,8 @@ class EuiFocusTrapClass extends Component {
gapMode: 'padding', // EUI defaults to padding because Kibana's body/layout CSS ignores `margin`
};
+ static contextType = EuiWindowContext;
+
state: State = {
hasBeenDisabledByClick: false,
};
@@ -129,7 +131,9 @@ class EuiFocusTrapClass extends Component {
// Programmatically sets focus on a nested DOM node; optional
setInitialFocus = (initialFocus?: FocusTarget) => {
if (!initialFocus) return;
- const node = findElementBySelectorOrRef(initialFocus);
+
+ const currentDocument = (this.context as EuiWindowContextValue).window.document;
+ const node = findElementBySelectorOrRef(initialFocus, currentDocument);
if (!node) return;
// `data-autofocus` is part of the 'react-focus-on' API
node.setAttribute('data-autofocus', 'true');
@@ -143,13 +147,15 @@ class EuiFocusTrapClass extends Component {
};
addMouseupListener = () => {
- document.addEventListener('mouseup', this.onMouseupOutside);
- document.addEventListener('touchend', this.onMouseupOutside);
+ const currentDocument = (this.context as EuiWindowContextValue).window.document;
+ currentDocument.addEventListener('mouseup', this.onMouseupOutside);
+ currentDocument.addEventListener('touchend', this.onMouseupOutside);
};
removeMouseupListener = () => {
- document.removeEventListener('mouseup', this.onMouseupOutside);
- document.removeEventListener('touchend', this.onMouseupOutside);
+ const currentDocument = (this.context as EuiWindowContextValue).window.document;
+ currentDocument.removeEventListener('mouseup', this.onMouseupOutside);
+ currentDocument.removeEventListener('touchend', this.onMouseupOutside);
};
handleOutsideClick: ReactFocusOnProps['onClickOutside'] = (event) => {
diff --git a/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx b/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx
index e15a7c0cde6..d72808800a4 100644
--- a/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx
+++ b/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx
@@ -15,6 +15,7 @@ import {
ReactElement,
} from 'react';
import { htmlIdGenerator } from '../../services/accessibility';
+import { EuiWindowContext, EuiWindowContextValue } from '../../services';
export interface EuiEvent extends Event {
euiGeneratedBy: string[];
@@ -45,6 +46,8 @@ export class EuiOutsideClickDetector extends Component HTMLElement);
-export const findElementBySelectorOrRef = (elementTarget?: ElementTarget) => {
+export const findElementBySelectorOrRef = (
+ elementTarget?: ElementTarget,
+ currentDocument?: Document
+) => {
let node = elementTarget instanceof HTMLElement ? elementTarget : null;
if (typeof elementTarget === 'string') {
- node = document.querySelector(elementTarget as string);
+ node = (currentDocument ?? document).querySelector(elementTarget as string);
} else if (typeof elementTarget === 'function') {
node = (elementTarget as () => HTMLElement)();
}
diff --git a/packages/eui/src/services/window_provider/provider.tsx b/packages/eui/src/services/window_provider/provider.tsx
index ad6e77a53b5..ca7df73ee43 100644
--- a/packages/eui/src/services/window_provider/provider.tsx
+++ b/packages/eui/src/services/window_provider/provider.tsx
@@ -6,11 +6,12 @@
* Side Public License, v 1.
*/
-import React, { PropsWithChildren } from 'react';
+import React, { ReactNode } from 'react';
import { EuiWindowContext } from './context';
-export interface EuiWindowProviderProps extends PropsWithChildren {
+export interface EuiWindowProviderProps {
window: Window;
+ children: ReactNode;
}
export function EuiWindowProvider({
From 4fb655c0f70c0c6891afbcec36d0b4a5a81545e4 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Wed, 22 May 2024 12:56:52 +0200
Subject: [PATCH 05/29] Formatting
---
packages/eui/src/components/flyout/flyout.tsx | 11 +++++++----
.../eui/src/components/focus_trap/focus_trap.tsx | 16 ++++++++++++----
2 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/packages/eui/src/components/flyout/flyout.tsx b/packages/eui/src/components/flyout/flyout.tsx
index 90840048273..65fc99e04d0 100644
--- a/packages/eui/src/components/flyout/flyout.tsx
+++ b/packages/eui/src/components/flyout/flyout.tsx
@@ -292,14 +292,17 @@ export const EuiFlyout = forwardRef(
* If not disabled, automatically add fixed EuiHeaders as shards
* to EuiFlyout focus traps, to prevent focus fighting
*/
- const flyoutToggle = useRef(currentWindow.document.activeElement);
+ const flyoutToggle = useRef(
+ currentWindow.document.activeElement
+ );
const [fixedHeaders, setFixedHeaders] = useState([]);
useEffect(() => {
if (includeFixedHeadersInFocusTrap) {
- const fixedHeaderEls = currentWindow.document.querySelectorAll(
- '.euiHeader[data-fixed-header]'
- );
+ const fixedHeaderEls =
+ currentWindow.document.querySelectorAll(
+ '.euiHeader[data-fixed-header]'
+ );
setFixedHeaders(Array.from(fixedHeaderEls));
// Flyouts that are toggled from fixed headers do not have working
diff --git a/packages/eui/src/components/focus_trap/focus_trap.tsx b/packages/eui/src/components/focus_trap/focus_trap.tsx
index dd37abdc5a0..b95dc509246 100644
--- a/packages/eui/src/components/focus_trap/focus_trap.tsx
+++ b/packages/eui/src/components/focus_trap/focus_trap.tsx
@@ -12,7 +12,12 @@ import { ReactFocusOnProps } from 'react-focus-on/dist/es5/types';
import { RemoveScrollBar } from 'react-remove-scroll-bar';
import { CommonProps } from '../common';
-import { findElementBySelectorOrRef, ElementTarget, EuiWindowContext, EuiWindowContextValue } from '../../services';
+import {
+ findElementBySelectorOrRef,
+ ElementTarget,
+ EuiWindowContext,
+ EuiWindowContextValue,
+} from '../../services';
import { usePropsWithComponentDefaults } from '../provider/component_defaults';
export type FocusTarget = ElementTarget;
@@ -132,7 +137,8 @@ class EuiFocusTrapClass extends Component {
setInitialFocus = (initialFocus?: FocusTarget) => {
if (!initialFocus) return;
- const currentDocument = (this.context as EuiWindowContextValue).window.document;
+ const currentDocument = (this.context as EuiWindowContextValue).window
+ .document;
const node = findElementBySelectorOrRef(initialFocus, currentDocument);
if (!node) return;
// `data-autofocus` is part of the 'react-focus-on' API
@@ -147,13 +153,15 @@ class EuiFocusTrapClass extends Component {
};
addMouseupListener = () => {
- const currentDocument = (this.context as EuiWindowContextValue).window.document;
+ const currentDocument = (this.context as EuiWindowContextValue).window
+ .document;
currentDocument.addEventListener('mouseup', this.onMouseupOutside);
currentDocument.addEventListener('touchend', this.onMouseupOutside);
};
removeMouseupListener = () => {
- const currentDocument = (this.context as EuiWindowContextValue).window.document;
+ const currentDocument = (this.context as EuiWindowContextValue).window
+ .document;
currentDocument.removeEventListener('mouseup', this.onMouseupOutside);
currentDocument.removeEventListener('touchend', this.onMouseupOutside);
};
From 26433d02acc1c4357235ece65ae0f137238dbde6 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Wed, 22 May 2024 12:58:45 +0200
Subject: [PATCH 06/29] Fix effect
---
packages/eui/src/components/flyout/flyout.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/eui/src/components/flyout/flyout.tsx b/packages/eui/src/components/flyout/flyout.tsx
index 65fc99e04d0..c81767ca9ff 100644
--- a/packages/eui/src/components/flyout/flyout.tsx
+++ b/packages/eui/src/components/flyout/flyout.tsx
@@ -232,7 +232,7 @@ export const EuiFlyout = forwardRef(
currentWindow.document.body.style[paddingSide] = '';
};
}
- }, [isPushed, side, width]);
+ }, [isPushed, side, width, currentWindow.document.body.style]);
/**
* This class doesn't actually do anything by EUI, but is nice to add for consumers (JIC)
From 0bf4e8eea8510fc1c6cc8f36c0a5958f630008b7 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Wed, 22 May 2024 13:27:03 +0200
Subject: [PATCH 07/29] Fix effects
---
packages/eui/src/components/flyout/flyout.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/eui/src/components/flyout/flyout.tsx b/packages/eui/src/components/flyout/flyout.tsx
index c81767ca9ff..a47f1cca9a6 100644
--- a/packages/eui/src/components/flyout/flyout.tsx
+++ b/packages/eui/src/components/flyout/flyout.tsx
@@ -243,7 +243,7 @@ export const EuiFlyout = forwardRef(
// Remove the hasFlyout class when the flyout is unmounted
currentWindow.document.body.classList.remove('euiBody--hasFlyout');
};
- }, []);
+ }, [currentWindow.document.body.classList]);
/**
* ESC key closes flyout (always?)
@@ -316,7 +316,7 @@ export const EuiFlyout = forwardRef(
// Clear existing headers if necessary, e.g. switching to `false`
setFixedHeaders((headers) => (headers.length ? [] : headers));
}
- }, [includeFixedHeadersInFocusTrap, resizeRef]);
+ }, [includeFixedHeadersInFocusTrap, resizeRef, currentWindow.document]);
const focusTrapProps: EuiFlyoutProps['focusTrapProps'] = useMemo(
() => ({
From c0b29cf0cb5a7c1849e784cdbe3a85f3809d072c Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Wed, 22 May 2024 13:34:43 +0200
Subject: [PATCH 08/29] Declare context
---
packages/eui/src/services/window_event/window_event.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/eui/src/services/window_event/window_event.ts b/packages/eui/src/services/window_event/window_event.ts
index a70ff711426..a33c4140280 100644
--- a/packages/eui/src/services/window_event/window_event.ts
+++ b/packages/eui/src/services/window_event/window_event.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { Component } from 'react';
+import { Component, ContextType } from 'react';
import { EuiWindowContext, EuiWindowContextValue } from '../window_provider';
type EventNames = keyof WindowEventMap;
@@ -18,6 +18,7 @@ interface Props {
export class EuiWindowEvent extends Component> {
static contextType = EuiWindowContext;
+ declare context: ContextType;
componentDidMount() {
this.addEvent(this.props);
From 96f438a5c2216903ed0089a26e804da8ae46a2ba Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Wed, 22 May 2024 13:42:12 +0200
Subject: [PATCH 09/29] Use context declaration
---
.../src/components/focus_trap/focus_trap.tsx | 17 ++++++++++-------
.../outside_click_detector.tsx | 10 +++++-----
.../src/services/window_event/window_event.ts | 8 +++-----
3 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/packages/eui/src/components/focus_trap/focus_trap.tsx b/packages/eui/src/components/focus_trap/focus_trap.tsx
index b95dc509246..7cad41642db 100644
--- a/packages/eui/src/components/focus_trap/focus_trap.tsx
+++ b/packages/eui/src/components/focus_trap/focus_trap.tsx
@@ -6,7 +6,12 @@
* Side Public License, v 1.
*/
-import React, { Component, FunctionComponent, CSSProperties } from 'react';
+import React, {
+ Component,
+ FunctionComponent,
+ CSSProperties,
+ ContextType,
+} from 'react';
import { FocusOn } from 'react-focus-on';
import { ReactFocusOnProps } from 'react-focus-on/dist/es5/types';
import { RemoveScrollBar } from 'react-remove-scroll-bar';
@@ -111,6 +116,7 @@ class EuiFocusTrapClass extends Component {
};
static contextType = EuiWindowContext;
+ declare context: ContextType;
state: State = {
hasBeenDisabledByClick: false,
@@ -137,8 +143,7 @@ class EuiFocusTrapClass extends Component {
setInitialFocus = (initialFocus?: FocusTarget) => {
if (!initialFocus) return;
- const currentDocument = (this.context as EuiWindowContextValue).window
- .document;
+ const currentDocument = this.context.window.document;
const node = findElementBySelectorOrRef(initialFocus, currentDocument);
if (!node) return;
// `data-autofocus` is part of the 'react-focus-on' API
@@ -153,15 +158,13 @@ class EuiFocusTrapClass extends Component {
};
addMouseupListener = () => {
- const currentDocument = (this.context as EuiWindowContextValue).window
- .document;
+ const currentDocument = this.context.window.document;
currentDocument.addEventListener('mouseup', this.onMouseupOutside);
currentDocument.addEventListener('touchend', this.onMouseupOutside);
};
removeMouseupListener = () => {
- const currentDocument = (this.context as EuiWindowContextValue).window
- .document;
+ const currentDocument = this.context.window.document;
currentDocument.removeEventListener('mouseup', this.onMouseupOutside);
currentDocument.removeEventListener('touchend', this.onMouseupOutside);
};
diff --git a/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx b/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx
index d72808800a4..2a83acef947 100644
--- a/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx
+++ b/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx
@@ -13,9 +13,10 @@ import {
EventHandler,
MouseEvent as ReactMouseEvent,
ReactElement,
+ ContextType,
} from 'react';
import { htmlIdGenerator } from '../../services/accessibility';
-import { EuiWindowContext, EuiWindowContextValue } from '../../services';
+import { EuiWindowContext } from '../../services';
export interface EuiEvent extends Event {
euiGeneratedBy: string[];
@@ -47,6 +48,7 @@ export class EuiOutsideClickDetector extends Component;
private id: string;
@@ -99,15 +101,13 @@ export class EuiOutsideClickDetector extends Component extends Component> {
}
addEvent({ event, handler }: Props) {
- const currentWindow = (this.context as EuiWindowContextValue).window;
- currentWindow.addEventListener(event, handler);
+ this.context.window.addEventListener(event, handler);
}
removeEvent({ event, handler }: Props) {
- const currentWindow = (this.context as EuiWindowContextValue).window;
- currentWindow.removeEventListener(event, handler);
+ this.context.window.removeEventListener(event, handler);
}
render() {
From 6555c1a47a2caede4f1862289e2bd7691ca6fa1b Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Wed, 22 May 2024 13:43:01 +0200
Subject: [PATCH 10/29] Delete unused import
---
packages/eui/src/components/focus_trap/focus_trap.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/eui/src/components/focus_trap/focus_trap.tsx b/packages/eui/src/components/focus_trap/focus_trap.tsx
index 7cad41642db..8f8b7c326e3 100644
--- a/packages/eui/src/components/focus_trap/focus_trap.tsx
+++ b/packages/eui/src/components/focus_trap/focus_trap.tsx
@@ -21,7 +21,6 @@ import {
findElementBySelectorOrRef,
ElementTarget,
EuiWindowContext,
- EuiWindowContextValue,
} from '../../services';
import { usePropsWithComponentDefaults } from '../provider/component_defaults';
From cfbedf28a8273e11e1dcf8a0e127f7a51c3869cd Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Wed, 22 May 2024 15:03:59 +0200
Subject: [PATCH 11/29] Make default window object resolved at runtime to fix
tests
---
packages/eui/i18ntokens.json | 24 +++++++++----------
.../src/components/focus_trap/focus_trap.tsx | 6 ++---
.../outside_click_detector.tsx | 4 ++--
.../src/services/window_event/window_event.ts | 4 ++--
.../src/services/window_provider/context.ts | 6 +++--
.../eui/src/services/window_provider/hooks.ts | 2 +-
6 files changed, 24 insertions(+), 22 deletions(-)
diff --git a/packages/eui/i18ntokens.json b/packages/eui/i18ntokens.json
index 0e0ce9e4488..4281b27c3fe 100644
--- a/packages/eui/i18ntokens.json
+++ b/packages/eui/i18ntokens.json
@@ -4685,14 +4685,14 @@
"highlighting": "string",
"loc": {
"start": {
- "line": 338,
+ "line": 341,
"column": 14,
- "index": 11045
+ "index": 11167
},
"end": {
- "line": 341,
+ "line": 344,
"column": 16,
- "index": 11260
+ "index": 11382
}
},
"filepath": "src/components/flyout/flyout.tsx"
@@ -4703,14 +4703,14 @@
"highlighting": "string",
"loc": {
"start": {
- "line": 343,
+ "line": 346,
"column": 14,
- "index": 11293
+ "index": 11415
},
"end": {
- "line": 346,
+ "line": 349,
"column": 16,
- "index": 11471
+ "index": 11593
}
},
"filepath": "src/components/flyout/flyout.tsx"
@@ -4721,14 +4721,14 @@
"highlighting": "string",
"loc": {
"start": {
- "line": 349,
+ "line": 352,
"column": 14,
- "index": 11548
+ "index": 11670
},
"end": {
- "line": 352,
+ "line": 355,
"column": 16,
- "index": 11741
+ "index": 11863
}
},
"filepath": "src/components/flyout/flyout.tsx"
diff --git a/packages/eui/src/components/focus_trap/focus_trap.tsx b/packages/eui/src/components/focus_trap/focus_trap.tsx
index 8f8b7c326e3..460b9c6626b 100644
--- a/packages/eui/src/components/focus_trap/focus_trap.tsx
+++ b/packages/eui/src/components/focus_trap/focus_trap.tsx
@@ -142,7 +142,7 @@ class EuiFocusTrapClass extends Component {
setInitialFocus = (initialFocus?: FocusTarget) => {
if (!initialFocus) return;
- const currentDocument = this.context.window.document;
+ const currentDocument = (this.context.window ?? window).document;
const node = findElementBySelectorOrRef(initialFocus, currentDocument);
if (!node) return;
// `data-autofocus` is part of the 'react-focus-on' API
@@ -157,13 +157,13 @@ class EuiFocusTrapClass extends Component {
};
addMouseupListener = () => {
- const currentDocument = this.context.window.document;
+ const currentDocument = (this.context.window ?? window).document;
currentDocument.addEventListener('mouseup', this.onMouseupOutside);
currentDocument.addEventListener('touchend', this.onMouseupOutside);
};
removeMouseupListener = () => {
- const currentDocument = this.context.window.document;
+ const currentDocument = (this.context.window ?? window).document;
currentDocument.removeEventListener('mouseup', this.onMouseupOutside);
currentDocument.removeEventListener('touchend', this.onMouseupOutside);
};
diff --git a/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx b/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx
index 2a83acef947..ae544f9c9b8 100644
--- a/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx
+++ b/packages/eui/src/components/outside_click_detector/outside_click_detector.tsx
@@ -101,13 +101,13 @@ export class EuiOutsideClickDetector extends Component extends Component> {
}
addEvent({ event, handler }: Props) {
- this.context.window.addEventListener(event, handler);
+ (this.context.window ?? window).addEventListener(event, handler);
}
removeEvent({ event, handler }: Props) {
- this.context.window.removeEventListener(event, handler);
+ (this.context.window ?? window).removeEventListener(event, handler);
}
render() {
diff --git a/packages/eui/src/services/window_provider/context.ts b/packages/eui/src/services/window_provider/context.ts
index 6cc7099a873..742025e6e56 100644
--- a/packages/eui/src/services/window_provider/context.ts
+++ b/packages/eui/src/services/window_provider/context.ts
@@ -8,10 +8,12 @@
import { createContext } from 'react';
+// If 'window' field is undefined, fallback to global window object at runtime.
+// This is compatible with jsdom tests.
export interface EuiWindowContextValue {
- window: Window;
+ window?: Window;
}
export const EuiWindowContext = createContext({
- window: window,
+ window: undefined,
});
diff --git a/packages/eui/src/services/window_provider/hooks.ts b/packages/eui/src/services/window_provider/hooks.ts
index a39c5d711f8..1df9c4d2256 100644
--- a/packages/eui/src/services/window_provider/hooks.ts
+++ b/packages/eui/src/services/window_provider/hooks.ts
@@ -11,5 +11,5 @@ import { EuiWindowContext } from './context';
export function useEuiWindow() {
const context = useContext(EuiWindowContext);
- return context.window;
+ return context.window ?? window;
}
From a46fea6667715184492f3871af8e99ab6b3a325d Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Wed, 22 May 2024 15:53:21 +0200
Subject: [PATCH 12/29] Fix for SSR scenario
---
packages/eui/src/components/portal/portal.tsx | 2 +-
packages/eui/src/services/window_provider/hooks.ts | 5 ++++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/packages/eui/src/components/portal/portal.tsx b/packages/eui/src/components/portal/portal.tsx
index aea8ea1cebb..f8e9f072661 100644
--- a/packages/eui/src/components/portal/portal.tsx
+++ b/packages/eui/src/components/portal/portal.tsx
@@ -81,7 +81,7 @@ export class EuiPortalClass extends Component {
if (insert == null) {
// no insertion defined, append to body
- (currentWindow ?? window).document.body.appendChild(portalNode);
+ (currentWindow?.document ?? document).body.appendChild(portalNode);
} else {
// inserting before or after an element
const { sibling, position } = insert;
diff --git a/packages/eui/src/services/window_provider/hooks.ts b/packages/eui/src/services/window_provider/hooks.ts
index 1df9c4d2256..15745364353 100644
--- a/packages/eui/src/services/window_provider/hooks.ts
+++ b/packages/eui/src/services/window_provider/hooks.ts
@@ -9,7 +9,10 @@
import { useContext } from 'react';
import { EuiWindowContext } from './context';
+/**
+ * In SSR scenarios it can return undefined as no window is available.
+ */
export function useEuiWindow() {
const context = useContext(EuiWindowContext);
- return context.window ?? window;
+ return context.window ?? (typeof window !== 'undefined' ? window : undefined);
}
From 99ed11cb884fa2b289b976b58b2681f2335fedee Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Wed, 22 May 2024 15:55:50 +0200
Subject: [PATCH 13/29] Fix problems in Flyout
---
packages/eui/src/components/flyout/flyout.tsx | 26 +++++++++----------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/packages/eui/src/components/flyout/flyout.tsx b/packages/eui/src/components/flyout/flyout.tsx
index a47f1cca9a6..d4649b8320c 100644
--- a/packages/eui/src/components/flyout/flyout.tsx
+++ b/packages/eui/src/components/flyout/flyout.tsx
@@ -204,6 +204,7 @@ export const EuiFlyout = forwardRef(
const Element = as || defaultElement;
const maskRef = useRef(null);
const currentWindow = useEuiWindow();
+ const currentDocument = currentWindow?.document ?? document;
const windowIsLargeEnoughToPush =
useIsWithinMinBreakpoint(pushMinBreakpoint);
@@ -227,23 +228,23 @@ export const EuiFlyout = forwardRef(
const paddingSide =
side === 'left' ? 'paddingInlineStart' : 'paddingInlineEnd';
- currentWindow.document.body.style[paddingSide] = `${width}px`;
+ currentDocument.body.style[paddingSide] = `${width}px`;
return () => {
- currentWindow.document.body.style[paddingSide] = '';
+ currentDocument.body.style[paddingSide] = '';
};
}
- }, [isPushed, side, width, currentWindow.document.body.style]);
+ }, [isPushed, side, width, currentDocument.body.style]);
/**
* This class doesn't actually do anything by EUI, but is nice to add for consumers (JIC)
*/
useEffect(() => {
- currentWindow.document.body.classList.add('euiBody--hasFlyout');
+ currentDocument.body.classList.add('euiBody--hasFlyout');
return () => {
// Remove the hasFlyout class when the flyout is unmounted
- currentWindow.document.body.classList.remove('euiBody--hasFlyout');
+ currentDocument.body.classList.remove('euiBody--hasFlyout');
};
- }, [currentWindow.document.body.classList]);
+ }, [currentDocument.body.classList]);
/**
* ESC key closes flyout (always?)
@@ -292,17 +293,14 @@ export const EuiFlyout = forwardRef(
* If not disabled, automatically add fixed EuiHeaders as shards
* to EuiFlyout focus traps, to prevent focus fighting
*/
- const flyoutToggle = useRef(
- currentWindow.document.activeElement
- );
+ const flyoutToggle = useRef(currentDocument.activeElement);
const [fixedHeaders, setFixedHeaders] = useState([]);
useEffect(() => {
if (includeFixedHeadersInFocusTrap) {
- const fixedHeaderEls =
- currentWindow.document.querySelectorAll(
- '.euiHeader[data-fixed-header]'
- );
+ const fixedHeaderEls = currentDocument.querySelectorAll(
+ '.euiHeader[data-fixed-header]'
+ );
setFixedHeaders(Array.from(fixedHeaderEls));
// Flyouts that are toggled from fixed headers do not have working
@@ -316,7 +314,7 @@ export const EuiFlyout = forwardRef(
// Clear existing headers if necessary, e.g. switching to `false`
setFixedHeaders((headers) => (headers.length ? [] : headers));
}
- }, [includeFixedHeadersInFocusTrap, resizeRef, currentWindow.document]);
+ }, [includeFixedHeadersInFocusTrap, resizeRef, currentDocument]);
const focusTrapProps: EuiFlyoutProps['focusTrapProps'] = useMemo(
() => ({
From 2d396428a5336639ada5aa3b79f308a40bcc7a39 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Wed, 22 May 2024 16:00:00 +0200
Subject: [PATCH 14/29] Update i18ntokens.json
---
packages/eui/i18ntokens.json | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/packages/eui/i18ntokens.json b/packages/eui/i18ntokens.json
index 4281b27c3fe..089c5460238 100644
--- a/packages/eui/i18ntokens.json
+++ b/packages/eui/i18ntokens.json
@@ -4685,14 +4685,14 @@
"highlighting": "string",
"loc": {
"start": {
- "line": 341,
+ "line": 339,
"column": 14,
- "index": 11167
+ "index": 11143
},
"end": {
- "line": 344,
+ "line": 342,
"column": 16,
- "index": 11382
+ "index": 11358
}
},
"filepath": "src/components/flyout/flyout.tsx"
@@ -4703,14 +4703,14 @@
"highlighting": "string",
"loc": {
"start": {
- "line": 346,
+ "line": 344,
"column": 14,
- "index": 11415
+ "index": 11391
},
"end": {
- "line": 349,
+ "line": 347,
"column": 16,
- "index": 11593
+ "index": 11569
}
},
"filepath": "src/components/flyout/flyout.tsx"
@@ -4721,14 +4721,14 @@
"highlighting": "string",
"loc": {
"start": {
- "line": 352,
+ "line": 350,
"column": 14,
- "index": 11670
+ "index": 11646
},
"end": {
- "line": 355,
+ "line": 353,
"column": 16,
- "index": 11863
+ "index": 11839
}
},
"filepath": "src/components/flyout/flyout.tsx"
From a9a23886673e8a4a51f01e188268b26181adcf38 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Thu, 5 Sep 2024 15:39:15 +0200
Subject: [PATCH 15/29] Add build-publish for yalc
---
packages/eui/package.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/eui/package.json b/packages/eui/package.json
index 87642938237..8d1d33932f5 100644
--- a/packages/eui/package.json
+++ b/packages/eui/package.json
@@ -19,6 +19,7 @@
"build-docs": "cross-env BABEL_MODULES=false cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=4096 webpack --config=src-docs/webpack.config.js",
"build": "yarn extract-i18n-strings && node ./scripts/compile-clean.js && node ./scripts/compile-eui.js && yarn compile-scss",
"build-pack": "yarn build && npm pack",
+ "build-publish": "yarn build && yalc publish",
"compile-icons": "node ./scripts/compile-icons.js && prettier --write --loglevel=warn \"./src/components/icon/assets/**/*.tsx\"",
"compile-scss": "node ./scripts/compile-scss.js",
"extract-i18n-strings": "node ./scripts/babel/fetch-i18n-strings",
From d90a8b5e2d6b01a2aae37990b8ef77ecb2e8f292 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Thu, 19 Sep 2024 23:50:42 +0200
Subject: [PATCH 16/29] Make tooltip component use window provider
---
.../eui/src/components/tool_tip/tool_tip.tsx | 18 +++++++++++-------
.../components/tool_tip/tool_tip_popover.tsx | 11 ++++++-----
2 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/packages/eui/src/components/tool_tip/tool_tip.tsx b/packages/eui/src/components/tool_tip/tool_tip.tsx
index 616ca2b5ab0..45f9dd47b8a 100644
--- a/packages/eui/src/components/tool_tip/tool_tip.tsx
+++ b/packages/eui/src/components/tool_tip/tool_tip.tsx
@@ -12,11 +12,12 @@ import React, {
ReactNode,
MouseEvent as ReactMouseEvent,
HTMLAttributes,
+ ContextType,
} from 'react';
import classNames from 'classnames';
import { CommonProps } from '../common';
-import { findPopoverPosition, htmlIdGenerator, keys } from '../../services';
+import { EuiWindowContext, findPopoverPosition, htmlIdGenerator, keys } from '../../services';
import { enqueueStateChange } from '../../services/react';
import { EuiResizeObserver } from '../observer/resize_observer';
import { EuiPortal } from '../portal';
@@ -142,6 +143,9 @@ export class EuiToolTip extends Component {
display: 'inlineBlock',
};
+ static contextType = EuiWindowContext;
+ declare context: ContextType;
+
clearAnimationTimeout = () => {
if (this.timeoutId) {
this.timeoutId = clearTimeout(this.timeoutId) as undefined;
@@ -151,14 +155,14 @@ export class EuiToolTip extends Component {
componentDidMount() {
this._isMounted = true;
if (this.props.repositionOnScroll) {
- window.addEventListener('scroll', this.positionToolTip, true);
+ (this.context.window ?? window).addEventListener('scroll', this.positionToolTip, true);
}
}
componentWillUnmount() {
this.clearAnimationTimeout();
this._isMounted = false;
- window.removeEventListener('scroll', this.positionToolTip, true);
+ (this.context.window ?? window).removeEventListener('scroll', this.positionToolTip, true);
}
componentDidUpdate(prevProps: EuiToolTipProps, prevState: State) {
@@ -169,9 +173,9 @@ export class EuiToolTip extends Component {
// update scroll listener
if (prevProps.repositionOnScroll !== this.props.repositionOnScroll) {
if (this.props.repositionOnScroll) {
- window.addEventListener('scroll', this.positionToolTip, true);
+ (this.context.window ?? window).addEventListener('scroll', this.positionToolTip, true);
} else {
- window.removeEventListener('scroll', this.positionToolTip, true);
+ (this.context.window ?? window).removeEventListener('scroll', this.positionToolTip, true);
}
}
}
@@ -180,7 +184,7 @@ export class EuiToolTip extends Component {
// when the tooltip is visible, this checks if the anchor is still part of document
// this fixes when the react root is removed from the dom without unmounting
// https://github.com/elastic/eui/issues/1105
- if (document.body.contains(this.anchor) === false) {
+ if ((this.context.window?.document ?? document).body.contains(this.anchor) === false) {
// the anchor is no longer part of `document`
this.hideToolTip();
} else {
@@ -231,7 +235,7 @@ export class EuiToolTip extends Component {
// To prevent this, we can orient from the right so that text line wrapping does not occur, negating
// the second resizeObserver callback call.
const windowWidth =
- document.documentElement.clientWidth || window.innerWidth;
+ (this.context.window?.document ?? document).documentElement.clientWidth || window.innerWidth;
const useRightValue = windowWidth / 2 < left;
const toolTipStyles: ToolTipStyles = {
diff --git a/packages/eui/src/components/tool_tip/tool_tip_popover.tsx b/packages/eui/src/components/tool_tip/tool_tip_popover.tsx
index a561b30b7c1..e958a0b790a 100644
--- a/packages/eui/src/components/tool_tip/tool_tip_popover.tsx
+++ b/packages/eui/src/components/tool_tip/tool_tip_popover.tsx
@@ -16,7 +16,7 @@ import React, {
} from 'react';
import classNames from 'classnames';
import { CommonProps } from '../common';
-import { useEuiTheme } from '../../services';
+import { useEuiTheme, useEuiWindow } from '../../services';
import { euiToolTipStyles } from './tool_tip.styles';
export type ToolTipPositions = 'top' | 'right' | 'bottom' | 'left';
@@ -42,6 +42,7 @@ export const EuiToolTipPopover: FunctionComponent = ({
const popover = useRef();
const euiTheme = useEuiTheme();
+ const currentWindow = useEuiWindow();
const styles = euiToolTipStyles(euiTheme);
const cssStyles = [
styles.euiToolTip,
@@ -64,12 +65,12 @@ export const EuiToolTipPopover: FunctionComponent = ({
};
useEffect(() => {
- document.body.classList.add('euiBody-hasPortalContent');
- window.addEventListener('resize', updateDimensions);
+ (currentWindow?.document ?? document).body.classList.add('euiBody-hasPortalContent');
+ (currentWindow ?? window).addEventListener('resize', updateDimensions);
return () => {
- document.body.classList.remove('euiBody-hasPortalContent');
- window.removeEventListener('resize', updateDimensions);
+ (currentWindow?.document ?? document).body.classList.remove('euiBody-hasPortalContent');
+ (currentWindow ?? window).removeEventListener('resize', updateDimensions);
};
}, [updateDimensions]);
From 8fcf539bc8248ed470f1a7b81858213e0257931f Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Fri, 20 Sep 2024 00:04:50 +0200
Subject: [PATCH 17/29] Missed one window ref
---
packages/eui/src/components/tool_tip/tool_tip.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/eui/src/components/tool_tip/tool_tip.tsx b/packages/eui/src/components/tool_tip/tool_tip.tsx
index 45f9dd47b8a..9ef3d5921c9 100644
--- a/packages/eui/src/components/tool_tip/tool_tip.tsx
+++ b/packages/eui/src/components/tool_tip/tool_tip.tsx
@@ -235,7 +235,7 @@ export class EuiToolTip extends Component {
// To prevent this, we can orient from the right so that text line wrapping does not occur, negating
// the second resizeObserver callback call.
const windowWidth =
- (this.context.window?.document ?? document).documentElement.clientWidth || window.innerWidth;
+ (this.context.window?.document ?? document).documentElement.clientWidth || (this.context.window ?? window).innerWidth;
const useRightValue = windowWidth / 2 < left;
const toolTipStyles: ToolTipStyles = {
From adf4ca7de125f63ed23b3d99a87199f4937cb62b Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Fri, 20 Sep 2024 02:05:52 +0200
Subject: [PATCH 18/29] Make popover component use window provider
---
packages/eui/src/components/popover/popover.tsx | 8 +++++++-
packages/eui/src/components/tool_tip/tool_tip.tsx | 1 +
.../services/popover/popover_positioning.test.ts | 9 +++++++++
.../src/services/popover/popover_positioning.ts | 15 +++++++++------
4 files changed, 26 insertions(+), 7 deletions(-)
diff --git a/packages/eui/src/components/popover/popover.tsx b/packages/eui/src/components/popover/popover.tsx
index e31db77517b..9089bc71335 100644
--- a/packages/eui/src/components/popover/popover.tsx
+++ b/packages/eui/src/components/popover/popover.tsx
@@ -15,6 +15,7 @@ import React, {
Ref,
RefCallback,
PropsWithChildren,
+ ContextType,
} from 'react';
import classNames from 'classnames';
import { focusable } from 'tabbable';
@@ -28,6 +29,7 @@ import {
getWaitDuration,
performOnFrame,
htmlIdGenerator,
+ EuiWindowContext,
} from '../../services';
import { setMultipleRefs } from '../../services/hooks/useCombinedRefs';
@@ -299,6 +301,9 @@ export class EuiPopover extends Component {
display: 'inline-block',
};
+ static contextType = EuiWindowContext;
+ declare context: ContextType;
+
static getDerivedStateFromProps(
nextProps: Props,
prevState: State
@@ -546,6 +551,7 @@ export class EuiPopover extends Component {
returnBoundingBox: this.props.attachToAnchor,
allowCrossAxis: this.props.repositionToCrossAxis,
buffer: this.props.buffer,
+ currentWindow: this.context.window ?? window
});
// the popover's z-index must inherit from the button
@@ -554,7 +560,7 @@ export class EuiPopover extends Component {
const { zIndex: zIndexProp } = this.props;
const zIndex =
zIndexProp == null
- ? getElementZIndex(this.button, this.panel) + 2000
+ ? getElementZIndex(this.button, this.panel, this.context.window ?? window) + 2000
: zIndexProp;
const popoverStyles = {
diff --git a/packages/eui/src/components/tool_tip/tool_tip.tsx b/packages/eui/src/components/tool_tip/tool_tip.tsx
index 9ef3d5921c9..ea9b8db6e69 100644
--- a/packages/eui/src/components/tool_tip/tool_tip.tsx
+++ b/packages/eui/src/components/tool_tip/tool_tip.tsx
@@ -226,6 +226,7 @@ export class EuiToolTip extends Component {
arrowWidth: 12,
arrowBuffer: 4,
},
+ currentWindow: this.context.window ?? window
});
// If encroaching the right edge of the window:
diff --git a/packages/eui/src/services/popover/popover_positioning.test.ts b/packages/eui/src/services/popover/popover_positioning.test.ts
index bae2c9d40ec..68597182eaf 100644
--- a/packages/eui/src/services/popover/popover_positioning.test.ts
+++ b/packages/eui/src/services/popover/popover_positioning.test.ts
@@ -486,6 +486,7 @@ describe('popover_positioning', () => {
popover,
container,
offset: 7,
+ currentWindow: window,
})
).toEqual({
fit: 1,
@@ -518,6 +519,7 @@ describe('popover_positioning', () => {
popover,
container,
offset: 5,
+ currentWindow: window,
})
).toEqual({
fit: 1,
@@ -550,6 +552,7 @@ describe('popover_positioning', () => {
popover,
container,
offset: 5,
+ currentWindow: window,
})
).toEqual({
fit: 1,
@@ -581,6 +584,7 @@ describe('popover_positioning', () => {
popover,
container,
offset: 5,
+ currentWindow: window,
})
).toEqual({
fit: 1,
@@ -612,6 +616,7 @@ describe('popover_positioning', () => {
popover,
container,
offset: 5,
+ currentWindow: window,
})
).toEqual({
fit: 0,
@@ -644,6 +649,7 @@ describe('popover_positioning', () => {
popover,
container,
offset: 5,
+ currentWindow: window,
})
).toEqual({
fit: 1,
@@ -680,6 +686,7 @@ describe('popover_positioning', () => {
popover,
container,
offset: 7,
+ currentWindow: window,
})
).toEqual({
fit: 1,
@@ -711,6 +718,7 @@ describe('popover_positioning', () => {
popover,
container,
allowCrossAxis: false,
+ currentWindow: window,
})
).toEqual({
fit: 0.34,
@@ -736,6 +744,7 @@ describe('popover_positioning', () => {
returnBoundingBox: true,
anchor,
popover,
+ currentWindow: window,
})
).toEqual({
fit: 1,
diff --git a/packages/eui/src/services/popover/popover_positioning.ts b/packages/eui/src/services/popover/popover_positioning.ts
index 8cc1a131600..8cb039310ac 100644
--- a/packages/eui/src/services/popover/popover_positioning.ts
+++ b/packages/eui/src/services/popover/popover_positioning.ts
@@ -76,6 +76,7 @@ interface FindPopoverPositionArgs {
container?: HTMLElement;
arrowConfig?: { arrowWidth: number; arrowBuffer: number };
returnBoundingBox?: boolean;
+ currentWindow: Window;
}
interface FindPopoverPositionResult {
@@ -129,6 +130,7 @@ export function findPopoverPosition({
container,
arrowConfig,
returnBoundingBox,
+ currentWindow,
}: FindPopoverPositionArgs): FindPopoverPositionResult {
// find the screen-relative bounding boxes of the anchor, popover, and container
const anchorBoundingBox = getElementBoundingBox(anchor);
@@ -138,9 +140,9 @@ export function findPopoverPosition({
// window.(innerWidth|innerHeight) do not account for scrollbars
// so prefer the clientWidth/clientHeight of the DOM if available
const documentWidth =
- document.documentElement.clientWidth || window.innerWidth;
+ (currentWindow ?? window).document.documentElement.clientWidth || (currentWindow ?? window).innerWidth;
const documentHeight =
- document.documentElement.clientHeight || window.innerHeight;
+ (currentWindow ?? window).document.documentElement.clientHeight || (currentWindow ?? window).innerHeight;
const windowBoundingBox: EuiClientRect = {
top: 0,
right: documentWidth,
@@ -224,8 +226,8 @@ export function findPopoverPosition({
bestPosition = {
fit: screenCoordinates.fit,
position: iterationPosition,
- top: screenCoordinates.top + window.pageYOffset,
- left: screenCoordinates.left + window.pageXOffset,
+ top: screenCoordinates.top + (currentWindow ?? window).pageYOffset,
+ left: screenCoordinates.left + (currentWindow ?? window).pageXOffset,
arrow: screenCoordinates.arrow,
};
@@ -738,7 +740,8 @@ export function intersectBoundingBoxes(
*/
export function getElementZIndex(
element: HTMLElement,
- cousin: HTMLElement
+ cousin: HTMLElement,
+ currentWindow: Window
): number {
/**
* finding the z-index of `element` is not the full story
@@ -785,7 +788,7 @@ export function getElementZIndex(
for (const node of nodesToInspect) {
// get this node's z-index css value
- const zIndex = window.document
+ const zIndex = (currentWindow ?? window).document
.defaultView!.getComputedStyle(node)
.getPropertyValue('z-index');
From 3f5a87126658672a0b53664f226146442daeeffe Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Sat, 21 Sep 2024 01:24:32 +0200
Subject: [PATCH 19/29] Use current window for Popover and Portal
---
.../src/components/popover/input_popover.tsx | 9 ++++---
.../eui/src/components/popover/popover.tsx | 26 ++++++++++---------
.../popover/wrapping_popover.stories.tsx | 4 ++-
packages/eui/src/components/portal/portal.tsx | 2 +-
4 files changed, 23 insertions(+), 18 deletions(-)
diff --git a/packages/eui/src/components/popover/input_popover.tsx b/packages/eui/src/components/popover/input_popover.tsx
index a852ec2a6a0..3ecc986b492 100644
--- a/packages/eui/src/components/popover/input_popover.tsx
+++ b/packages/eui/src/components/popover/input_popover.tsx
@@ -22,7 +22,7 @@ import classnames from 'classnames';
import { tabbable } from 'tabbable';
import { logicalCSS } from '../../global_styling';
-import { keys, useCombinedRefs, useEuiTheme } from '../../services';
+import { keys, useCombinedRefs, useEuiTheme, useEuiWindow } from '../../services';
import { CommonProps } from '../common';
import { useResizeObserver } from '../observer/resize_observer';
import { EuiFocusTrap } from '../focus_trap';
@@ -84,6 +84,7 @@ export const EuiInputPopover: FunctionComponent = ({
const classes = classnames('euiInputPopover', className);
const euiTheme = useEuiTheme();
const formMaxWidth = euiFormMaxWidth(euiTheme);
+ const currentWindow = useEuiWindow();
/**
* Ref setup
@@ -151,7 +152,7 @@ export const EuiInputPopover: FunctionComponent = ({
if (!tabbableItems.length) return;
const tabbingFromLastItemInPopover =
- document.activeElement === tabbableItems[tabbableItems.length - 1];
+ (currentWindow ?? window).document.activeElement === tabbableItems[tabbableItems.length - 1];
if (tabbingFromLastItemInPopover) {
closePopover();
@@ -193,14 +194,14 @@ export const EuiInputPopover: FunctionComponent = ({
// Kibana Cypress tests trigger a scroll event in many common situations when the options list div is appended
// to the DOM; in testing it was always within 100ms, but setting a timeout here for 500ms to be safe
const timeoutId = setTimeout(() => {
- window.addEventListener('scroll', closePopoverOnScroll, {
+ (currentWindow ?? window).addEventListener('scroll', closePopoverOnScroll, {
passive: true, // for better performance as we won't call preventDefault
capture: true, // scroll events don't bubble, they must be captured instead
});
}, 500);
return () => {
- window.removeEventListener('scroll', closePopoverOnScroll, {
+ (currentWindow ?? window).removeEventListener('scroll', closePopoverOnScroll, {
capture: true,
});
clearTimeout(timeoutId);
diff --git a/packages/eui/src/components/popover/popover.tsx b/packages/eui/src/components/popover/popover.tsx
index 9089bc71335..a5d25917834 100644
--- a/packages/eui/src/components/popover/popover.tsx
+++ b/packages/eui/src/components/popover/popover.tsx
@@ -371,14 +371,16 @@ export class EuiPopover extends Component {
};
handleStrandedFocus = () => {
- this.strandedFocusTimeout = window.setTimeout(() => {
+ const currentWindow = (this.context.window ?? window);
+ this.strandedFocusTimeout = currentWindow.setTimeout(() => {
// If `returnFocus` failed and focus was stranded,
// attempt to manually restore focus to the toggle button.
// The stranded focus is either in most cases on body but
// it will be on the panel instead on mount when isOpen=true
+ const currentDocument = currentWindow.document;
if (
- document.activeElement === document.body ||
- document.activeElement === this.panel
+ currentDocument.activeElement === currentDocument.body ||
+ currentDocument.activeElement === this.panel
) {
if (!this.button) return;
@@ -413,7 +415,7 @@ export class EuiPopover extends Component {
}
// We need to set this state a beat after the render takes place, so that the CSS
// transition can take effect.
- this.closingTransitionAnimationFrame = window.requestAnimationFrame(() => {
+ this.closingTransitionAnimationFrame = (this.context.window ?? window).requestAnimationFrame(() => {
this.setState({
isOpening: true,
});
@@ -438,7 +440,7 @@ export class EuiPopover extends Component {
);
clearTimeout(this.respositionTimeout);
- this.respositionTimeout = window.setTimeout(() => {
+ this.respositionTimeout = (this.context.window ?? window).setTimeout(() => {
this.setState({ isOpenStable: true }, () => {
this.positionPopoverFixed();
});
@@ -455,7 +457,7 @@ export class EuiPopover extends Component {
}
if (this.props.repositionOnScroll) {
- window.addEventListener('scroll', this.positionPopoverFixed, true);
+ (this.context.window ?? window).addEventListener('scroll', this.positionPopoverFixed, true);
}
}
@@ -479,9 +481,9 @@ export class EuiPopover extends Component {
// update scroll listener
if (prevProps.repositionOnScroll !== this.props.repositionOnScroll) {
if (this.props.repositionOnScroll) {
- window.addEventListener('scroll', this.positionPopoverFixed, true);
+ (this.context.window ?? window).addEventListener('scroll', this.positionPopoverFixed, true);
} else {
- window.removeEventListener('scroll', this.positionPopoverFixed, true);
+ (this.context.window ?? window).removeEventListener('scroll', this.positionPopoverFixed, true);
}
}
@@ -489,7 +491,7 @@ export class EuiPopover extends Component {
if (prevProps.isOpen && !this.props.isOpen) {
// If the user has just closed the popover, queue up the removal of the content after the
// transition is complete.
- this.closingTransitionTimeout = window.setTimeout(() => {
+ this.closingTransitionTimeout = (this.context.window ?? window).setTimeout(() => {
this.setState({
isClosing: false,
});
@@ -498,7 +500,7 @@ export class EuiPopover extends Component {
}
componentWillUnmount() {
- window.removeEventListener('scroll', this.positionPopoverFixed, true);
+ (this.context.window ?? window).removeEventListener('scroll', this.positionPopoverFixed, true);
clearTimeout(this.respositionTimeout);
clearTimeout(this.strandedFocusTimeout);
clearTimeout(this.closingTransitionTimeout);
@@ -605,11 +607,11 @@ export class EuiPopover extends Component {
openPosition: null,
isOpenStable: false,
});
- window.removeEventListener('resize', this.positionPopoverFluid);
+ (this.context.window ?? window).removeEventListener('resize', this.positionPopoverFluid);
} else {
// panel is coming into existence
this.positionPopoverFluid();
- window.addEventListener('resize', this.positionPopoverFluid);
+ (this.context.window ?? window).addEventListener('resize', this.positionPopoverFluid);
}
};
diff --git a/packages/eui/src/components/popover/wrapping_popover.stories.tsx b/packages/eui/src/components/popover/wrapping_popover.stories.tsx
index 238429e63d6..d78ae10abf3 100644
--- a/packages/eui/src/components/popover/wrapping_popover.stories.tsx
+++ b/packages/eui/src/components/popover/wrapping_popover.stories.tsx
@@ -24,6 +24,7 @@ import {
EuiWrappingPopover,
EuiWrappingPopoverProps,
} from './wrapping_popover';
+import { useEuiWindow } from 'src/services';
// NOTE: extended EuiPopoverProps are not resolved for some reason
// so we are currently manually adding them back
@@ -87,6 +88,7 @@ const StatefulPopover = ({
...rest
}: EuiWrappingPopoverProps) => {
const [isOpen, setOpen] = useState(_isOpen);
+ const currentWindow = useEuiWindow();
const handleOnClose = () => {
setOpen(false);
@@ -115,7 +117,7 @@ const StatefulPopover = ({
{isOpen && (
diff --git a/packages/eui/src/components/portal/portal.tsx b/packages/eui/src/components/portal/portal.tsx
index f8e9f072661..eb76be85549 100644
--- a/packages/eui/src/components/portal/portal.tsx
+++ b/packages/eui/src/components/portal/portal.tsx
@@ -76,7 +76,7 @@ export class EuiPortalClass extends Component {
componentDidMount() {
const { insert, currentWindow } = this.props;
- const portalNode = document.createElement('div');
+ const portalNode = (currentWindow?.document ?? document).createElement('div');
portalNode.dataset.euiportal = 'true';
if (insert == null) {
From 11b93e4d967e929f28ba94455cd9991c993d4f0c Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Sat, 21 Sep 2024 03:01:21 +0200
Subject: [PATCH 20/29] Apply formatting
---
.../src/components/popover/input_popover.tsx | 32 ++++++++----
.../eui/src/components/popover/popover.tsx | 52 +++++++++++++++----
packages/eui/src/components/portal/portal.tsx | 4 +-
.../eui/src/components/tool_tip/tool_tip.tsx | 41 ++++++++++++---
.../components/tool_tip/tool_tip_popover.tsx | 8 ++-
5 files changed, 106 insertions(+), 31 deletions(-)
diff --git a/packages/eui/src/components/popover/input_popover.tsx b/packages/eui/src/components/popover/input_popover.tsx
index 3ecc986b492..8ec0ff58904 100644
--- a/packages/eui/src/components/popover/input_popover.tsx
+++ b/packages/eui/src/components/popover/input_popover.tsx
@@ -22,7 +22,12 @@ import classnames from 'classnames';
import { tabbable } from 'tabbable';
import { logicalCSS } from '../../global_styling';
-import { keys, useCombinedRefs, useEuiTheme, useEuiWindow } from '../../services';
+import {
+ keys,
+ useCombinedRefs,
+ useEuiTheme,
+ useEuiWindow,
+} from '../../services';
import { CommonProps } from '../common';
import { useResizeObserver } from '../observer/resize_observer';
import { EuiFocusTrap } from '../focus_trap';
@@ -152,7 +157,8 @@ export const EuiInputPopover: FunctionComponent = ({
if (!tabbableItems.length) return;
const tabbingFromLastItemInPopover =
- (currentWindow ?? window).document.activeElement === tabbableItems[tabbableItems.length - 1];
+ (currentWindow ?? window).document.activeElement ===
+ tabbableItems[tabbableItems.length - 1];
if (tabbingFromLastItemInPopover) {
closePopover();
@@ -194,16 +200,24 @@ export const EuiInputPopover: FunctionComponent = ({
// Kibana Cypress tests trigger a scroll event in many common situations when the options list div is appended
// to the DOM; in testing it was always within 100ms, but setting a timeout here for 500ms to be safe
const timeoutId = setTimeout(() => {
- (currentWindow ?? window).addEventListener('scroll', closePopoverOnScroll, {
- passive: true, // for better performance as we won't call preventDefault
- capture: true, // scroll events don't bubble, they must be captured instead
- });
+ (currentWindow ?? window).addEventListener(
+ 'scroll',
+ closePopoverOnScroll,
+ {
+ passive: true, // for better performance as we won't call preventDefault
+ capture: true, // scroll events don't bubble, they must be captured instead
+ }
+ );
}, 500);
return () => {
- (currentWindow ?? window).removeEventListener('scroll', closePopoverOnScroll, {
- capture: true,
- });
+ (currentWindow ?? window).removeEventListener(
+ 'scroll',
+ closePopoverOnScroll,
+ {
+ capture: true,
+ }
+ );
clearTimeout(timeoutId);
};
}
diff --git a/packages/eui/src/components/popover/popover.tsx b/packages/eui/src/components/popover/popover.tsx
index a5d25917834..54a198a61ce 100644
--- a/packages/eui/src/components/popover/popover.tsx
+++ b/packages/eui/src/components/popover/popover.tsx
@@ -371,7 +371,7 @@ export class EuiPopover extends Component {
};
handleStrandedFocus = () => {
- const currentWindow = (this.context.window ?? window);
+ const currentWindow = this.context.window ?? window;
this.strandedFocusTimeout = currentWindow.setTimeout(() => {
// If `returnFocus` failed and focus was stranded,
// attempt to manually restore focus to the toggle button.
@@ -415,7 +415,9 @@ export class EuiPopover extends Component {
}
// We need to set this state a beat after the render takes place, so that the CSS
// transition can take effect.
- this.closingTransitionAnimationFrame = (this.context.window ?? window).requestAnimationFrame(() => {
+ this.closingTransitionAnimationFrame = (
+ this.context.window ?? window
+ ).requestAnimationFrame(() => {
this.setState({
isOpening: true,
});
@@ -457,7 +459,11 @@ export class EuiPopover extends Component {
}
if (this.props.repositionOnScroll) {
- (this.context.window ?? window).addEventListener('scroll', this.positionPopoverFixed, true);
+ (this.context.window ?? window).addEventListener(
+ 'scroll',
+ this.positionPopoverFixed,
+ true
+ );
}
}
@@ -481,9 +487,17 @@ export class EuiPopover extends Component {
// update scroll listener
if (prevProps.repositionOnScroll !== this.props.repositionOnScroll) {
if (this.props.repositionOnScroll) {
- (this.context.window ?? window).addEventListener('scroll', this.positionPopoverFixed, true);
+ (this.context.window ?? window).addEventListener(
+ 'scroll',
+ this.positionPopoverFixed,
+ true
+ );
} else {
- (this.context.window ?? window).removeEventListener('scroll', this.positionPopoverFixed, true);
+ (this.context.window ?? window).removeEventListener(
+ 'scroll',
+ this.positionPopoverFixed,
+ true
+ );
}
}
@@ -491,7 +505,9 @@ export class EuiPopover extends Component {
if (prevProps.isOpen && !this.props.isOpen) {
// If the user has just closed the popover, queue up the removal of the content after the
// transition is complete.
- this.closingTransitionTimeout = (this.context.window ?? window).setTimeout(() => {
+ this.closingTransitionTimeout = (
+ this.context.window ?? window
+ ).setTimeout(() => {
this.setState({
isClosing: false,
});
@@ -500,7 +516,11 @@ export class EuiPopover extends Component {
}
componentWillUnmount() {
- (this.context.window ?? window).removeEventListener('scroll', this.positionPopoverFixed, true);
+ (this.context.window ?? window).removeEventListener(
+ 'scroll',
+ this.positionPopoverFixed,
+ true
+ );
clearTimeout(this.respositionTimeout);
clearTimeout(this.strandedFocusTimeout);
clearTimeout(this.closingTransitionTimeout);
@@ -553,7 +573,7 @@ export class EuiPopover extends Component {
returnBoundingBox: this.props.attachToAnchor,
allowCrossAxis: this.props.repositionToCrossAxis,
buffer: this.props.buffer,
- currentWindow: this.context.window ?? window
+ currentWindow: this.context.window ?? window,
});
// the popover's z-index must inherit from the button
@@ -562,7 +582,11 @@ export class EuiPopover extends Component {
const { zIndex: zIndexProp } = this.props;
const zIndex =
zIndexProp == null
- ? getElementZIndex(this.button, this.panel, this.context.window ?? window) + 2000
+ ? getElementZIndex(
+ this.button,
+ this.panel,
+ this.context.window ?? window
+ ) + 2000
: zIndexProp;
const popoverStyles = {
@@ -607,11 +631,17 @@ export class EuiPopover extends Component {
openPosition: null,
isOpenStable: false,
});
- (this.context.window ?? window).removeEventListener('resize', this.positionPopoverFluid);
+ (this.context.window ?? window).removeEventListener(
+ 'resize',
+ this.positionPopoverFluid
+ );
} else {
// panel is coming into existence
this.positionPopoverFluid();
- (this.context.window ?? window).addEventListener('resize', this.positionPopoverFluid);
+ (this.context.window ?? window).addEventListener(
+ 'resize',
+ this.positionPopoverFluid
+ );
}
};
diff --git a/packages/eui/src/components/portal/portal.tsx b/packages/eui/src/components/portal/portal.tsx
index eb76be85549..73de9d6bda5 100644
--- a/packages/eui/src/components/portal/portal.tsx
+++ b/packages/eui/src/components/portal/portal.tsx
@@ -76,7 +76,9 @@ export class EuiPortalClass extends Component {
componentDidMount() {
const { insert, currentWindow } = this.props;
- const portalNode = (currentWindow?.document ?? document).createElement('div');
+ const portalNode = (currentWindow?.document ?? document).createElement(
+ 'div'
+ );
portalNode.dataset.euiportal = 'true';
if (insert == null) {
diff --git a/packages/eui/src/components/tool_tip/tool_tip.tsx b/packages/eui/src/components/tool_tip/tool_tip.tsx
index ea9b8db6e69..464f1a1ee20 100644
--- a/packages/eui/src/components/tool_tip/tool_tip.tsx
+++ b/packages/eui/src/components/tool_tip/tool_tip.tsx
@@ -17,7 +17,12 @@ import React, {
import classNames from 'classnames';
import { CommonProps } from '../common';
-import { EuiWindowContext, findPopoverPosition, htmlIdGenerator, keys } from '../../services';
+import {
+ EuiWindowContext,
+ findPopoverPosition,
+ htmlIdGenerator,
+ keys,
+} from '../../services';
import { enqueueStateChange } from '../../services/react';
import { EuiResizeObserver } from '../observer/resize_observer';
import { EuiPortal } from '../portal';
@@ -155,14 +160,22 @@ export class EuiToolTip extends Component {
componentDidMount() {
this._isMounted = true;
if (this.props.repositionOnScroll) {
- (this.context.window ?? window).addEventListener('scroll', this.positionToolTip, true);
+ (this.context.window ?? window).addEventListener(
+ 'scroll',
+ this.positionToolTip,
+ true
+ );
}
}
componentWillUnmount() {
this.clearAnimationTimeout();
this._isMounted = false;
- (this.context.window ?? window).removeEventListener('scroll', this.positionToolTip, true);
+ (this.context.window ?? window).removeEventListener(
+ 'scroll',
+ this.positionToolTip,
+ true
+ );
}
componentDidUpdate(prevProps: EuiToolTipProps, prevState: State) {
@@ -173,9 +186,17 @@ export class EuiToolTip extends Component {
// update scroll listener
if (prevProps.repositionOnScroll !== this.props.repositionOnScroll) {
if (this.props.repositionOnScroll) {
- (this.context.window ?? window).addEventListener('scroll', this.positionToolTip, true);
+ (this.context.window ?? window).addEventListener(
+ 'scroll',
+ this.positionToolTip,
+ true
+ );
} else {
- (this.context.window ?? window).removeEventListener('scroll', this.positionToolTip, true);
+ (this.context.window ?? window).removeEventListener(
+ 'scroll',
+ this.positionToolTip,
+ true
+ );
}
}
}
@@ -184,7 +205,10 @@ export class EuiToolTip extends Component {
// when the tooltip is visible, this checks if the anchor is still part of document
// this fixes when the react root is removed from the dom without unmounting
// https://github.com/elastic/eui/issues/1105
- if ((this.context.window?.document ?? document).body.contains(this.anchor) === false) {
+ if (
+ (this.context.window?.document ?? document).body.contains(this.anchor) ===
+ false
+ ) {
// the anchor is no longer part of `document`
this.hideToolTip();
} else {
@@ -226,7 +250,7 @@ export class EuiToolTip extends Component {
arrowWidth: 12,
arrowBuffer: 4,
},
- currentWindow: this.context.window ?? window
+ currentWindow: this.context.window ?? window,
});
// If encroaching the right edge of the window:
@@ -236,7 +260,8 @@ export class EuiToolTip extends Component {
// To prevent this, we can orient from the right so that text line wrapping does not occur, negating
// the second resizeObserver callback call.
const windowWidth =
- (this.context.window?.document ?? document).documentElement.clientWidth || (this.context.window ?? window).innerWidth;
+ (this.context.window?.document ?? document).documentElement.clientWidth ||
+ (this.context.window ?? window).innerWidth;
const useRightValue = windowWidth / 2 < left;
const toolTipStyles: ToolTipStyles = {
diff --git a/packages/eui/src/components/tool_tip/tool_tip_popover.tsx b/packages/eui/src/components/tool_tip/tool_tip_popover.tsx
index e958a0b790a..643a15ce6fa 100644
--- a/packages/eui/src/components/tool_tip/tool_tip_popover.tsx
+++ b/packages/eui/src/components/tool_tip/tool_tip_popover.tsx
@@ -65,11 +65,15 @@ export const EuiToolTipPopover: FunctionComponent = ({
};
useEffect(() => {
- (currentWindow?.document ?? document).body.classList.add('euiBody-hasPortalContent');
+ (currentWindow?.document ?? document).body.classList.add(
+ 'euiBody-hasPortalContent'
+ );
(currentWindow ?? window).addEventListener('resize', updateDimensions);
return () => {
- (currentWindow?.document ?? document).body.classList.remove('euiBody-hasPortalContent');
+ (currentWindow?.document ?? document).body.classList.remove(
+ 'euiBody-hasPortalContent'
+ );
(currentWindow ?? window).removeEventListener('resize', updateDimensions);
};
}, [updateDimensions]);
From cdffdc8b89e6c4a17d3584edaf9469e21475d66c Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Sat, 21 Sep 2024 03:03:23 +0200
Subject: [PATCH 21/29] Apply formatting
---
.../eui/src/components/popover/wrapping_popover.stories.tsx | 4 +++-
packages/eui/src/services/popover/popover_positioning.ts | 6 ++++--
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/packages/eui/src/components/popover/wrapping_popover.stories.tsx b/packages/eui/src/components/popover/wrapping_popover.stories.tsx
index d78ae10abf3..3e5e66fa9e7 100644
--- a/packages/eui/src/components/popover/wrapping_popover.stories.tsx
+++ b/packages/eui/src/components/popover/wrapping_popover.stories.tsx
@@ -117,7 +117,9 @@ const StatefulPopover = ({
{isOpen && (
diff --git a/packages/eui/src/services/popover/popover_positioning.ts b/packages/eui/src/services/popover/popover_positioning.ts
index 8cb039310ac..c8667032c08 100644
--- a/packages/eui/src/services/popover/popover_positioning.ts
+++ b/packages/eui/src/services/popover/popover_positioning.ts
@@ -140,9 +140,11 @@ export function findPopoverPosition({
// window.(innerWidth|innerHeight) do not account for scrollbars
// so prefer the clientWidth/clientHeight of the DOM if available
const documentWidth =
- (currentWindow ?? window).document.documentElement.clientWidth || (currentWindow ?? window).innerWidth;
+ (currentWindow ?? window).document.documentElement.clientWidth ||
+ (currentWindow ?? window).innerWidth;
const documentHeight =
- (currentWindow ?? window).document.documentElement.clientHeight || (currentWindow ?? window).innerHeight;
+ (currentWindow ?? window).document.documentElement.clientHeight ||
+ (currentWindow ?? window).innerHeight;
const windowBoundingBox: EuiClientRect = {
top: 0,
right: documentWidth,
From 0d48da69b9d95ea5d72d1cd13e3aaa28d5280449 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Sat, 21 Sep 2024 03:05:22 +0200
Subject: [PATCH 22/29] Fix import
---
.../eui/src/components/popover/wrapping_popover.stories.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/eui/src/components/popover/wrapping_popover.stories.tsx b/packages/eui/src/components/popover/wrapping_popover.stories.tsx
index 3e5e66fa9e7..7fe5bb88963 100644
--- a/packages/eui/src/components/popover/wrapping_popover.stories.tsx
+++ b/packages/eui/src/components/popover/wrapping_popover.stories.tsx
@@ -24,7 +24,7 @@ import {
EuiWrappingPopover,
EuiWrappingPopoverProps,
} from './wrapping_popover';
-import { useEuiWindow } from 'src/services';
+import { useEuiWindow } from '../../services';
// NOTE: extended EuiPopoverProps are not resolved for some reason
// so we are currently manually adding them back
From 9e7faebbfc5d68e06b8f24b53a17ccc08d16c457 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Sat, 21 Sep 2024 03:07:47 +0200
Subject: [PATCH 23/29] Fix hooks
---
packages/eui/src/components/popover/input_popover.tsx | 10 ++++++++--
.../eui/src/components/tool_tip/tool_tip_popover.tsx | 2 +-
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/packages/eui/src/components/popover/input_popover.tsx b/packages/eui/src/components/popover/input_popover.tsx
index 8ec0ff58904..f067bdef3ce 100644
--- a/packages/eui/src/components/popover/input_popover.tsx
+++ b/packages/eui/src/components/popover/input_popover.tsx
@@ -166,7 +166,13 @@ export const EuiInputPopover: FunctionComponent = ({
}
}
},
- [disableFocusTrap, ownFocus, closePopover, panelPropsOnKeyDown]
+ [
+ disableFocusTrap,
+ ownFocus,
+ closePopover,
+ panelPropsOnKeyDown,
+ currentWindow,
+ ]
);
/**
@@ -221,7 +227,7 @@ export const EuiInputPopover: FunctionComponent = ({
clearTimeout(timeoutId);
};
}
- }, [closeOnScroll, closePopover, panelEl, inputEl]);
+ }, [closeOnScroll, closePopover, panelEl, inputEl, currentWindow]);
return (
= ({
);
(currentWindow ?? window).removeEventListener('resize', updateDimensions);
};
- }, [updateDimensions]);
+ }, [updateDimensions, currentWindow]);
const classes = classNames('euiToolTipPopover', className);
From af610db2b275aae70dcf032270b697b6b7c72832 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Mon, 23 Sep 2024 22:26:40 +0200
Subject: [PATCH 24/29] Fix EuiOverlayMask
---
.../overlay_mask/overlay_mask.styles.ts | 54 +++++++++++--------
.../components/overlay_mask/overlay_mask.tsx | 12 +++--
packages/eui/src/services/index.ts | 1 +
.../src/services/window_provider/context.ts | 5 ++
.../eui/src/services/window_provider/hooks.ts | 5 ++
.../eui/src/services/window_provider/index.ts | 2 +-
.../src/services/window_provider/provider.tsx | 8 ++-
7 files changed, 58 insertions(+), 29 deletions(-)
diff --git a/packages/eui/src/components/overlay_mask/overlay_mask.styles.ts b/packages/eui/src/components/overlay_mask/overlay_mask.styles.ts
index 54baf0490a7..3b5f0a72536 100644
--- a/packages/eui/src/components/overlay_mask/overlay_mask.styles.ts
+++ b/packages/eui/src/components/overlay_mask/overlay_mask.styles.ts
@@ -6,29 +6,37 @@
* Side Public License, v 1.
*/
-import { css } from '@emotion/css';
+import { Emotion } from '@emotion/css/create-instance';
import { logicalCSS, euiAnimFadeIn } from '../../global_styling';
import { transparentize, UseEuiTheme } from '../../services';
-export const euiOverlayMaskStyles = ({ euiTheme }: UseEuiTheme) => ({
- euiOverlayMask: css`
- position: fixed;
- ${logicalCSS('top', 0)}
- ${logicalCSS('left', 0)}
- ${logicalCSS('right', 0)}
- ${logicalCSS('bottom', 0)}
- display: flex;
- align-items: center;
- justify-content: center;
- ${logicalCSS('padding-bottom', '10vh')}
- animation: ${euiAnimFadeIn} ${euiTheme.animation.fast} ease-in;
- background: ${transparentize(euiTheme.colors.ink, 0.5)};
- `,
- aboveHeader: css`
- z-index: ${euiTheme.levels.mask};
- `,
- belowHeader: css`
- z-index: ${euiTheme.levels.maskBelowHeader};
- ${logicalCSS('top', 'var(--euiFixedHeadersOffset, 0)')}
- `,
-});
+export const euiOverlayMaskStyles = ({
+ euiTheme,
+ css,
+}: {
+ euiTheme: UseEuiTheme['euiTheme'];
+ css: Emotion['css'];
+}) => {
+ return {
+ euiOverlayMask: css`
+ position: fixed;
+ ${logicalCSS('top', 0)}
+ ${logicalCSS('left', 0)}
+ ${logicalCSS('right', 0)}
+ ${logicalCSS('bottom', 0)}
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ ${logicalCSS('padding-bottom', '10vh')}
+ animation: ${euiAnimFadeIn} ${euiTheme.animation.fast} ease-in;
+ background: ${transparentize(euiTheme.colors.ink, 0.5)};
+ `,
+ aboveHeader: css`
+ z-index: ${euiTheme.levels.mask};
+ `,
+ belowHeader: css`
+ z-index: ${euiTheme.levels.maskBelowHeader};
+ ${logicalCSS('top', 'var(--euiFixedHeadersOffset, 0)')}
+ `,
+ };
+};
diff --git a/packages/eui/src/components/overlay_mask/overlay_mask.tsx b/packages/eui/src/components/overlay_mask/overlay_mask.tsx
index 2bae2e37fb0..821950cdae2 100644
--- a/packages/eui/src/components/overlay_mask/overlay_mask.tsx
+++ b/packages/eui/src/components/overlay_mask/overlay_mask.tsx
@@ -15,10 +15,13 @@ import React, {
useEffect,
useState,
} from 'react';
-import { cx } from '@emotion/css';
import { Global } from '@emotion/react';
import { CommonProps, keysOf } from '../common';
-import { useCombinedRefs, useEuiTheme } from '../../services';
+import {
+ useCombinedRefs,
+ useEuiTheme,
+ useEuiWindowEmotion,
+} from '../../services';
import { EuiPortal } from '../portal';
import { euiOverlayMaskStyles } from './overlay_mask.styles';
import { euiOverlayMaskBodyStyles } from './overlay_mask_body.styles';
@@ -59,8 +62,9 @@ export const EuiOverlayMask: FunctionComponent = ({
setOverlayMaskNode,
maskRef,
]);
- const euiTheme = useEuiTheme();
- const styles = euiOverlayMaskStyles(euiTheme);
+ const { euiTheme } = useEuiTheme();
+ const { css, cx } = useEuiWindowEmotion();
+ const styles = euiOverlayMaskStyles({ euiTheme, css });
const cssStyles = cx([
styles.euiOverlayMask,
styles[`${headerZindexLocation}Header`],
diff --git a/packages/eui/src/services/index.ts b/packages/eui/src/services/index.ts
index 4a42bd5c7fd..6d2552acb55 100644
--- a/packages/eui/src/services/index.ts
+++ b/packages/eui/src/services/index.ts
@@ -105,5 +105,6 @@ export {
EuiWindowContext,
EuiWindowProvider,
useEuiWindow,
+ useEuiWindowEmotion,
} from './window_provider';
export type { EuiWindowContextValue } from './window_provider';
diff --git a/packages/eui/src/services/window_provider/context.ts b/packages/eui/src/services/window_provider/context.ts
index 742025e6e56..27e80ed4be0 100644
--- a/packages/eui/src/services/window_provider/context.ts
+++ b/packages/eui/src/services/window_provider/context.ts
@@ -6,14 +6,19 @@
* Side Public License, v 1.
*/
+import { css, cx } from '@emotion/css';
import { createContext } from 'react';
// If 'window' field is undefined, fallback to global window object at runtime.
// This is compatible with jsdom tests.
export interface EuiWindowContextValue {
window?: Window;
+ css: typeof css;
+ cx: typeof cx;
}
export const EuiWindowContext = createContext({
window: undefined,
+ css,
+ cx,
});
diff --git a/packages/eui/src/services/window_provider/hooks.ts b/packages/eui/src/services/window_provider/hooks.ts
index 15745364353..7e89ee64c59 100644
--- a/packages/eui/src/services/window_provider/hooks.ts
+++ b/packages/eui/src/services/window_provider/hooks.ts
@@ -16,3 +16,8 @@ export function useEuiWindow() {
const context = useContext(EuiWindowContext);
return context.window ?? (typeof window !== 'undefined' ? window : undefined);
}
+
+export function useEuiWindowEmotion() {
+ const { css, cx } = useContext(EuiWindowContext);
+ return { css, cx };
+}
diff --git a/packages/eui/src/services/window_provider/index.ts b/packages/eui/src/services/window_provider/index.ts
index 96958d34504..e8af62436c7 100644
--- a/packages/eui/src/services/window_provider/index.ts
+++ b/packages/eui/src/services/window_provider/index.ts
@@ -9,4 +9,4 @@
export { EuiWindowProvider } from './provider';
export type { EuiWindowContextValue } from './context';
export { EuiWindowContext } from './context';
-export { useEuiWindow } from './hooks';
+export { useEuiWindow, useEuiWindowEmotion } from './hooks';
diff --git a/packages/eui/src/services/window_provider/provider.tsx b/packages/eui/src/services/window_provider/provider.tsx
index ca7df73ee43..df9634fba9c 100644
--- a/packages/eui/src/services/window_provider/provider.tsx
+++ b/packages/eui/src/services/window_provider/provider.tsx
@@ -8,6 +8,7 @@
import React, { ReactNode } from 'react';
import { EuiWindowContext } from './context';
+import createEmotion from '@emotion/css/create-instance';
export interface EuiWindowProviderProps {
window: Window;
@@ -18,8 +19,13 @@ export function EuiWindowProvider({
window,
children,
}: EuiWindowProviderProps) {
+ const { css, cx } = createEmotion({
+ key: 'eui-child-window',
+ container: window.document.head,
+ });
+
return (
-
+
{children}
);
From 41e633caaaaa75b1b0d6df2f745e306a64f6d6c6 Mon Sep 17 00:00:00 2001
From: Przemyslaw Ryciuk
Date: Tue, 24 Sep 2024 03:10:38 +0200
Subject: [PATCH 25/29] Fix tests
---
.../src/components/modal/__snapshots__/modal.test.tsx.snap | 2 +-
.../overlay_mask/__snapshots__/overlay_mask.test.tsx.snap | 4 ++--
.../eui/src/components/overlay_mask/overlay_mask.test.tsx | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/eui/src/components/modal/__snapshots__/modal.test.tsx.snap b/packages/eui/src/components/modal/__snapshots__/modal.test.tsx.snap
index 60b64fc75fa..61e3bb42805 100644
--- a/packages/eui/src/components/modal/__snapshots__/modal.test.tsx.snap
+++ b/packages/eui/src/components/modal/__snapshots__/modal.test.tsx.snap
@@ -4,7 +4,7 @@ exports[`EuiModal renders 1`] = `