Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EuiWindowProvider for multi-window support #7782

Draft
wants to merge 30 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c303002
Add EuiWindowProvider for EuiPortal and EuiWindowEvent
stil May 21, 2024
77f5704
Formatting
stil May 21, 2024
7d1d391
Formatting
stil May 21, 2024
cf06fb6
Add Flyout, FocusTrap, OutsideClickDetector, findElement support for …
stil May 22, 2024
4fb655c
Formatting
stil May 22, 2024
26433d0
Fix effect
stil May 22, 2024
0bf4e8e
Fix effects
stil May 22, 2024
c0b29cf
Declare context
stil May 22, 2024
96f438a
Use context declaration
stil May 22, 2024
6555c1a
Delete unused import
stil May 22, 2024
cfbedf2
Make default window object resolved at runtime to fix tests
stil May 22, 2024
a46fea6
Fix for SSR scenario
stil May 22, 2024
99ed11c
Fix problems in Flyout
stil May 22, 2024
2d39642
Update i18ntokens.json
stil May 22, 2024
4ce72c6
Merge remote-tracking branch 'upstream/main' into add-window-provider…
stil Aug 20, 2024
a9a2388
Add build-publish for yalc
stil Sep 5, 2024
6ec4cf0
Merge remote-tracking branch 'upstream/main' into add-window-provider…
stil Sep 5, 2024
d90a8b5
Make tooltip component use window provider
stil Sep 19, 2024
8fcf539
Missed one window ref
stil Sep 19, 2024
adf4ca7
Make popover component use window provider
stil Sep 20, 2024
3f5a871
Use current window for Popover and Portal
stil Sep 20, 2024
11b93e4
Apply formatting
stil Sep 21, 2024
cdffdc8
Apply formatting
stil Sep 21, 2024
0d48da6
Fix import
stil Sep 21, 2024
9e7faeb
Fix hooks
stil Sep 21, 2024
af610db
Fix EuiOverlayMask
stil Sep 23, 2024
69f3c93
Merge remote-tracking branch 'upstream/main' into add-window-provider
stil Sep 24, 2024
41e633c
Fix tests
stil Sep 24, 2024
3b73f6c
Remove custom publish script
stil Sep 24, 2024
ba42379
Merge branch 'main' into add-window-provider
stil Sep 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions packages/eui/i18ntokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -4811,14 +4811,14 @@
"highlighting": "string",
"loc": {
"start": {
"line": 336,
"line": 339,
"column": 14,
"index": 10903
"index": 11143
},
"end": {
"line": 339,
"line": 342,
"column": 16,
"index": 11118
"index": 11358
}
},
"filepath": "src/components/flyout/flyout.tsx"
Expand All @@ -4829,14 +4829,14 @@
"highlighting": "string",
"loc": {
"start": {
"line": 341,
"line": 344,
"column": 14,
"index": 11151
"index": 11391
},
"end": {
"line": 344,
"line": 347,
"column": 16,
"index": 11329
"index": 11569
}
},
"filepath": "src/components/flyout/flyout.tsx"
Expand All @@ -4847,14 +4847,14 @@
"highlighting": "string",
"loc": {
"start": {
"line": 347,
"line": 350,
"column": 14,
"index": 11406
"index": 11646
},
"end": {
"line": 350,
"line": 353,
"column": 16,
"index": 11599
"index": 11839
}
},
"filepath": "src/components/flyout/flyout.tsx"
Expand Down
21 changes: 12 additions & 9 deletions packages/eui/src/components/flyout/flyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
useIsWithinMinBreakpoint,
useEuiMemoizedStyles,
useGeneratedHtmlId,
useEuiWindow,
} from '../../services';
import { logicalStyle } from '../../global_styling';

Expand Down Expand Up @@ -202,6 +203,8 @@ export const EuiFlyout = forwardRef(
) => {
const Element = as || defaultElement;
const maskRef = useRef<HTMLDivElement>(null);
const currentWindow = useEuiWindow();
const currentDocument = currentWindow?.document ?? document;

const windowIsLargeEnoughToPush =
useIsWithinMinBreakpoint(pushMinBreakpoint);
Expand All @@ -225,23 +228,23 @@ export const EuiFlyout = forwardRef(
const paddingSide =
side === 'left' ? 'paddingInlineStart' : 'paddingInlineEnd';

document.body.style[paddingSide] = `${width}px`;
currentDocument.body.style[paddingSide] = `${width}px`;
return () => {
document.body.style[paddingSide] = '';
currentDocument.body.style[paddingSide] = '';
};
}
}, [isPushed, side, width]);
}, [isPushed, side, width, currentDocument.body.style]);

/**
* This class doesn't actually do anything by EUI, but is nice to add for consumers (JIC)
*/
useEffect(() => {
document.body.classList.add('euiBody--hasFlyout');
currentDocument.body.classList.add('euiBody--hasFlyout');
return () => {
// Remove the hasFlyout class when the flyout is unmounted
document.body.classList.remove('euiBody--hasFlyout');
currentDocument.body.classList.remove('euiBody--hasFlyout');
};
}, []);
}, [currentDocument.body.classList]);

/**
* ESC key closes flyout (always?)
Expand Down Expand Up @@ -290,12 +293,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<Element | null>(document.activeElement);
const flyoutToggle = useRef<Element | null>(currentDocument.activeElement);
const [fixedHeaders, setFixedHeaders] = useState<HTMLDivElement[]>([]);

useEffect(() => {
if (includeFixedHeadersInFocusTrap) {
const fixedHeaderEls = document.querySelectorAll<HTMLDivElement>(
const fixedHeaderEls = currentDocument.querySelectorAll<HTMLDivElement>(
'.euiHeader[data-fixed-header]'
);
setFixedHeaders(Array.from(fixedHeaderEls));
Expand All @@ -311,7 +314,7 @@ export const EuiFlyout = forwardRef(
// Clear existing headers if necessary, e.g. switching to `false`
setFixedHeaders((headers) => (headers.length ? [] : headers));
}
}, [includeFixedHeadersInFocusTrap, resizeRef]);
}, [includeFixedHeadersInFocusTrap, resizeRef, currentDocument]);

const focusTrapProps: EuiFlyoutProps['focusTrapProps'] = useMemo(
() => ({
Expand Down
30 changes: 23 additions & 7 deletions packages/eui/src/components/focus_trap/focus_trap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@
* 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';

import { CommonProps } from '../common';
import { findElementBySelectorOrRef, ElementTarget } from '../../services';
import {
findElementBySelectorOrRef,
ElementTarget,
EuiWindowContext,
} from '../../services';
import { usePropsWithComponentDefaults } from '../provider/component_defaults';

export type FocusTarget = ElementTarget;
Expand Down Expand Up @@ -105,6 +114,9 @@ class EuiFocusTrapClass extends Component<EuiFocusTrapProps, State> {
gapMode: 'padding', // EUI defaults to padding because Kibana's body/layout CSS ignores `margin`
};

static contextType = EuiWindowContext;
declare context: ContextType<typeof EuiWindowContext>;

state: State = {
hasBeenDisabledByClick: false,
};
Expand All @@ -129,7 +141,9 @@ class EuiFocusTrapClass extends Component<EuiFocusTrapProps, State> {
// Programmatically sets focus on a nested DOM node; optional
setInitialFocus = (initialFocus?: FocusTarget) => {
if (!initialFocus) return;
const node = findElementBySelectorOrRef(initialFocus);

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
node.setAttribute('data-autofocus', 'true');
Expand All @@ -143,13 +157,15 @@ class EuiFocusTrapClass extends Component<EuiFocusTrapProps, State> {
};

addMouseupListener = () => {
document.addEventListener('mouseup', this.onMouseupOutside);
document.addEventListener('touchend', this.onMouseupOutside);
const currentDocument = (this.context.window ?? 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.window ?? window).document;
currentDocument.removeEventListener('mouseup', this.onMouseupOutside);
currentDocument.removeEventListener('touchend', this.onMouseupOutside);
};

handleOutsideClick: ReactFocusOnProps['onClickOutside'] = (event) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ exports[`EuiModal renders 1`] = `
<body>
<div />
<div
class="euiOverlayMask emotion-euiOverlayMask-aboveHeader"
class="euiOverlayMask css-5vqto6"
data-euiportal="true"
data-relative-to-header="above"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import {
EventHandler,
MouseEvent as ReactMouseEvent,
ReactElement,
ContextType,
} from 'react';
import { htmlIdGenerator } from '../../services/accessibility';
import { EuiWindowContext } from '../../services';

export interface EuiEvent extends Event {
euiGeneratedBy: string[];
Expand Down Expand Up @@ -45,6 +47,9 @@ export class EuiOutsideClickDetector extends Component<EuiOutsideClickDetectorPr
// about user intention. So, consider the down/start and up/end
// items below as the deconstruction of a click event.

static contextType = EuiWindowContext;
declare context: ContextType<typeof EuiWindowContext>;

private id: string;

private capturedDownIds: string[];
Expand Down Expand Up @@ -96,13 +101,15 @@ export class EuiOutsideClickDetector extends Component<EuiOutsideClickDetectorPr
};

componentDidMount() {
document.addEventListener('mouseup', this.onClickOutside);
document.addEventListener('touchend', this.onClickOutside);
const currentDocument = (this.context.window ?? window).document;
currentDocument.addEventListener('mouseup', this.onClickOutside);
currentDocument.addEventListener('touchend', this.onClickOutside);
}

componentWillUnmount() {
document.removeEventListener('mouseup', this.onClickOutside);
document.removeEventListener('touchend', this.onClickOutside);
const currentDocument = (this.context.window ?? window).document;
currentDocument.removeEventListener('mouseup', this.onClickOutside);
currentDocument.removeEventListener('touchend', this.onClickOutside);
}

onChildClick = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ exports[`EuiOverlayMask props headerZindexLocation 1`] = `
<body>
<div />
<div
class="euiOverlayMask emotion-euiOverlayMask-belowHeader"
class="euiOverlayMask css-xdflgg"
data-euiportal="true"
data-relative-to-header="below"
>
Expand All @@ -18,7 +18,7 @@ exports[`EuiOverlayMask renders 1`] = `
<div />
<div
aria-label="aria-label"
class="euiOverlayMask emotion-euiOverlayMask-aboveHeader testClass1 testClass2 emotion-euiTestCss"
class="euiOverlayMask css-5vqto6 testClass1 testClass2 emotion-euiTestCss"
data-euiportal="true"
data-relative-to-header="above"
data-test-subj="test subject string"
Expand Down
54 changes: 31 additions & 23 deletions packages/eui/src/components/overlay_mask/overlay_mask.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)')}
`,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('EuiOverlayMask', () => {
baseElement.querySelector('.euiOverlayMask')!.className;

expect(getClassName()).toMatchInlineSnapshot(
`"euiOverlayMask css-1hzbeld-euiOverlayMask-aboveHeader hello"`
`"euiOverlayMask css-5vqto6 hello"`
);

rerender(
Expand All @@ -54,7 +54,7 @@ describe('EuiOverlayMask', () => {
</EuiOverlayMask>
);
expect(getClassName()).toMatchInlineSnapshot(
`"euiOverlayMask css-1j0pa91-euiOverlayMask-belowHeader world"`
`"euiOverlayMask css-xdflgg world"`
);
});

Expand Down
12 changes: 8 additions & 4 deletions packages/eui/src/components/overlay_mask/overlay_mask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -59,8 +62,9 @@ export const EuiOverlayMask: FunctionComponent<EuiOverlayMaskProps> = ({
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`],
Expand Down
Loading