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`] = `
diff --git a/packages/eui/src/components/overlay_mask/__snapshots__/overlay_mask.test.tsx.snap b/packages/eui/src/components/overlay_mask/__snapshots__/overlay_mask.test.tsx.snap index efda6c196c3..6f8f9961a2e 100644 --- a/packages/eui/src/components/overlay_mask/__snapshots__/overlay_mask.test.tsx.snap +++ b/packages/eui/src/components/overlay_mask/__snapshots__/overlay_mask.test.tsx.snap @@ -4,7 +4,7 @@ exports[`EuiOverlayMask props headerZindexLocation 1`] = `
@@ -18,7 +18,7 @@ exports[`EuiOverlayMask renders 1`] = `
{ baseElement.querySelector('.euiOverlayMask')!.className; expect(getClassName()).toMatchInlineSnapshot( - `"euiOverlayMask css-1hzbeld-euiOverlayMask-aboveHeader hello"` + `"euiOverlayMask css-5vqto6 hello"` ); rerender( @@ -54,7 +54,7 @@ describe('EuiOverlayMask', () => { ); expect(getClassName()).toMatchInlineSnapshot( - `"euiOverlayMask css-1j0pa91-euiOverlayMask-belowHeader world"` + `"euiOverlayMask css-xdflgg world"` ); }); From 3b73f6c013196df7d5bb9aa52930bb0b458496e8 Mon Sep 17 00:00:00 2001 From: Przemyslaw Ryciuk Date: Tue, 24 Sep 2024 03:11:30 +0200 Subject: [PATCH 26/29] Remove custom publish script --- packages/eui/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/eui/package.json b/packages/eui/package.json index 0ce68558572..7d8e81608a0 100644 --- a/packages/eui/package.json +++ b/packages/eui/package.json @@ -19,7 +19,6 @@ "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 b3d699059bdf0be24599469941abad6f1f2df8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ryciuk?= Date: Sun, 6 Oct 2024 17:28:16 +0200 Subject: [PATCH 27/29] Add EuiWindowProvider documentation page --- packages/eui/src-docs/src/routes.js | 3 + .../src/views/window_provider/props.tsx | 9 + .../views/window_provider/window_provider.tsx | 167 ++++++++++++++++++ .../window_provider_example.js | 34 ++++ 4 files changed, 213 insertions(+) create mode 100644 packages/eui/src-docs/src/views/window_provider/props.tsx create mode 100644 packages/eui/src-docs/src/views/window_provider/window_provider.tsx create mode 100644 packages/eui/src-docs/src/views/window_provider/window_provider_example.js diff --git a/packages/eui/src-docs/src/routes.js b/packages/eui/src-docs/src/routes.js index 8038e2fa687..1c78b1a6b35 100644 --- a/packages/eui/src-docs/src/routes.js +++ b/packages/eui/src-docs/src/routes.js @@ -237,6 +237,8 @@ import { TourExample } from './views/tour/tour_example'; import { WindowEventExample } from './views/window_event/window_event_example'; +import { WindowProviderExample } from './views/window_provider/window_provider_example'; + import { Changelog } from './views/package/changelog'; import { I18nTokens } from './views/package/i18n_tokens'; @@ -667,6 +669,7 @@ const navigation = [ ].map((example) => createExample(example)), createTabbedPage(TextTruncateExample), createExample(WindowEventExample), + createExample(WindowProviderExample), ], }, { diff --git a/packages/eui/src-docs/src/views/window_provider/props.tsx b/packages/eui/src-docs/src/views/window_provider/props.tsx new file mode 100644 index 00000000000..1a00da4bbbd --- /dev/null +++ b/packages/eui/src-docs/src/views/window_provider/props.tsx @@ -0,0 +1,9 @@ +import React, { FunctionComponent } from 'react'; + +import { EuiWindowProviderProps } from '../../../../src/services/window_provider/provider'; + +export const useEuiWindowProviderProps: FunctionComponent< + EuiWindowProviderProps +> = () => { + return
; +}; diff --git a/packages/eui/src-docs/src/views/window_provider/window_provider.tsx b/packages/eui/src-docs/src/views/window_provider/window_provider.tsx new file mode 100644 index 00000000000..e4cc20da3c3 --- /dev/null +++ b/packages/eui/src-docs/src/views/window_provider/window_provider.tsx @@ -0,0 +1,167 @@ +import React, { useState } from 'react'; +import { createPortal } from 'react-dom'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiHorizontalRule, + EuiLink, + EuiPanel, + EuiPopover, + EuiText, + EuiTitle, + EuiToolTip, +} from '../../../../src/components'; +import { EuiWindowProvider } from '../../../../src/services'; +import createCache, { EmotionCache } from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; +import { WindowProvider } from 'react-style-singleton'; + +export default () => { + const [childWindow, setChildWindow] = useState< + | { + type: 'open'; + window: Window; + emotionCache: EmotionCache; + } + | { type: 'closed' } + >({ type: 'closed' }); + + const openWindow = () => { + const newWindow = window.open('', '_blank', `width=800,height=600`); + + if (!newWindow) { + throw new Error('Could not open the window.'); + } + + newWindow.onbeforeunload = () => { + setChildWindow({ type: 'closed' }); + }; + + copyStyles(newWindow); + + const emotionCache = createCache({ + key: 'child-window', + container: newWindow.document.head, + }); + + setChildWindow({ type: 'open', window: newWindow, emotionCache }); + }; + + return ( +
+ Open new window + {childWindow.type === 'open' && ( + + + + + + + + + + )} +
+ ); +}; + +const NewWindow = ({ + windowHandle, + children, +}: { + windowHandle: Window; + children: React.ReactNode; +}) => { + return createPortal(children, windowHandle.document.body); +}; + +const WindowContents = () => { + const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + return ( + <> + {isFlyoutOpen ? ( + { + setIsFlyoutOpen(false); + }} + paddingSize="m" + > + + +

Flyout header

+
+
+ + Flyout contents. + +
+ ) : null} + + + +

Interactivity example

+
+ + + + { + setIsFlyoutOpen(true); + }} + > + Open flyout + + + + + { + setIsPopoverOpen(true); + }} + > + Popover example (click to open) + + } + isOpen={isPopoverOpen} + closePopover={() => { + setIsPopoverOpen(false); + }} + > + +

Popover content that’s wider than the default width

+
+
+ + + + + Tooltip example (hover to open) + +
+ + ); +}; + +function copyStyles(targetWindow: Window) { + const collectedStyles: string[] = []; + + const elements = [ + ...document.head.querySelectorAll('link[data-react-helmet="true"]'), + ...document.head.getElementsByTagName('style'), + ]; + + elements.forEach((element) => { + collectedStyles.push(element.outerHTML); + }); + + targetWindow.document.head.innerHTML += collectedStyles.join('\r\n'); +} diff --git a/packages/eui/src-docs/src/views/window_provider/window_provider_example.js b/packages/eui/src-docs/src/views/window_provider/window_provider_example.js new file mode 100644 index 00000000000..a8f04f77316 --- /dev/null +++ b/packages/eui/src-docs/src/views/window_provider/window_provider_example.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { GuideSectionTypes } from '../../components'; + +import { EuiCode } from '../../../../src/components'; +import WindowProviderDemo from './window_provider'; +import { EuiWindowProvider } from '../../../../src/services'; +const windowProviderSource = require('!!raw-loader!./window_provider'); + +export const WindowProviderExample = { + title: 'Window provider', + sections: [ + { + source: [ + { + type: GuideSectionTypes.TSX, + code: windowProviderSource, + }, + ], + text: ( + <> +

+ There might be situations when you need to render EUI components + inside iframes or in another window using React Portals. To ensure + that target components use the correct window and{' '} + document object, use the{' '} + EuiWindowProvider component. +

+ + ), + demo: , + props: { EuiWindowProvider }, + }, + ], +}; From b703c405504b742efa71f42a8d333b28ae35b760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ryciuk?= Date: Sun, 6 Oct 2024 19:05:18 +0200 Subject: [PATCH 28/29] Add changelog entry --- packages/eui/changelogs/upcoming/7782.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/eui/changelogs/upcoming/7782.md diff --git a/packages/eui/changelogs/upcoming/7782.md b/packages/eui/changelogs/upcoming/7782.md new file mode 100644 index 00000000000..d22126efd15 --- /dev/null +++ b/packages/eui/changelogs/upcoming/7782.md @@ -0,0 +1,5 @@ +- Added `EuiWindowProvider` that fixes interactivity of some components in scenarios where rendering is done via React Portals into another browser window or iframe. ([#7782](https://github.com/elastic/eui/pull/7782)) + +**Dependency updates** + +- Updated `react-focus-on` to vX.Y.Z (TODO) From adae23d2e1af732ae54f72bffe449aa4126741cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ryciuk?= Date: Sun, 6 Oct 2024 22:58:59 +0200 Subject: [PATCH 29/29] Add props descriptions --- packages/eui/src/services/window_provider/provider.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/eui/src/services/window_provider/provider.tsx b/packages/eui/src/services/window_provider/provider.tsx index df9634fba9c..29d207f6de8 100644 --- a/packages/eui/src/services/window_provider/provider.tsx +++ b/packages/eui/src/services/window_provider/provider.tsx @@ -11,7 +11,14 @@ import { EuiWindowContext } from './context'; import createEmotion from '@emotion/css/create-instance'; export interface EuiWindowProviderProps { + /** + * Window object to be used for children components. + */ window: Window; + + /** + * ReactNode to render as this component's content + */ children: ReactNode; }