diff --git a/src/components/modal/__snapshots__/modal.test.tsx.snap b/src/components/modal/__snapshots__/modal.test.tsx.snap
index 82305843b3e..4ef88e6cd23 100644
--- a/src/components/modal/__snapshots__/modal.test.tsx.snap
+++ b/src/components/modal/__snapshots__/modal.test.tsx.snap
@@ -1,51 +1,61 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`renders EuiModal 1`] = `
-Array [
+exports[`EuiModal renders 1`] = `
+
,
+ />
,
-
- ,
- ,
-]
+
+
+
`;
diff --git a/src/components/modal/modal.test.tsx b/src/components/modal/modal.test.tsx
index 8dec11a3b07..9f313d15a6c 100644
--- a/src/components/modal/modal.test.tsx
+++ b/src/components/modal/modal.test.tsx
@@ -7,19 +7,44 @@
*/
import React from 'react';
-import { mount } from 'enzyme';
-import { requiredProps, takeMountedSnapshot } from '../../test';
+import { fireEvent } from '@testing-library/dom';
+import { render } from '../../test/rtl';
+import { requiredProps } from '../../test';
import { EuiModal } from './modal';
-test('renders EuiModal', () => {
- const component = (
- {}} {...requiredProps}>
- children
-
- );
+describe('EuiModal', () => {
+ it('renders', () => {
+ const { baseElement } = render(
+ {}} {...requiredProps}>
+ children
+
+ );
- expect(
- takeMountedSnapshot(mount(component), { hasArrayOutput: true })
- ).toMatchSnapshot();
+ // NOTE: Using baseElement instead of container is required for components that use portals
+ expect(baseElement).toMatchSnapshot();
+ });
+
+ // TODO: Remove this onFocus scroll workaround after react-focus-on supports focusOptions
+ // @see https://github.com/elastic/eui/issues/6304
+ describe('focus/scroll workaround', () => {
+ it('scrolls back to the original window position on initial modal focus', () => {
+ window.scrollTo = jest.fn();
+
+ const { getByTestSubject } = render(
+ {}}>
+ children
+
+ );
+
+ // For whatever reason, react-focus-lock doesn't appear to trigger focus in RTL so we'll do it manually
+ fireEvent.focusIn(getByTestSubject('modal'));
+ // Confirm that scrolling does not occur more than once
+ fireEvent.focusIn(getByTestSubject('modal'));
+ fireEvent.focusIn(getByTestSubject('modal'));
+
+ expect(window.scrollTo).toHaveBeenCalledTimes(1);
+ jest.restoreAllMocks();
+ });
+ });
});
diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx
index 84301fba62b..50d99879725 100644
--- a/src/components/modal/modal.tsx
+++ b/src/components/modal/modal.tsx
@@ -6,7 +6,13 @@
* Side Public License, v 1.
*/
-import React, { FunctionComponent, ReactNode, HTMLAttributes } from 'react';
+import React, {
+ FunctionComponent,
+ ReactNode,
+ HTMLAttributes,
+ useRef,
+ useCallback,
+} from 'react';
import classnames from 'classnames';
import { keys } from '../../services';
@@ -52,6 +58,18 @@ export const EuiModal: FunctionComponent = ({
style,
...rest
}) => {
+ // TODO: Remove this onFocus scroll workaround after react-focus-on supports focusOptions
+ // @see https://github.com/elastic/eui/issues/6304
+ const bodyScrollTop = useRef(
+ typeof window === 'undefined' ? undefined : window.scrollY // Account for SSR
+ );
+ const onFocus = useCallback(() => {
+ if (bodyScrollTop.current != null) {
+ window.scrollTo({ top: bodyScrollTop.current });
+ bodyScrollTop.current = undefined; // Unset after first auto focus
+ }
+ }, []);
+
const onKeyDown = (event: React.KeyboardEvent) => {
if (event.key === keys.ESCAPE) {
event.preventDefault();
@@ -73,7 +91,7 @@ export const EuiModal: FunctionComponent = ({
return (
-
+
{
// Create a child div instead of applying these props directly to FocusTrap, or else
// fallbackFocus won't work.
@@ -82,6 +100,7 @@ export const EuiModal: FunctionComponent = ({
className={classes}
onKeyDown={onKeyDown}
tabIndex={0}
+ onFocus={onFocus}
style={newStyle || style}
{...rest}
>
diff --git a/upcoming_changelogs/6327.md b/upcoming_changelogs/6327.md
new file mode 100644
index 00000000000..de9a37d3a02
--- /dev/null
+++ b/upcoming_changelogs/6327.md
@@ -0,0 +1,3 @@
+**Bug fixes**
+
+- Temporarily patched `EuiModal` to not cause scroll-jumping issues on modal open