diff --git a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md
index c6920fc30d4ee..fdf56012e4729 100644
--- a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md
+++ b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.md
@@ -15,11 +15,6 @@ export interface ChromeNavControl
| Property | Type | Description |
| --- | --- | --- |
+| [mount](./kibana-plugin-public.chromenavcontrol.mount.md) | MountPoint
| |
| [order](./kibana-plugin-public.chromenavcontrol.order.md) | number
| |
-## Methods
-
-| Method | Description |
-| --- | --- |
-| [mount(targetDomElement)](./kibana-plugin-public.chromenavcontrol.mount.md) | |
-
diff --git a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md
index 6ce5d7f0d5c4d..3e1f5a1f78f89 100644
--- a/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md
+++ b/docs/development/core/public/kibana-plugin-public.chromenavcontrol.mount.md
@@ -2,21 +2,10 @@
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavControl](./kibana-plugin-public.chromenavcontrol.md) > [mount](./kibana-plugin-public.chromenavcontrol.mount.md)
-## ChromeNavControl.mount() method
+## ChromeNavControl.mount property
Signature:
```typescript
-mount(targetDomElement: HTMLElement): () => void;
+mount: MountPoint;
```
-
-## Parameters
-
-| Parameter | Type | Description |
-| --- | --- | --- |
-| targetDomElement | HTMLElement
| |
-
-Returns:
-
-`() => void`
-
diff --git a/docs/development/core/public/kibana-plugin-public.mountpoint.md b/docs/development/core/public/kibana-plugin-public.mountpoint.md
index 58f407904a576..928d22f00ed00 100644
--- a/docs/development/core/public/kibana-plugin-public.mountpoint.md
+++ b/docs/development/core/public/kibana-plugin-public.mountpoint.md
@@ -9,5 +9,5 @@ A function that should mount DOM content inside the provided container element a
Signature:
```typescript
-export declare type MountPoint = (element: HTMLElement) => UnmountCallback;
+export declare type MountPoint = (element: T) => UnmountCallback;
```
diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.md b/docs/development/core/public/kibana-plugin-public.overlaystart.md
index 6bcf0a581df80..8b6f11bd819f8 100644
--- a/docs/development/core/public/kibana-plugin-public.overlaystart.md
+++ b/docs/development/core/public/kibana-plugin-public.overlaystart.md
@@ -16,6 +16,6 @@ export interface OverlayStart
| Property | Type | Description |
| --- | --- | --- |
| [banners](./kibana-plugin-public.overlaystart.banners.md) | OverlayBannersStart
| [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) |
-| [openFlyout](./kibana-plugin-public.overlaystart.openflyout.md) | (flyoutChildren: React.ReactNode, flyoutProps?: {
closeButtonAriaLabel?: string;
'data-test-subj'?: string;
}) => OverlayRef
| |
-| [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | (modalChildren: React.ReactNode, modalProps?: {
className?: string;
closeButtonAriaLabel?: string;
'data-test-subj'?: string;
}) => OverlayRef
| |
+| [openFlyout](./kibana-plugin-public.overlaystart.openflyout.md) | OverlayFlyoutStart['open']
| |
+| [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | OverlayModalStart['open']
| |
diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md b/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md
index 6d015d6a34382..ad3351fb4d098 100644
--- a/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md
+++ b/docs/development/core/public/kibana-plugin-public.overlaystart.openflyout.md
@@ -4,11 +4,9 @@
## OverlayStart.openFlyout property
+
Signature:
```typescript
-openFlyout: (flyoutChildren: React.ReactNode, flyoutProps?: {
- closeButtonAriaLabel?: string;
- 'data-test-subj'?: string;
- }) => OverlayRef;
+openFlyout: OverlayFlyoutStart['open'];
```
diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md b/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md
index a4569e178f17d..2c983d6151f4c 100644
--- a/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md
+++ b/docs/development/core/public/kibana-plugin-public.overlaystart.openmodal.md
@@ -4,12 +4,9 @@
## OverlayStart.openModal property
+
Signature:
```typescript
-openModal: (modalChildren: React.ReactNode, modalProps?: {
- className?: string;
- closeButtonAriaLabel?: string;
- 'data-test-subj'?: string;
- }) => OverlayRef;
+openModal: OverlayModalStart['open'];
```
diff --git a/src/core/public/chrome/nav_controls/nav_controls_service.ts b/src/core/public/chrome/nav_controls/nav_controls_service.ts
index 0088ef68aaeb8..7f9c75595a4ce 100644
--- a/src/core/public/chrome/nav_controls/nav_controls_service.ts
+++ b/src/core/public/chrome/nav_controls/nav_controls_service.ts
@@ -20,11 +20,12 @@
import { sortBy } from 'lodash';
import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
+import { MountPoint } from '../../types';
/** @public */
export interface ChromeNavControl {
order?: number;
- mount(targetDomElement: HTMLElement): () => void;
+ mount: MountPoint;
}
/**
diff --git a/src/core/public/chrome/ui/header/header_extension.tsx b/src/core/public/chrome/ui/header/header_extension.tsx
index 90e7907b0068c..76413a0ea0317 100644
--- a/src/core/public/chrome/ui/header/header_extension.tsx
+++ b/src/core/public/chrome/ui/header/header_extension.tsx
@@ -18,9 +18,10 @@
*/
import React from 'react';
+import { MountPoint } from '../../../types';
interface Props {
- extension?: (el: HTMLDivElement) => () => void;
+ extension?: MountPoint;
}
export class HeaderExtension extends React.Component {
diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts
index 79fa32040b14c..22e315f9e1b03 100644
--- a/src/core/public/legacy/legacy_service.ts
+++ b/src/core/public/legacy/legacy_service.ts
@@ -19,7 +19,7 @@
import angular from 'angular';
import { InternalCoreSetup, InternalCoreStart } from '../core_system';
-import { LegacyCoreSetup, LegacyCoreStart } from '../';
+import { LegacyCoreSetup, LegacyCoreStart, MountPoint } from '../';
/** @internal */
export interface LegacyPlatformParams {
@@ -40,7 +40,7 @@ interface StartDeps {
}
interface BootstrapModule {
- bootstrap: (targetDomElement: HTMLElement) => void;
+ bootstrap: MountPoint;
}
/**
diff --git a/src/core/public/notifications/toasts/error_toast.test.tsx b/src/core/public/notifications/toasts/error_toast.test.tsx
index b72b2de85340a..b497be526093d 100644
--- a/src/core/public/notifications/toasts/error_toast.test.tsx
+++ b/src/core/public/notifications/toasts/error_toast.test.tsx
@@ -40,6 +40,7 @@ function render(props: ErrorToastProps = {}) {
error={props.error || new Error('error message')}
title={props.title || 'An error occured'}
toastMessage={props.toastMessage || 'This is the toast message'}
+ i18nContext={() => ({ children }) => {children}}
/>
);
}
diff --git a/src/core/public/notifications/toasts/error_toast.tsx b/src/core/public/notifications/toasts/error_toast.tsx
index 10bc51559644b..6b53719839b0f 100644
--- a/src/core/public/notifications/toasts/error_toast.tsx
+++ b/src/core/public/notifications/toasts/error_toast.tsx
@@ -18,6 +18,7 @@
*/
import React from 'react';
+import ReactDOM from 'react-dom';
import {
EuiButton,
@@ -32,12 +33,14 @@ import { EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { OverlayStart } from '../../overlays';
+import { I18nStart } from '../../i18n';
interface ErrorToastProps {
title: string;
error: Error;
toastMessage: string;
openModal: OverlayStart['openModal'];
+ i18nContext: () => I18nStart['Context'];
}
/**
@@ -50,33 +53,48 @@ function showErrorDialog({
title,
error,
openModal,
-}: Pick) {
+ i18nContext,
+}: Pick) {
+ const I18nContext = i18nContext();
const modal = openModal(
-
-
- {title}
-
-
-
- {error.stack && (
-
-
-
- {error.stack}
-
-
- )}
-
-
- modal.close()} fill>
-
-
-
-
+ mount(
+
+
+
+ {title}
+
+
+
+ {error.stack && (
+
+
+
+ {error.stack}
+
+
+ )}
+
+
+ modal.close()} fill>
+
+
+
+
+
+ )
);
}
-export function ErrorToast({ title, error, toastMessage, openModal }: ErrorToastProps) {
+export function ErrorToast({
+ title,
+ error,
+ toastMessage,
+ openModal,
+ i18nContext,
+}: ErrorToastProps) {
return (
{toastMessage}
@@ -84,7 +102,7 @@ export function ErrorToast({ title, error, toastMessage, openModal }: ErrorToast
showErrorDialog({ title, error, openModal })}
+ onClick={() => showErrorDialog({ title, error, openModal, i18nContext })}
>
);
}
+
+const mount = (component: React.ReactElement) => (container: HTMLElement) => {
+ ReactDOM.render(component, container);
+ return () => ReactDOM.unmountComponentAtNode(container);
+};
diff --git a/src/core/public/notifications/toasts/toasts_api.test.ts b/src/core/public/notifications/toasts/toasts_api.test.ts
index f99a28617aa5c..a0e419e989657 100644
--- a/src/core/public/notifications/toasts/toasts_api.test.ts
+++ b/src/core/public/notifications/toasts/toasts_api.test.ts
@@ -51,10 +51,13 @@ function uiSettingsMock() {
function toastDeps() {
return {
uiSettings: uiSettingsMock(),
- i18n: i18nServiceMock.createStartContract(),
};
}
+function startDeps() {
+ return { overlays: {} as any, i18n: i18nServiceMock.createStartContract() };
+}
+
describe('#get$()', () => {
it('returns observable that emits NEW toast list when something added or removed', () => {
const toasts = new ToastsApi(toastDeps());
@@ -188,6 +191,7 @@ describe('#addDanger()', () => {
describe('#addError', () => {
it('adds an error toast', async () => {
const toasts = new ToastsApi(toastDeps());
+ toasts.start(startDeps());
const toast = toasts.addError(new Error('unexpected error'), { title: 'Something went wrong' });
expect(toast).toHaveProperty('color', 'danger');
expect(toast).toHaveProperty('title', 'Something went wrong');
@@ -195,6 +199,7 @@ describe('#addError', () => {
it('returns the created toast', async () => {
const toasts = new ToastsApi(toastDeps());
+ toasts.start(startDeps());
const toast = toasts.addError(new Error('unexpected error'), { title: 'Something went wrong' });
const currentToasts = await getCurrentToasts(toasts);
expect(currentToasts[0]).toBe(toast);
diff --git a/src/core/public/notifications/toasts/toasts_api.tsx b/src/core/public/notifications/toasts/toasts_api.tsx
index b49bafda5b26e..a21b727b02d73 100644
--- a/src/core/public/notifications/toasts/toasts_api.tsx
+++ b/src/core/public/notifications/toasts/toasts_api.tsx
@@ -26,6 +26,7 @@ import { MountPoint } from '../../types';
import { mountReactNode } from '../../utils';
import { UiSettingsClientContract } from '../../ui_settings';
import { OverlayStart } from '../../overlays';
+import { I18nStart } from '../../i18n';
/**
* Allowed fields for {@link ToastInput}.
@@ -96,14 +97,16 @@ export class ToastsApi implements IToasts {
private uiSettings: UiSettingsClientContract;
private overlays?: OverlayStart;
+ private i18n?: I18nStart;
constructor(deps: { uiSettings: UiSettingsClientContract }) {
this.uiSettings = deps.uiSettings;
}
/** @internal */
- public registerOverlays(overlays: OverlayStart) {
+ public start({ overlays, i18n }: { overlays: OverlayStart; i18n: I18nStart }) {
this.overlays = overlays;
+ this.i18n = i18n;
}
/** Observable of the toast messages to show to the user. */
@@ -206,6 +209,7 @@ export class ToastsApi implements IToasts {
error={error}
title={options.title}
toastMessage={message}
+ i18nContext={() => this.i18n!.Context}
/>
),
});
diff --git a/src/core/public/notifications/toasts/toasts_service.tsx b/src/core/public/notifications/toasts/toasts_service.tsx
index 47d8c14f9af8b..81d23afc4f4d3 100644
--- a/src/core/public/notifications/toasts/toasts_service.tsx
+++ b/src/core/public/notifications/toasts/toasts_service.tsx
@@ -58,7 +58,7 @@ export class ToastsService {
}
public start({ i18n, overlays, targetDomElement }: StartDeps) {
- this.api!.registerOverlays(overlays);
+ this.api!.start({ overlays, i18n });
this.targetDomElement = targetDomElement;
render(
diff --git a/src/core/public/overlays/__snapshots__/flyout.test.tsx.snap b/src/core/public/overlays/__snapshots__/flyout.test.tsx.snap
deleted file mode 100644
index 0c19c6312a672..0000000000000
--- a/src/core/public/overlays/__snapshots__/flyout.test.tsx.snap
+++ /dev/null
@@ -1,70 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`FlyoutService FlyoutRef#close() can be called multiple times on the same FlyoutRef 1`] = `
-Array [
- Array [
- ,
- ],
-]
-`;
-
-exports[`FlyoutService openFlyout() renders a flyout to the DOM 1`] = `
-Array [
- Array [
-
-
-
- Flyout content
-
-
- ,
- ,
- ],
-]
-`;
-
-exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = `
-Array [
- Array [
-
-
-
- Flyout content 1
-
-
- ,
- ,
- ],
- Array [
-
-
-
- Flyout content 2
-
-
- ,
- ,
- ],
-]
-`;
diff --git a/src/core/public/overlays/__snapshots__/modal.test.tsx.snap b/src/core/public/overlays/__snapshots__/modal.test.tsx.snap
deleted file mode 100644
index a4e6f5d6f72b8..0000000000000
--- a/src/core/public/overlays/__snapshots__/modal.test.tsx.snap
+++ /dev/null
@@ -1,64 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ModalService ModalRef#close() can be called multiple times on the same ModalRef 1`] = `
-Array [
- Array [
- ,
- ],
-]
-`;
-
-exports[`ModalService openModal() renders a modal to the DOM 1`] = `
-Array [
- Array [
-
-
-
-
- Modal content
-
-
-
- ,
- ,
- ],
-]
-`;
-
-exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = `
-Array [
- Array [
-
-
-
-
- Modal content 1
-
-
-
- ,
- ,
- ],
- Array [
-
-
-
-
- Flyout content 2
-
-
-
- ,
- ,
- ],
-]
-`;
diff --git a/src/core/public/overlays/_index.scss b/src/core/public/overlays/_index.scss
index fe86883aedcf9..368dc9b644ff9 100644
--- a/src/core/public/overlays/_index.scss
+++ b/src/core/public/overlays/_index.scss
@@ -1 +1,2 @@
@import './banners/index';
+@import './mount_wrapper';
diff --git a/src/core/public/overlays/_mount_wrapper.scss b/src/core/public/overlays/_mount_wrapper.scss
new file mode 100644
index 0000000000000..aafcc4bbe87db
--- /dev/null
+++ b/src/core/public/overlays/_mount_wrapper.scss
@@ -0,0 +1,5 @@
+.kbnOverlayMountWrapper {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
diff --git a/src/core/public/overlays/banners/banners_service.tsx b/src/core/public/overlays/banners/banners_service.tsx
index 31d49b5952e87..a26a7c71bc61f 100644
--- a/src/core/public/overlays/banners/banners_service.tsx
+++ b/src/core/public/overlays/banners/banners_service.tsx
@@ -97,9 +97,7 @@ export class OverlayBannersService {
if (!banners$.value.has(id)) {
return false;
}
-
banners$.next(banners$.value.remove(id));
-
return true;
},
@@ -107,10 +105,8 @@ export class OverlayBannersService {
if (!id || !banners$.value.has(id)) {
return this.add(mount, priority);
}
-
const nextId = genId();
const nextBanner = { id: nextId, mount, priority };
-
banners$.next(banners$.value.remove(id).add(nextId, nextBanner));
return nextId;
},
diff --git a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
new file mode 100644
index 0000000000000..94c11f0185427
--- /dev/null
+++ b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
@@ -0,0 +1,77 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FlyoutService FlyoutRef#close() can be called multiple times on the same FlyoutRef 1`] = `
+Array [
+ Array [
+ ,
+ ],
+]
+`;
+
+exports[`FlyoutService openFlyout() renders a flyout to the DOM 1`] = `
+Array [
+ Array [
+
+
+
+
+ ,
+ ,
+ ],
+]
+`;
+
+exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `""`;
+
+exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = `
+Array [
+ Array [
+
+
+
+
+ ,
+ ,
+ ],
+ Array [
+
+
+
+
+ ,
+ ,
+ ],
+]
+`;
+
+exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `""`;
diff --git a/src/core/public/overlays/flyout/flyout_service.mock.ts b/src/core/public/overlays/flyout/flyout_service.mock.ts
new file mode 100644
index 0000000000000..91544500713d6
--- /dev/null
+++ b/src/core/public/overlays/flyout/flyout_service.mock.ts
@@ -0,0 +1,43 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { FlyoutService, OverlayFlyoutStart } from './flyout_service';
+
+const createStartContractMock = () => {
+ const startContract: jest.Mocked = {
+ open: jest.fn().mockReturnValue({
+ close: jest.fn(),
+ onClose: Promise.resolve(),
+ }),
+ };
+ return startContract;
+};
+
+const createMock = () => {
+ const mocked: jest.Mocked> = {
+ start: jest.fn(),
+ };
+ mocked.start.mockReturnValue(createStartContractMock());
+ return mocked;
+};
+
+export const overlayFlyoutServiceMock = {
+ create: createMock,
+ createStartContract: createStartContractMock,
+};
diff --git a/src/core/public/overlays/flyout.test.tsx b/src/core/public/overlays/flyout/flyout_service.test.tsx
similarity index 65%
rename from src/core/public/overlays/flyout.test.tsx
rename to src/core/public/overlays/flyout/flyout_service.test.tsx
index afe37fc50776b..25ba94f577993 100644
--- a/src/core/public/overlays/flyout.test.tsx
+++ b/src/core/public/overlays/flyout/flyout_service.test.tsx
@@ -16,11 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { mockReactDomRender, mockReactDomUnmount } from './flyout.test.mocks';
+import { mockReactDomRender, mockReactDomUnmount } from '../overlay.test.mocks';
-import React from 'react';
-import { i18nServiceMock } from '../i18n/i18n_service.mock';
-import { FlyoutRef, FlyoutService } from './flyout';
+import { mount } from 'enzyme';
+import { i18nServiceMock } from '../../i18n/i18n_service.mock';
+import { FlyoutService, OverlayFlyoutStart } from './flyout_service';
+import { OverlayRef } from '../types';
const i18nMock = i18nServiceMock.createStartContract();
@@ -29,35 +30,50 @@ beforeEach(() => {
mockReactDomUnmount.mockClear();
});
+const mountText = (text: string) => (container: HTMLElement) => {
+ const content = document.createElement('span');
+ content.textContent = text;
+ container.append(content);
+ return () => {};
+};
+
+const getServiceStart = () => {
+ const service = new FlyoutService();
+ return service.start({ i18n: i18nMock, targetDomElement: document.createElement('div') });
+};
+
describe('FlyoutService', () => {
+ let flyouts: OverlayFlyoutStart;
+ beforeEach(() => {
+ flyouts = getServiceStart();
+ });
+
describe('openFlyout()', () => {
it('renders a flyout to the DOM', () => {
- const target = document.createElement('div');
- const flyoutService = new FlyoutService(target);
expect(mockReactDomRender).not.toHaveBeenCalled();
- flyoutService.openFlyout(i18nMock, Flyout content);
+ flyouts.open(mountText('Flyout content'));
expect(mockReactDomRender.mock.calls).toMatchSnapshot();
+ const modalContent = mount(mockReactDomRender.mock.calls[0][0]);
+ expect(modalContent.html()).toMatchSnapshot();
});
describe('with a currently active flyout', () => {
- let target: HTMLElement;
- let flyoutService: FlyoutService;
- let ref1: FlyoutRef;
+ let ref1: OverlayRef;
beforeEach(() => {
- target = document.createElement('div');
- flyoutService = new FlyoutService(target);
- ref1 = flyoutService.openFlyout(i18nMock, Flyout content 1);
+ ref1 = flyouts.open(mountText('Flyout content'));
});
it('replaces the current flyout with a new one', () => {
- flyoutService.openFlyout(i18nMock, Flyout content 2);
+ flyouts.open(mountText('Flyout content 2'));
expect(mockReactDomRender.mock.calls).toMatchSnapshot();
expect(mockReactDomUnmount).toHaveBeenCalledTimes(1);
+ const modalContent = mount(mockReactDomRender.mock.calls[1][0]);
+ expect(modalContent.html()).toMatchSnapshot();
expect(() => ref1.close()).not.toThrowError();
expect(mockReactDomUnmount).toHaveBeenCalledTimes(1);
});
it('resolves onClose on the previous ref', async () => {
const onCloseComplete = jest.fn();
ref1.onClose.then(onCloseComplete);
- flyoutService.openFlyout(i18nMock, Flyout content 2);
+ flyouts.open(mountText('Flyout content 2'));
await ref1.onClose;
expect(onCloseComplete).toBeCalledTimes(1);
});
@@ -65,9 +81,7 @@ describe('FlyoutService', () => {
});
describe('FlyoutRef#close()', () => {
it('resolves the onClose Promise', async () => {
- const target = document.createElement('div');
- const flyoutService = new FlyoutService(target);
- const ref = flyoutService.openFlyout(i18nMock, Flyout content);
+ const ref = flyouts.open(mountText('Flyout content'));
const onCloseComplete = jest.fn();
ref.onClose.then(onCloseComplete);
@@ -76,9 +90,7 @@ describe('FlyoutService', () => {
expect(onCloseComplete).toHaveBeenCalledTimes(1);
});
it('can be called multiple times on the same FlyoutRef', async () => {
- const target = document.createElement('div');
- const flyoutService = new FlyoutService(target);
- const ref = flyoutService.openFlyout(i18nMock, Flyout content);
+ const ref = flyouts.open(mountText('Flyout content'));
expect(mockReactDomUnmount).not.toHaveBeenCalled();
await ref.close();
expect(mockReactDomUnmount.mock.calls).toMatchSnapshot();
@@ -86,10 +98,8 @@ describe('FlyoutService', () => {
expect(mockReactDomUnmount).toHaveBeenCalledTimes(1);
});
it("on a stale FlyoutRef doesn't affect the active flyout", async () => {
- const target = document.createElement('div');
- const flyoutService = new FlyoutService(target);
- const ref1 = flyoutService.openFlyout(i18nMock, Flyout content 1);
- const ref2 = flyoutService.openFlyout(i18nMock, Flyout content 2);
+ const ref1 = flyouts.open(mountText('Flyout content 1'));
+ const ref2 = flyouts.open(mountText('Flyout content 2'));
const onCloseComplete = jest.fn();
ref2.onClose.then(onCloseComplete);
mockReactDomUnmount.mockClear();
diff --git a/src/core/public/overlays/flyout.tsx b/src/core/public/overlays/flyout/flyout_service.tsx
similarity index 57%
rename from src/core/public/overlays/flyout.tsx
rename to src/core/public/overlays/flyout/flyout_service.tsx
index c8ba9c6b284d3..b609b2ce1d741 100644
--- a/src/core/public/overlays/flyout.tsx
+++ b/src/core/public/overlays/flyout/flyout_service.tsx
@@ -23,8 +23,10 @@ import { EuiFlyout } from '@elastic/eui';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { Subject } from 'rxjs';
-import { I18nStart } from '../i18n';
-import { OverlayRef } from './overlay_service';
+import { I18nStart } from '../../i18n';
+import { MountPoint } from '../../types';
+import { OverlayRef } from '../types';
+import { MountWrapper } from '../../utils';
/**
* A FlyoutRef is a reference to an opened flyout panel. It offers methods to
@@ -37,7 +39,7 @@ import { OverlayRef } from './overlay_service';
*
* @public
*/
-export class FlyoutRef implements OverlayRef {
+class FlyoutRef implements OverlayRef {
/**
* An Promise that will resolve once this flyout is closed.
*
@@ -66,55 +68,77 @@ export class FlyoutRef implements OverlayRef {
}
}
+/**
+ * APIs to open and manage fly-out dialogs.
+ *
+ * @public
+ */
+export interface OverlayFlyoutStart {
+ /**
+ * Opens a flyout panel with the given mount point inside. You can use
+ * `close()` on the returned FlyoutRef to close the flyout.
+ *
+ * @param mount {@link MountPoint} - Mounts the children inside a flyout panel
+ * @param options {@link OverlayFlyoutOpenOptions} - options for the flyout
+ * @return {@link OverlayRef} A reference to the opened flyout panel.
+ */
+ open(mount: MountPoint, options?: OverlayFlyoutOpenOptions): OverlayRef;
+}
+
+/**
+ * @public
+ */
+export interface OverlayFlyoutOpenOptions {
+ className?: string;
+ closeButtonAriaLabel?: string;
+ 'data-test-subj'?: string;
+}
+
+interface StartDeps {
+ i18n: I18nStart;
+ targetDomElement: Element;
+}
+
/** @internal */
export class FlyoutService {
private activeFlyout: FlyoutRef | null = null;
+ private targetDomElement: Element | null = null;
- constructor(private readonly targetDomElement: Element) {}
+ public start({ i18n, targetDomElement }: StartDeps): OverlayFlyoutStart {
+ this.targetDomElement = targetDomElement;
- /**
- * Opens a flyout panel with the given component inside. You can use
- * `close()` on the returned FlyoutRef to close the flyout.
- *
- * @param flyoutChildren - Mounts the children inside a flyout panel
- * @return {FlyoutRef} A reference to the opened flyout panel.
- */
- public openFlyout = (
- i18n: I18nStart,
- flyoutChildren: React.ReactNode,
- flyoutProps: {
- closeButtonAriaLabel?: string;
- 'data-test-subj'?: string;
- } = {}
- ): FlyoutRef => {
- // If there is an active flyout session close it before opening a new one.
- if (this.activeFlyout) {
- this.activeFlyout.close();
- this.cleanupDom();
- }
+ return {
+ open: (mount: MountPoint, options: OverlayFlyoutOpenOptions = {}): OverlayRef => {
+ // If there is an active flyout session close it before opening a new one.
+ if (this.activeFlyout) {
+ this.activeFlyout.close();
+ this.cleanupDom();
+ }
- const flyout = new FlyoutRef();
+ const flyout = new FlyoutRef();
- // If a flyout gets closed through it's FlyoutRef, remove it from the dom
- flyout.onClose.then(() => {
- if (this.activeFlyout === flyout) {
- this.cleanupDom();
- }
- });
+ // If a flyout gets closed through it's FlyoutRef, remove it from the dom
+ flyout.onClose.then(() => {
+ if (this.activeFlyout === flyout) {
+ this.cleanupDom();
+ }
+ });
- this.activeFlyout = flyout;
+ this.activeFlyout = flyout;
- render(
-
- flyout.close()}>
- {flyoutChildren}
-
- ,
- this.targetDomElement
- );
+ render(
+
+ flyout.close()}>
+
+
+ ,
+ this.targetDomElement
+ );
- return flyout;
- };
+ return flyout;
+ },
+ };
+ }
/**
* Using React.Render to re-render into a target DOM element will replace
@@ -124,8 +148,10 @@ export class FlyoutService {
* depend on unmounting for cleanup behaviour.
*/
private cleanupDom(): void {
- unmountComponentAtNode(this.targetDomElement);
- this.targetDomElement.innerHTML = '';
+ if (this.targetDomElement != null) {
+ unmountComponentAtNode(this.targetDomElement);
+ this.targetDomElement.innerHTML = '';
+ }
this.activeFlyout = null;
}
}
diff --git a/src/core/public/overlays/flyout/index.ts b/src/core/public/overlays/flyout/index.ts
new file mode 100644
index 0000000000000..b01cc3af5fa38
--- /dev/null
+++ b/src/core/public/overlays/flyout/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { FlyoutService, OverlayFlyoutStart, OverlayFlyoutOpenOptions } from './flyout_service';
diff --git a/src/core/public/overlays/index.ts b/src/core/public/overlays/index.ts
index ff03e5dffb2ca..417486f78f719 100644
--- a/src/core/public/overlays/index.ts
+++ b/src/core/public/overlays/index.ts
@@ -17,5 +17,8 @@
* under the License.
*/
+export { OverlayRef } from './types';
export { OverlayBannersStart } from './banners';
-export { OverlayService, OverlayStart, OverlayRef } from './overlay_service';
+export { OverlayFlyoutStart, OverlayFlyoutOpenOptions } from './flyout';
+export { OverlayModalStart, OverlayModalOpenOptions } from './modal';
+export { OverlayService, OverlayStart } from './overlay_service';
diff --git a/src/core/public/overlays/modal.tsx b/src/core/public/overlays/modal.tsx
deleted file mode 100644
index 6f94788b84d71..0000000000000
--- a/src/core/public/overlays/modal.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/* eslint-disable max-classes-per-file */
-
-import { EuiModal, EuiOverlayMask } from '@elastic/eui';
-import React from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { Subject } from 'rxjs';
-import { I18nStart } from '../i18n';
-import { OverlayRef } from './overlay_service';
-
-/**
- * A ModalRef is a reference to an opened modal. It offers methods to
- * close the modal.
- *
- * @public
- */
-export class ModalRef implements OverlayRef {
- public readonly onClose: Promise;
-
- private closeSubject = new Subject();
-
- constructor() {
- this.onClose = this.closeSubject.toPromise();
- }
-
- /**
- * Closes the referenced modal if it's still open which in turn will
- * resolve the `onClose` Promise. If the modal had already been
- * closed this method does nothing.
- */
- public close(): Promise {
- if (!this.closeSubject.closed) {
- this.closeSubject.next();
- this.closeSubject.complete();
- }
- return this.onClose;
- }
-}
-
-/** @internal */
-export class ModalService {
- private activeModal: ModalRef | null = null;
-
- constructor(private readonly targetDomElement: Element) {}
-
- /**
- * Opens a flyout panel with the given component inside. You can use
- * `close()` on the returned FlyoutRef to close the flyout.
- *
- * @param flyoutChildren - Mounts the children inside a flyout panel
- * @return {FlyoutRef} A reference to the opened flyout panel.
- */
- public openModal = (
- i18n: I18nStart,
- modalChildren: React.ReactNode,
- modalProps: {
- closeButtonAriaLabel?: string;
- 'data-test-subj'?: string;
- } = {}
- ): ModalRef => {
- // If there is an active flyout session close it before opening a new one.
- if (this.activeModal) {
- this.activeModal.close();
- this.cleanupDom();
- }
-
- const modal = new ModalRef();
-
- // If a modal gets closed through it's ModalRef, remove it from the dom
- modal.onClose.then(() => {
- if (this.activeModal === modal) {
- this.cleanupDom();
- }
- });
-
- this.activeModal = modal;
-
- render(
-
-
- modal.close()}>
- {modalChildren}
-
-
- ,
- this.targetDomElement
- );
-
- return modal;
- };
-
- /**
- * Using React.Render to re-render into a target DOM element will replace
- * the content of the target but won't call unmountComponent on any
- * components inside the target or any of their children. So we properly
- * cleanup the DOM here to prevent subtle bugs in child components which
- * depend on unmounting for cleanup behaviour.
- */
- private cleanupDom(): void {
- unmountComponentAtNode(this.targetDomElement);
- this.targetDomElement.innerHTML = '';
- this.activeModal = null;
- }
-}
diff --git a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
new file mode 100644
index 0000000000000..3928c54f90179
--- /dev/null
+++ b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
@@ -0,0 +1,69 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ModalService ModalRef#close() can be called multiple times on the same ModalRef 1`] = `
+Array [
+ Array [
+ ,
+ ],
+]
+`;
+
+exports[`ModalService openModal() renders a modal to the DOM 1`] = `
+Array [
+ Array [
+
+
+
+
+
+
+ ,
+ ,
+ ],
+]
+`;
+
+exports[`ModalService openModal() renders a modal to the DOM 2`] = `""`;
+
+exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = `
+Array [
+ Array [
+
+
+
+
+
+
+ ,
+ ,
+ ],
+ Array [
+
+
+
+
+
+
+ ,
+ ,
+ ],
+]
+`;
diff --git a/src/core/public/overlays/modal/index.ts b/src/core/public/overlays/modal/index.ts
new file mode 100644
index 0000000000000..9ef4492af3a3a
--- /dev/null
+++ b/src/core/public/overlays/modal/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { ModalService, OverlayModalStart, OverlayModalOpenOptions } from './modal_service';
diff --git a/src/core/public/overlays/modal/modal_service.mock.ts b/src/core/public/overlays/modal/modal_service.mock.ts
new file mode 100644
index 0000000000000..726209b8f277c
--- /dev/null
+++ b/src/core/public/overlays/modal/modal_service.mock.ts
@@ -0,0 +1,43 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ModalService, OverlayModalStart } from './modal_service';
+
+const createStartContractMock = () => {
+ const startContract: jest.Mocked = {
+ open: jest.fn().mockReturnValue({
+ close: jest.fn(),
+ onClose: Promise.resolve(),
+ }),
+ };
+ return startContract;
+};
+
+const createMock = () => {
+ const mocked: jest.Mocked> = {
+ start: jest.fn(),
+ };
+ mocked.start.mockReturnValue(createStartContractMock());
+ return mocked;
+};
+
+export const overlayModalServiceMock = {
+ create: createMock,
+ createStartContract: createStartContractMock,
+};
diff --git a/src/core/public/overlays/modal.test.tsx b/src/core/public/overlays/modal/modal_service.test.tsx
similarity index 65%
rename from src/core/public/overlays/modal.test.tsx
rename to src/core/public/overlays/modal/modal_service.test.tsx
index ee8bba5d61112..582c2697aef30 100644
--- a/src/core/public/overlays/modal.test.tsx
+++ b/src/core/public/overlays/modal/modal_service.test.tsx
@@ -16,11 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { mockReactDomRender, mockReactDomUnmount } from './flyout.test.mocks';
+import { mockReactDomRender, mockReactDomUnmount } from '../overlay.test.mocks';
import React from 'react';
-import { i18nServiceMock } from '../i18n/i18n_service.mock';
-import { ModalService, ModalRef } from './modal';
+import { mount } from 'enzyme';
+import { i18nServiceMock } from '../../i18n/i18n_service.mock';
+import { ModalService, OverlayModalStart } from './modal_service';
+import { mountReactNode } from '../../utils';
+import { OverlayRef } from '../types';
const i18nMock = i18nServiceMock.createStartContract();
@@ -29,45 +32,59 @@ beforeEach(() => {
mockReactDomUnmount.mockClear();
});
+const getServiceStart = () => {
+ const service = new ModalService();
+ return service.start({ i18n: i18nMock, targetDomElement: document.createElement('div') });
+};
+
describe('ModalService', () => {
+ let modals: OverlayModalStart;
+ beforeEach(() => {
+ modals = getServiceStart();
+ });
+
describe('openModal()', () => {
it('renders a modal to the DOM', () => {
- const target = document.createElement('div');
- const modalService = new ModalService(target);
expect(mockReactDomRender).not.toHaveBeenCalled();
- modalService.openModal(i18nMock, Modal content);
+ modals.open(container => {
+ const content = document.createElement('span');
+ content.textContent = 'Modal content';
+ container.append(content);
+ return () => {};
+ });
expect(mockReactDomRender.mock.calls).toMatchSnapshot();
+ const modalContent = mount(mockReactDomRender.mock.calls[0][0]);
+ expect(modalContent.html()).toMatchSnapshot();
});
+
describe('with a currently active modal', () => {
- let target: HTMLElement;
- let modalService: ModalService;
- let ref1: ModalRef;
+ let ref1: OverlayRef;
+
beforeEach(() => {
- target = document.createElement('div');
- modalService = new ModalService(target);
- ref1 = modalService.openModal(i18nMock, Modal content 1);
+ ref1 = modals.open(mountReactNode(Modal content 1));
});
+
it('replaces the current modal with a new one', () => {
- modalService.openModal(i18nMock, Flyout content 2);
+ modals.open(mountReactNode(Flyout content 2));
expect(mockReactDomRender.mock.calls).toMatchSnapshot();
expect(mockReactDomUnmount).toHaveBeenCalledTimes(1);
expect(() => ref1.close()).not.toThrowError();
expect(mockReactDomUnmount).toHaveBeenCalledTimes(1);
});
+
it('resolves onClose on the previous ref', async () => {
const onCloseComplete = jest.fn();
ref1.onClose.then(onCloseComplete);
- modalService.openModal(i18nMock, Flyout content 2);
+ modals.open(mountReactNode(Flyout content 2));
await ref1.onClose;
expect(onCloseComplete).toBeCalledTimes(1);
});
});
});
+
describe('ModalRef#close()', () => {
it('resolves the onClose Promise', async () => {
- const target = document.createElement('div');
- const modalService = new ModalService(target);
- const ref = modalService.openModal(i18nMock, Flyout content);
+ const ref = modals.open(mountReactNode(Flyout content));
const onCloseComplete = jest.fn();
ref.onClose.then(onCloseComplete);
@@ -75,21 +92,19 @@ describe('ModalService', () => {
await ref.close();
expect(onCloseComplete).toHaveBeenCalledTimes(1);
});
+
it('can be called multiple times on the same ModalRef', async () => {
- const target = document.createElement('div');
- const modalService = new ModalService(target);
- const ref = modalService.openModal(i18nMock, Flyout content);
+ const ref = modals.open(mountReactNode(Flyout content));
expect(mockReactDomUnmount).not.toHaveBeenCalled();
await ref.close();
expect(mockReactDomUnmount.mock.calls).toMatchSnapshot();
await ref.close();
expect(mockReactDomUnmount).toHaveBeenCalledTimes(1);
});
+
it("on a stale ModalRef doesn't affect the active flyout", async () => {
- const target = document.createElement('div');
- const modalService = new ModalService(target);
- const ref1 = modalService.openModal(i18nMock, Modal content 1);
- const ref2 = modalService.openModal(i18nMock, Modal content 2);
+ const ref1 = modals.open(mountReactNode(Modal content 1));
+ const ref2 = modals.open(mountReactNode(Modal content 2));
const onCloseComplete = jest.fn();
ref2.onClose.then(onCloseComplete);
mockReactDomUnmount.mockClear();
diff --git a/src/core/public/overlays/modal/modal_service.tsx b/src/core/public/overlays/modal/modal_service.tsx
new file mode 100644
index 0000000000000..cb77c2ec4c88c
--- /dev/null
+++ b/src/core/public/overlays/modal/modal_service.tsx
@@ -0,0 +1,148 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* eslint-disable max-classes-per-file */
+
+import { EuiModal, EuiOverlayMask } from '@elastic/eui';
+import React from 'react';
+import { render, unmountComponentAtNode } from 'react-dom';
+import { Subject } from 'rxjs';
+import { I18nStart } from '../../i18n';
+import { MountPoint } from '../../types';
+import { OverlayRef } from '../types';
+import { MountWrapper } from '../../utils';
+
+/**
+ * A ModalRef is a reference to an opened modal. It offers methods to
+ * close the modal.
+ *
+ * @public
+ */
+class ModalRef implements OverlayRef {
+ public readonly onClose: Promise;
+
+ private closeSubject = new Subject();
+
+ constructor() {
+ this.onClose = this.closeSubject.toPromise();
+ }
+
+ /**
+ * Closes the referenced modal if it's still open which in turn will
+ * resolve the `onClose` Promise. If the modal had already been
+ * closed this method does nothing.
+ */
+ public close(): Promise {
+ if (!this.closeSubject.closed) {
+ this.closeSubject.next();
+ this.closeSubject.complete();
+ }
+ return this.onClose;
+ }
+}
+
+/**
+ * APIs to open and manage modal dialogs.
+ *
+ * @public
+ */
+export interface OverlayModalStart {
+ /**
+ * Opens a modal panel with the given mount point inside. You can use
+ * `close()` on the returned OverlayRef to close the modal.
+ *
+ * @param mount {@link MountPoint} - Mounts the children inside the modal
+ * @param options {@link OverlayModalOpenOptions} - options for the modal
+ * @return {@link OverlayRef} A reference to the opened modal.
+ */
+ open(mount: MountPoint, options?: OverlayModalOpenOptions): OverlayRef;
+}
+
+/**
+ * @public
+ */
+export interface OverlayModalOpenOptions {
+ className?: string;
+ closeButtonAriaLabel?: string;
+ 'data-test-subj'?: string;
+}
+
+interface StartDeps {
+ i18n: I18nStart;
+ targetDomElement: Element;
+}
+
+/** @internal */
+export class ModalService {
+ private activeModal: ModalRef | null = null;
+ private targetDomElement: Element | null = null;
+
+ public start({ i18n, targetDomElement }: StartDeps): OverlayModalStart {
+ this.targetDomElement = targetDomElement;
+
+ return {
+ open: (mount: MountPoint, options: OverlayModalOpenOptions = {}): OverlayRef => {
+ // If there is an active flyout session close it before opening a new one.
+ if (this.activeModal) {
+ this.activeModal.close();
+ this.cleanupDom();
+ }
+
+ const modal = new ModalRef();
+
+ // If a modal gets closed through it's ModalRef, remove it from the dom
+ modal.onClose.then(() => {
+ if (this.activeModal === modal) {
+ this.cleanupDom();
+ }
+ });
+
+ this.activeModal = modal;
+
+ render(
+
+
+ modal.close()}>
+
+
+
+ ,
+ targetDomElement
+ );
+
+ return modal;
+ },
+ };
+ }
+
+ /**
+ * Using React.Render to re-render into a target DOM element will replace
+ * the content of the target but won't call unmountComponent on any
+ * components inside the target or any of their children. So we properly
+ * cleanup the DOM here to prevent subtle bugs in child components which
+ * depend on unmounting for cleanup behaviour.
+ */
+ private cleanupDom(): void {
+ if (this.targetDomElement != null) {
+ unmountComponentAtNode(this.targetDomElement);
+ this.targetDomElement.innerHTML = '';
+ }
+ this.activeModal = null;
+ }
+}
diff --git a/src/core/public/overlays/flyout.test.mocks.ts b/src/core/public/overlays/overlay.test.mocks.ts
similarity index 88%
rename from src/core/public/overlays/flyout.test.mocks.ts
rename to src/core/public/overlays/overlay.test.mocks.ts
index 35a046e960077..563f414a0ae99 100644
--- a/src/core/public/overlays/flyout.test.mocks.ts
+++ b/src/core/public/overlays/overlay.test.mocks.ts
@@ -19,7 +19,9 @@
export const mockReactDomRender = jest.fn();
export const mockReactDomUnmount = jest.fn();
+export const mockReactDomCreatePortal = jest.fn().mockImplementation(component => component);
jest.doMock('react-dom', () => ({
render: mockReactDomRender,
+ createPortal: mockReactDomCreatePortal,
unmountComponentAtNode: mockReactDomUnmount,
}));
diff --git a/src/core/public/overlays/overlay_service.mock.ts b/src/core/public/overlays/overlay_service.mock.ts
index 39abc6f765b97..2937ec89bfc74 100644
--- a/src/core/public/overlays/overlay_service.mock.ts
+++ b/src/core/public/overlays/overlay_service.mock.ts
@@ -18,17 +18,15 @@
*/
import { OverlayService, OverlayStart } from './overlay_service';
import { overlayBannersServiceMock } from './banners/banners_service.mock';
+import { overlayFlyoutServiceMock } from './flyout/flyout_service.mock';
+import { overlayModalServiceMock } from './modal/modal_service.mock';
const createStartContractMock = () => {
const startContract: DeeplyMockedKeys = {
- openFlyout: jest.fn(),
- openModal: jest.fn(),
+ openFlyout: overlayFlyoutServiceMock.createStartContract().open,
+ openModal: overlayModalServiceMock.createStartContract().open,
banners: overlayBannersServiceMock.createStartContract(),
};
- startContract.openModal.mockReturnValue({
- close: jest.fn(),
- onClose: Promise.resolve(),
- });
return startContract;
};
diff --git a/src/core/public/overlays/overlay_service.ts b/src/core/public/overlays/overlay_service.ts
index 1a72bb5dbe435..82fe753d6f283 100644
--- a/src/core/public/overlays/overlay_service.ts
+++ b/src/core/public/overlays/overlay_service.ts
@@ -17,34 +17,11 @@
* under the License.
*/
-import React from 'react';
-
-import { FlyoutService } from './flyout';
-import { ModalService } from './modal';
import { I18nStart } from '../i18n';
-import { OverlayBannersStart, OverlayBannersService } from './banners';
import { UiSettingsClientContract } from '../ui_settings';
-
-/**
- * Returned by {@link OverlayStart} methods for closing a mounted overlay.
- * @public
- */
-export interface OverlayRef {
- /**
- * A Promise that will resolve once this overlay is closed.
- *
- * Overlays can close from user interaction, calling `close()` on the overlay
- * reference or another overlay replacing yours via `openModal` or `openFlyout`.
- */
- onClose: Promise;
-
- /**
- * Closes the referenced overlay if it's still open which in turn will
- * resolve the `onClose` Promise. If the overlay had already been
- * closed this method does nothing.
- */
- close(): Promise;
-}
+import { OverlayBannersStart, OverlayBannersService } from './banners';
+import { FlyoutService, OverlayFlyoutStart } from './flyout';
+import { ModalService, OverlayModalStart } from './modal';
interface StartDeps {
i18n: I18nStart;
@@ -54,19 +31,25 @@ interface StartDeps {
/** @internal */
export class OverlayService {
+ private bannersService = new OverlayBannersService();
+ private modalService = new ModalService();
+ private flyoutService = new FlyoutService();
+
public start({ i18n, targetDomElement, uiSettings }: StartDeps): OverlayStart {
const flyoutElement = document.createElement('div');
- const modalElement = document.createElement('div');
targetDomElement.appendChild(flyoutElement);
+ const flyouts = this.flyoutService.start({ i18n, targetDomElement: flyoutElement });
+
+ const banners = this.bannersService.start({ i18n, uiSettings });
+
+ const modalElement = document.createElement('div');
targetDomElement.appendChild(modalElement);
- const flyoutService = new FlyoutService(flyoutElement);
- const modalService = new ModalService(modalElement);
- const bannersService = new OverlayBannersService();
+ const modals = this.modalService.start({ i18n, targetDomElement: modalElement });
return {
- banners: bannersService.start({ i18n, uiSettings }),
- openFlyout: flyoutService.openFlyout.bind(flyoutService, i18n),
- openModal: modalService.openModal.bind(modalService, i18n),
+ banners,
+ openFlyout: flyouts.open.bind(flyouts),
+ openModal: modals.open.bind(modals),
};
}
}
@@ -75,19 +58,8 @@ export class OverlayService {
export interface OverlayStart {
/** {@link OverlayBannersStart} */
banners: OverlayBannersStart;
- openFlyout: (
- flyoutChildren: React.ReactNode,
- flyoutProps?: {
- closeButtonAriaLabel?: string;
- 'data-test-subj'?: string;
- }
- ) => OverlayRef;
- openModal: (
- modalChildren: React.ReactNode,
- modalProps?: {
- className?: string;
- closeButtonAriaLabel?: string;
- 'data-test-subj'?: string;
- }
- ) => OverlayRef;
+ /** {@link OverlayFlyoutStart#open} */
+ openFlyout: OverlayFlyoutStart['open'];
+ /** {@link OverlayModalStart#open} */
+ openModal: OverlayModalStart['open'];
}
diff --git a/src/core/public/overlays/types.ts b/src/core/public/overlays/types.ts
new file mode 100644
index 0000000000000..d5bd01c672d1f
--- /dev/null
+++ b/src/core/public/overlays/types.ts
@@ -0,0 +1,39 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Returned by {@link OverlayStart} methods for closing a mounted overlay.
+ * @public
+ */
+export interface OverlayRef {
+ /**
+ * A Promise that will resolve once this overlay is closed.
+ *
+ * Overlays can close from user interaction, calling `close()` on the overlay
+ * reference or another overlay replacing yours via `openModal` or `openFlyout`.
+ */
+ onClose: Promise;
+
+ /**
+ * Closes the referenced overlay if it's still open which in turn will
+ * resolve the `onClose` Promise. If the overlay had already been
+ * closed this method does nothing.
+ */
+ close(): Promise;
+}
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 1e97d8e066d09..7abbbcd32fbb8 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -124,7 +124,7 @@ export type ChromeHelpExtension = (element: HTMLDivElement) => () => void;
// @public (undocumented)
export interface ChromeNavControl {
// (undocumented)
- mount(targetDomElement: HTMLElement): () => void;
+ mount: MountPoint;
// (undocumented)
order?: number;
}
@@ -620,7 +620,7 @@ export interface LegacyNavLink {
}
// @public
-export type MountPoint = (element: HTMLElement) => UnmountCallback;
+export type MountPoint = (element: T) => UnmountCallback;
// @public (undocumented)
export interface NotificationsSetup {
@@ -657,17 +657,14 @@ export interface OverlayRef {
export interface OverlayStart {
// (undocumented)
banners: OverlayBannersStart;
+ // Warning: (ae-forgotten-export) The symbol "OverlayFlyoutStart" needs to be exported by the entry point index.d.ts
+ //
// (undocumented)
- openFlyout: (flyoutChildren: React.ReactNode, flyoutProps?: {
- closeButtonAriaLabel?: string;
- 'data-test-subj'?: string;
- }) => OverlayRef;
+ openFlyout: OverlayFlyoutStart['open'];
+ // Warning: (ae-forgotten-export) The symbol "OverlayModalStart" needs to be exported by the entry point index.d.ts
+ //
// (undocumented)
- openModal: (modalChildren: React.ReactNode, modalProps?: {
- className?: string;
- closeButtonAriaLabel?: string;
- 'data-test-subj'?: string;
- }) => OverlayRef;
+ openModal: OverlayModalStart['open'];
}
// @public (undocumented)
@@ -941,9 +938,12 @@ export class ToastsApi implements IToasts {
addSuccess(toastOrTitle: ToastInput): Toast;
addWarning(toastOrTitle: ToastInput): Toast;
get$(): Rx.Observable;
- // @internal (undocumented)
- registerOverlays(overlays: OverlayStart): void;
remove(toastOrId: Toast | string): void;
+ // @internal (undocumented)
+ start({ overlays, i18n }: {
+ overlays: OverlayStart;
+ i18n: I18nStart;
+ }): void;
}
// @public (undocumented)
diff --git a/src/core/public/types.ts b/src/core/public/types.ts
index 4b12d5bc6da51..5abbb3c55813a 100644
--- a/src/core/public/types.ts
+++ b/src/core/public/types.ts
@@ -26,7 +26,7 @@
*
* @public
*/
-export type MountPoint = (element: HTMLElement) => UnmountCallback;
+export type MountPoint = (element: T) => UnmountCallback;
/**
* A function that will unmount the element previously mounted by
diff --git a/src/core/public/utils/mount.test.tsx b/src/core/public/utils/mount.test.tsx
new file mode 100644
index 0000000000000..1cacb7d6a796c
--- /dev/null
+++ b/src/core/public/utils/mount.test.tsx
@@ -0,0 +1,79 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { mount } from 'enzyme';
+import { MountWrapper, mountReactNode } from './mount';
+
+describe('MountWrapper', () => {
+ it('renders an html element in react tree', () => {
+ const mountPoint = (container: HTMLElement) => {
+ const el = document.createElement('p');
+ el.textContent = 'hello';
+ el.className = 'bar';
+ container.append(el);
+ return () => {};
+ };
+ const wrapper = ;
+ const container = mount(wrapper);
+ expect(container.html()).toMatchInlineSnapshot(
+ `""`
+ );
+ });
+
+ it('updates the react tree when the mounted element changes', () => {
+ const el = document.createElement('p');
+ el.textContent = 'initial';
+
+ const mountPoint = (container: HTMLElement) => {
+ container.append(el);
+ return () => {};
+ };
+
+ const wrapper = ;
+ const container = mount(wrapper);
+ expect(container.html()).toMatchInlineSnapshot(
+ `""`
+ );
+
+ el.textContent = 'changed';
+ container.update();
+ expect(container.html()).toMatchInlineSnapshot(
+ `""`
+ );
+ });
+
+ it('can render a detached react component', () => {
+ const mountPoint = mountReactNode(detached);
+ const wrapper = ;
+ const container = mount(wrapper);
+ expect(container.html()).toMatchInlineSnapshot(
+ `"detached
"`
+ );
+ });
+
+ it('accepts a className prop to override default className', () => {
+ const mountPoint = mountReactNode(detached);
+ const wrapper = ;
+ const container = mount(wrapper);
+ expect(container.html()).toMatchInlineSnapshot(
+ `"detached
"`
+ );
+ });
+});
diff --git a/src/core/public/utils/mount.tsx b/src/core/public/utils/mount.tsx
index dbd7d5da435a6..0fee67a6e7fbc 100644
--- a/src/core/public/utils/mount.tsx
+++ b/src/core/public/utils/mount.tsx
@@ -22,23 +22,26 @@ import { render, unmountComponentAtNode } from 'react-dom';
import { I18nProvider } from '@kbn/i18n/react';
import { MountPoint } from '../types';
+const defaultWrapperClass = 'kbnMountWrapper';
+
/**
* MountWrapper is a react component to mount a {@link MountPoint} inside a react tree.
*/
-export const MountWrapper: React.FunctionComponent<{ mount: MountPoint }> = ({ mount }) => {
+export const MountWrapper: React.FunctionComponent<{ mount: MountPoint; className?: string }> = ({
+ mount,
+ className = defaultWrapperClass,
+}) => {
const element = useRef(null);
useEffect(() => mount(element.current!), [mount]);
- return ;
+ return ;
};
/**
- * Mount converter for react components.
+ * Mount converter for react node.
*
- * @param component to get a mount for
+ * @param node to get a mount for
*/
-export const mountReactNode = (component: React.ReactNode): MountPoint => (
- element: HTMLElement
-) => {
- render({component}, element);
+export const mountReactNode = (node: React.ReactNode): MountPoint => (element: HTMLElement) => {
+ render({node}, element);
return () => unmountComponentAtNode(element);
};
diff --git a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts
index abe9ec6d6e873..39ec1f78b65f0 100644
--- a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts
+++ b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts
@@ -19,6 +19,7 @@
import { i18n } from '@kbn/i18n';
import { CoreStart } from 'src/core/public';
+import { toMountPoint } from '../../../../../../plugins/kibana_react/public';
import {
IAction,
createAction,
@@ -79,17 +80,19 @@ export function createFilterAction(
const filterSelectionPromise: Promise = new Promise(resolve => {
const overlay = overlays.openModal(
- applyFiltersPopover(
- filters,
- indexPatterns,
- () => {
- overlay.close();
- resolve([]);
- },
- (filterSelection: esFilters.Filter[]) => {
- overlay.close();
- resolve(filterSelection);
- }
+ toMountPoint(
+ applyFiltersPopover(
+ filters,
+ indexPatterns,
+ () => {
+ overlay.close();
+ resolve([]);
+ },
+ (filterSelection: esFilters.Filter[]) => {
+ overlay.close();
+ resolve(filterSelection);
+ }
+ )
),
{
'data-test-subj': 'test',
diff --git a/src/legacy/ui/public/courier/fetch/components/shard_failure_open_modal_button.tsx b/src/legacy/ui/public/courier/fetch/components/shard_failure_open_modal_button.tsx
index 5e53477b8ec04..b02344ce6dd72 100644
--- a/src/legacy/ui/public/courier/fetch/components/shard_failure_open_modal_button.tsx
+++ b/src/legacy/ui/public/courier/fetch/components/shard_failure_open_modal_button.tsx
@@ -22,6 +22,7 @@ import { npStart } from 'ui/new_platform';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiButton, EuiTextAlign } from '@elastic/eui';
+import { toMountPoint } from '../../../../../../plugins/kibana_react/public';
import { ShardFailureModal } from './shard_failure_modal';
import { ResponseWithShardFailure, Request } from './shard_failure_types';
@@ -34,12 +35,14 @@ interface Props {
export function ShardFailureOpenModalButton({ request, response, title }: Props) {
function onClick() {
const modal = npStart.core.overlays.openModal(
- modal.close()}
- />,
+ toMountPoint(
+ modal.close()}
+ />
+ ),
{
className: 'shardFailureModal',
}
diff --git a/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx b/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx
index cb354375e7a68..b30733760bbdf 100644
--- a/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx
@@ -18,6 +18,7 @@
*/
import React from 'react';
import { CoreStart } from '../../../../core/public';
+import { toMountPoint } from '../../../../plugins/kibana_react/public';
import { ReplacePanelFlyout } from './replace_panel_flyout';
import {
IEmbeddable,
@@ -44,18 +45,20 @@ export async function openReplacePanelFlyout(options: {
getEmbeddableFactories,
} = options;
const flyoutSession = core.overlays.openFlyout(
- {
- if (flyoutSession) {
- flyoutSession.close();
- }
- }}
- panelToRemove={panelToRemove}
- savedObjectsFinder={savedObjectFinder}
- notifications={notifications}
- getEmbeddableFactories={getEmbeddableFactories}
- />,
+ toMountPoint(
+ {
+ if (flyoutSession) {
+ flyoutSession.close();
+ }
+ }}
+ panelToRemove={panelToRemove}
+ savedObjectsFinder={savedObjectFinder}
+ notifications={notifications}
+ getEmbeddableFactories={getEmbeddableFactories}
+ />
+ ),
{
'data-test-subj': 'replacePanelFlyout',
}
diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
index 8a8a2f44b7fd8..33be01529c631 100644
--- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
+++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
@@ -25,7 +25,8 @@ import {
TGetActionsCompatibleWithTrigger,
IAction,
} from '../ui_actions';
-import { CoreStart } from '../../../../../core/public';
+import { CoreStart, OverlayStart } from '../../../../../core/public';
+import { toMountPoint } from '../../../../kibana_react/public';
import { Start as InspectorStartContract } from '../inspector';
import { CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER } from '../triggers';
@@ -200,17 +201,19 @@ export class EmbeddablePanel extends React.Component {
embeddable: this.props.embeddable,
});
- const createGetUserData = (overlays: CoreStart['overlays']) =>
+ const createGetUserData = (overlays: OverlayStart) =>
async function getUserData(context: { embeddable: IEmbeddable }) {
return new Promise<{ title: string | undefined }>(resolve => {
const session = overlays.openModal(
- {
- session.close();
- resolve({ title });
- }}
- />,
+ toMountPoint(
+ {
+ session.close();
+ resolve({ title });
+ }}
+ />
+ ),
{
'data-test-subj': 'customizePanel',
}
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts
index 3ca3a0864d9f1..9ecc4686c21b6 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts
@@ -18,8 +18,7 @@
*/
import { i18n } from '@kbn/i18n';
import { IAction } from 'src/plugins/ui_actions/public';
-import { NotificationsStart } from 'src/core/public';
-import { KibanaReactOverlays } from 'src/plugins/kibana_react/public';
+import { NotificationsStart, OverlayStart } from 'src/core/public';
import { ViewMode, GetEmbeddableFactory, GetEmbeddableFactories } from '../../../../types';
import { openAddPanelFlyout } from './open_add_panel_flyout';
import { IContainer } from '../../../../containers';
@@ -37,7 +36,7 @@ export class AddPanelAction implements IAction {
constructor(
private readonly getFactory: GetEmbeddableFactory,
private readonly getAllFactories: GetEmbeddableFactories,
- private readonly overlays: KibanaReactOverlays,
+ private readonly overlays: OverlayStart,
private readonly notifications: NotificationsStart,
private readonly SavedObjectFinder: React.ComponentType
) {}
diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx
index bfa4f6e31d84e..481693501066c 100644
--- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx
+++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx
@@ -17,8 +17,8 @@
* under the License.
*/
import React from 'react';
-import { NotificationsStart } from 'src/core/public';
-import { KibanaReactOverlays } from 'src/plugins/kibana_react/public';
+import { NotificationsStart, OverlayStart } from 'src/core/public';
+import { toMountPoint } from '../../../../../../../kibana_react/public';
import { IContainer } from '../../../../containers';
import { AddPanelFlyout } from './add_panel_flyout';
import { GetEmbeddableFactory, GetEmbeddableFactories } from '../../../../types';
@@ -27,7 +27,7 @@ export async function openAddPanelFlyout(options: {
embeddable: IContainer;
getFactory: GetEmbeddableFactory;
getAllFactories: GetEmbeddableFactories;
- overlays: KibanaReactOverlays;
+ overlays: OverlayStart;
notifications: NotificationsStart;
SavedObjectFinder: React.ComponentType;
}) {
@@ -40,18 +40,20 @@ export async function openAddPanelFlyout(options: {
SavedObjectFinder,
} = options;
const flyoutSession = overlays.openFlyout(
- {
- if (flyoutSession) {
- flyoutSession.close();
- }
- }}
- getFactory={getFactory}
- getAllFactories={getAllFactories}
- notifications={notifications}
- SavedObjectFinder={SavedObjectFinder}
- />,
+ toMountPoint(
+ {
+ if (flyoutSession) {
+ flyoutSession.close();
+ }
+ }}
+ getFactory={getFactory}
+ getAllFactories={getAllFactories}
+ notifications={notifications}
+ SavedObjectFinder={SavedObjectFinder}
+ />
+ ),
{
'data-test-subj': 'addPanelFlyout',
}
diff --git a/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx b/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx
index fc20a99987484..502269d7ac193 100644
--- a/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx
+++ b/src/plugins/embeddable/public/lib/test_samples/actions/send_message_action.tsx
@@ -20,6 +20,7 @@ import React from 'react';
import { EuiFlyoutBody } from '@elastic/eui';
import { createAction, IncompatibleActionError } from '../../ui_actions';
import { CoreStart } from '../../../../../../core/public';
+import { toMountPoint } from '../../../../../kibana_react/public';
import { Embeddable, EmbeddableInput } from '../../embeddables';
import { GetMessageModal } from './get_message_modal';
import { FullNameEmbeddableOutput, hasFullNameOutput } from './say_hello_action';
@@ -38,7 +39,7 @@ export function createSendMessageAction(overlays: CoreStart['overlays']) {
const greeting = `Hello, ${context.embeddable.getOutput().fullName}`;
const content = message ? `${greeting}. ${message}` : greeting;
- overlays.openFlyout({content});
+ overlays.openFlyout(toMountPoint({content}));
};
return createAction({
@@ -51,13 +52,15 @@ export function createSendMessageAction(overlays: CoreStart['overlays']) {
}
const modal = overlays.openModal(
- modal.close()}
- onDone={message => {
- modal.close();
- sendMessage(context, message);
- }}
- />
+ toMountPoint(
+ modal.close()}
+ onDone={message => {
+ modal.close();
+ sendMessage(context, message);
+ }}
+ />
+ )
);
},
});
diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx
index 962cddfa3735f..d1eea5d67fb41 100644
--- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx
+++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx
@@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n';
import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public';
import { CoreStart } from 'src/core/public';
+import { toMountPoint } from '../../../../../../kibana_react/public';
import { EmbeddableFactory } from '../../../embeddables';
import { Container } from '../../../containers';
import { ContactCardEmbeddable, ContactCardEmbeddableInput } from './contact_card_embeddable';
@@ -54,16 +55,18 @@ export class ContactCardEmbeddableFactory extends EmbeddableFactory> {
return new Promise(resolve => {
const modalSession = this.overlays.openModal(
- {
- modalSession.close();
- resolve(undefined);
- }}
- onCreate={(input: { firstName: string; lastName?: string }) => {
- modalSession.close();
- resolve(input);
- }}
- />,
+ toMountPoint(
+ {
+ modalSession.close();
+ resolve(undefined);
+ }}
+ onCreate={(input: { firstName: string; lastName?: string }) => {
+ modalSession.close();
+ resolve(input);
+ }}
+ />
+ ),
{
'data-test-subj': 'createContactCardEmbeddable',
}
diff --git a/src/plugins/inspector/public/plugin.tsx b/src/plugins/inspector/public/plugin.tsx
index 00714c5a8205c..cc9f2404d802f 100644
--- a/src/plugins/inspector/public/plugin.tsx
+++ b/src/plugins/inspector/public/plugin.tsx
@@ -20,6 +20,7 @@
import { i18n } from '@kbn/i18n';
import * as React from 'react';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public';
+import { toMountPoint } from '../../kibana_react/public';
import { InspectorViewRegistry } from './view_registry';
import { Adapters, InspectorOptions, InspectorSession } from './types';
import { InspectorPanel } from './ui/inspector_panel';
@@ -99,7 +100,7 @@ export class InspectorPublicPlugin implements Plugin {
}
return core.overlays.openFlyout(
- ,
+ toMountPoint(),
{
'data-test-subj': 'inspectorPanel',
closeButtonAriaLabel: closeButtonLabel,
diff --git a/src/plugins/kibana_react/public/overlays/create_react_overlays.test.tsx b/src/plugins/kibana_react/public/overlays/create_react_overlays.test.tsx
index b10cbbc87be7c..c4981c72f1a78 100644
--- a/src/plugins/kibana_react/public/overlays/create_react_overlays.test.tsx
+++ b/src/plugins/kibana_react/public/overlays/create_react_overlays.test.tsx
@@ -48,13 +48,11 @@ test('can open flyout with React element', () => {
overlays.openFlyout(foo
);
expect(coreOverlays.openFlyout).toHaveBeenCalledTimes(1);
- expect(coreOverlays.openFlyout.mock.calls[0][0]).toMatchInlineSnapshot(`
-
-
- foo
-
-
- `);
+
+ const container = document.createElement('div');
+ const mount = coreOverlays.openFlyout.mock.calls[0][0];
+ mount(container);
+ expect(container.innerHTML).toMatchInlineSnapshot(`"foo
"`);
});
test('can open modal with React element', () => {
@@ -68,13 +66,10 @@ test('can open modal with React element', () => {
overlays.openModal(bar
);
expect(coreOverlays.openModal).toHaveBeenCalledTimes(1);
- expect(coreOverlays.openModal.mock.calls[0][0]).toMatchInlineSnapshot(`
-
-
- bar
-
-
- `);
+ const container = document.createElement('div');
+ const mount = coreOverlays.openModal.mock.calls[0][0];
+ mount(container);
+ expect(container.innerHTML).toMatchInlineSnapshot(`"bar
"`);
});
test('passes through flyout options when opening flyout', () => {
diff --git a/src/plugins/kibana_react/public/overlays/create_react_overlays.tsx b/src/plugins/kibana_react/public/overlays/create_react_overlays.tsx
index a62c0970cf525..6d7b34128f3fe 100644
--- a/src/plugins/kibana_react/public/overlays/create_react_overlays.tsx
+++ b/src/plugins/kibana_react/public/overlays/create_react_overlays.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import { KibanaServices } from '../context/types';
import { KibanaReactOverlays } from './types';
+import { toMountPoint } from '../util';
export const createReactOverlays = (services: KibanaServices): KibanaReactOverlays => {
const checkCoreService = () => {
@@ -30,12 +31,12 @@ export const createReactOverlays = (services: KibanaServices): KibanaReactOverla
const openFlyout: KibanaReactOverlays['openFlyout'] = (node, options?) => {
checkCoreService();
- return services.overlays!.openFlyout(<>{node}>, options);
+ return services.overlays!.openFlyout(toMountPoint(<>{node}>), options);
};
const openModal: KibanaReactOverlays['openModal'] = (node, options?) => {
checkCoreService();
- return services.overlays!.openModal(<>{node}>, options);
+ return services.overlays!.openModal(toMountPoint(<>{node}>), options);
};
const overlays: KibanaReactOverlays = {
diff --git a/src/plugins/kibana_react/public/overlays/types.ts b/src/plugins/kibana_react/public/overlays/types.ts
index 0108d8eaba6cc..9822e80376d94 100644
--- a/src/plugins/kibana_react/public/overlays/types.ts
+++ b/src/plugins/kibana_react/public/overlays/types.ts
@@ -27,6 +27,6 @@ export interface KibanaReactOverlays {
) => ReturnType;
openModal: (
node: React.ReactNode,
- options?: Parameters['1']
+ options?: Parameters['1']
) => ReturnType;
}
diff --git a/src/plugins/ui_actions/public/tests/test_samples/hello_world_action.tsx b/src/plugins/ui_actions/public/tests/test_samples/hello_world_action.tsx
index 88a0c4ca08145..9e3d206c9a6dc 100644
--- a/src/plugins/ui_actions/public/tests/test_samples/hello_world_action.tsx
+++ b/src/plugins/ui_actions/public/tests/test_samples/hello_world_action.tsx
@@ -21,6 +21,7 @@ import React from 'react';
import { EuiFlyout } from '@elastic/eui';
import { CoreStart } from 'src/core/public';
import { createAction, IAction } from '../../actions';
+import { toMountPoint } from '../../../../kibana_react/public';
export const HELLO_WORLD_ACTION_ID = 'HELLO_WORLD_ACTION_ID';
@@ -29,9 +30,11 @@ export function createHelloWorldAction(overlays: CoreStart['overlays']): IAction
type: HELLO_WORLD_ACTION_ID,
execute: async () => {
const flyoutSession = overlays.openFlyout(
- flyoutSession && flyoutSession.close()}>
- Hello World, I am a hello world action!
- ,
+ toMountPoint(
+ flyoutSession && flyoutSession.close()}>
+ Hello World, I am a hello world action!
+
+ ),
{
'data-test-subj': 'helloWorldAction',
}
diff --git a/src/plugins/ui_actions/public/tests/test_samples/say_hello_action.tsx b/src/plugins/ui_actions/public/tests/test_samples/say_hello_action.tsx
index 3c4ecfb6e7c8a..e984dd8fb64cc 100644
--- a/src/plugins/ui_actions/public/tests/test_samples/say_hello_action.tsx
+++ b/src/plugins/ui_actions/public/tests/test_samples/say_hello_action.tsx
@@ -21,6 +21,7 @@ import React from 'react';
import { EuiFlyout } from '@elastic/eui';
import { CoreStart } from 'src/core/public';
import { IAction, createAction } from '../../actions';
+import { toMountPoint } from '../../../../kibana_react/public';
export const SAY_HELLO_ACTION = 'SAY_HELLO_ACTION';
@@ -31,9 +32,11 @@ export function createSayHelloAction(overlays: CoreStart['overlays']): IAction<{
isCompatible: async ({ name }) => name !== undefined,
execute: async context => {
const flyoutSession = overlays.openFlyout(
- flyoutSession && flyoutSession.close()}>
- this.getDisplayName(context)
- ,
+ toMountPoint(
+ flyoutSession && flyoutSession.close()}>
+ this.getDisplayName(context)
+
+ ),
{
'data-test-subj': 'sayHelloAction',
}
diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx
index 71724595d462a..4ce748e2c7118 100644
--- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx
+++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/public/sample_panel_action.tsx
@@ -22,6 +22,7 @@ import { npStart, npSetup } from 'ui/new_platform';
import { CONTEXT_MENU_TRIGGER, IEmbeddable } from '../../../../../src/plugins/embeddable/public';
import { createAction } from '../../../../../src/plugins/ui_actions/public';
+import { toMountPoint } from '../../../../../src/plugins/kibana_react/public';
interface ActionContext {
embeddable: IEmbeddable;
@@ -36,16 +37,18 @@ function createSamplePanelAction() {
return;
}
npStart.core.overlays.openFlyout(
-
-
-
- {embeddable.getTitle()}
-
-
-
- This is a sample action
-
- ,
+ toMountPoint(
+
+
+
+ {embeddable.getTitle()}
+
+
+
+ This is a sample action
+
+
+ ),
{
'data-test-subj': 'samplePanelActionFlyout',
}
diff --git a/x-pack/legacy/plugins/graph/public/app.js b/x-pack/legacy/plugins/graph/public/app.js
index b60f6b267ad84..aa08841e03f52 100644
--- a/x-pack/legacy/plugins/graph/public/app.js
+++ b/x-pack/legacy/plugins/graph/public/app.js
@@ -11,6 +11,7 @@ import React from 'react';
import { Provider } from 'react-redux';
import { isColorDark, hexToRgb } from '@elastic/eui';
+import { toMountPoint } from '../../../../../src/plugins/kibana_react/public';
import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal';
import { addAppRedirectMessageToUrl } from 'ui/notify';
@@ -551,9 +552,11 @@ export function initGraphApp(angularModule, deps) {
canEditDrillDownUrls: canEditDrillDownUrls
}), $scope.$digest.bind($scope));
coreStart.overlays.openFlyout(
-
-
- , {
+ toMountPoint(
+
+
+
+ ), {
size: 'm',
closeButtonAriaLabel: i18n.translate('xpack.graph.settings.closeLabel', { defaultMessage: 'Close' }),
'data-test-subj': 'graphSettingsFlyout',
diff --git a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx
index 369963fb46097..8dede207b803c 100644
--- a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx
+++ b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx
@@ -80,7 +80,8 @@ function GuidancePanelComponent(props: GuidancePanelProps) {
} = props;
const kibana = useKibana();
- const { overlays, savedObjects, uiSettings, chrome, application } = kibana.services;
+ const { services, overlays } = kibana;
+ const { savedObjects, uiSettings, chrome, application } = services;
if (!overlays || !chrome || !application) return null;
const onOpenDatasourcePicker = () => {
diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.tsx
index 293219cca9876..b6200d831b248 100644
--- a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx
+++ b/x-pack/legacy/plugins/graph/public/components/search_bar.tsx
@@ -86,7 +86,8 @@ export function SearchBarComponent(props: SearchBarProps) {
}, [currentDatasource]);
const kibana = useKibana();
- const { overlays, savedObjects, uiSettings } = kibana.services;
+ const { services, overlays } = kibana;
+ const { savedObjects, uiSettings } = services;
if (!overlays) return null;
return (
diff --git a/x-pack/legacy/plugins/graph/public/services/source_modal.tsx b/x-pack/legacy/plugins/graph/public/services/source_modal.tsx
index c985271f4dfe0..20a5b6d0786bd 100644
--- a/x-pack/legacy/plugins/graph/public/services/source_modal.tsx
+++ b/x-pack/legacy/plugins/graph/public/services/source_modal.tsx
@@ -6,6 +6,7 @@
import { CoreStart } from 'src/core/public';
import React from 'react';
+import { KibanaReactOverlays } from 'src/plugins/kibana_react/public';
import { SourceModal } from '../components/source_modal';
import { IndexPatternSavedObject } from '../types';
@@ -15,7 +16,7 @@ export function openSourceModal(
savedObjects,
uiSettings,
}: {
- overlays: CoreStart['overlays'];
+ overlays: KibanaReactOverlays;
savedObjects: CoreStart['savedObjects'];
uiSettings: CoreStart['uiSettings'];
},
diff --git a/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx b/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx
index 28bb9b2687913..c79bf52a86642 100644
--- a/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx
@@ -19,6 +19,7 @@ import {
import { i18n } from '@kbn/i18n';
import { npStart } from 'ui/new_platform';
+import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public';
const MAX_SIMPLE_MESSAGE_LENGTH = 140;
@@ -43,27 +44,29 @@ export const ToastNotificationText: FC<{ text: any }> = ({ text }) => {
const openModal = () => {
const modal = npStart.core.overlays.openModal(
- modal.close()}>
-
-
- {i18n.translate('xpack.transform.toastText.modalTitle', {
- defaultMessage: 'Error details',
- })}
-
-
-
-
- {formattedText}
-
-
-
- modal.close()}>
- {i18n.translate('xpack.transform.toastText.closeModalButtonText', {
- defaultMessage: 'Close',
- })}
-
-
-
+ toMountPoint(
+ modal.close()}>
+
+
+ {i18n.translate('xpack.transform.toastText.modalTitle', {
+ defaultMessage: 'Error details',
+ })}
+
+
+
+
+ {formattedText}
+
+
+
+ modal.close()}>
+ {i18n.translate('xpack.transform.toastText.closeModalButtonText', {
+ defaultMessage: 'Close',
+ })}
+
+
+
+ )
);
};
diff --git a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts
index bbdcf99495288..55e913e0f31da 100644
--- a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts
+++ b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_action.test.ts
@@ -14,7 +14,6 @@ import { EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import { TimeRangeEmbeddable, TimeRangeContainer, TIME_RANGE_EMBEDDABLE } from './test_helpers';
import { TimeRangeEmbeddableFactory } from './test_helpers/time_range_embeddable_factory';
import { CustomTimeRangeAction } from './custom_time_range_action';
-import { coreMock } from '../../../../src/core/public/mocks';
/* eslint-disable */
import {
HelloWorldEmbeddableFactory,
@@ -29,6 +28,12 @@ import { ReactElement } from 'react';
jest.mock('ui/new_platform');
+const createOpenModalMock = () => {
+ const mock = jest.fn();
+ mock.mockReturnValue({ close: jest.fn() });
+ return mock;
+};
+
test('Custom time range action prevents embeddable from using container time', async done => {
const embeddableFactories = new Map();
embeddableFactories.set(TIME_RANGE_EMBEDDABLE, new TimeRangeEmbeddableFactory());
@@ -66,11 +71,10 @@ test('Custom time range action prevents embeddable from using container time', a
expect(child2).toBeDefined();
expect(child2.getInput().timeRange).toEqual({ from: 'now-15m', to: 'now' });
- const start = coreMock.createStart();
- const overlayMock = start.overlays;
- overlayMock.openModal.mockClear();
+ const openModalMock = createOpenModalMock();
+
new CustomTimeRangeAction({
- openModal: start.overlays.openModal,
+ openModal: openModalMock,
commonlyUsedRanges: [],
dateFormat: 'MM YYY',
}).execute({
@@ -78,7 +82,7 @@ test('Custom time range action prevents embeddable from using container time', a
});
await nextTick();
- const openModal = overlayMock.openModal.mock.calls[0][0] as ReactElement;
+ const openModal = openModalMock.mock.calls[0][0] as ReactElement;
const wrapper = mount(openModal);
wrapper.setState({ timeRange: { from: 'now-30days', to: 'now-29days' } });
@@ -129,11 +133,9 @@ test('Removing custom time range action resets embeddable back to container time
const child1 = container.getChild('1');
const child2 = container.getChild('2');
- const start = coreMock.createStart();
- const overlayMock = start.overlays;
- overlayMock.openModal.mockClear();
+ const openModalMock = createOpenModalMock();
new CustomTimeRangeAction({
- openModal: start.overlays.openModal,
+ openModal: openModalMock,
commonlyUsedRanges: [],
dateFormat: 'MM YYY',
}).execute({
@@ -141,7 +143,7 @@ test('Removing custom time range action resets embeddable back to container time
});
await nextTick();
- const openModal = overlayMock.openModal.mock.calls[0][0] as ReactElement;
+ const openModal = openModalMock.mock.calls[0][0] as ReactElement;
const wrapper = mount(openModal);
wrapper.setState({ timeRange: { from: 'now-30days', to: 'now-29days' } });
@@ -151,7 +153,7 @@ test('Removing custom time range action resets embeddable back to container time
container.updateInput({ timeRange: { from: 'now-30m', to: 'now-1m' } });
new CustomTimeRangeAction({
- openModal: start.overlays.openModal,
+ openModal: openModalMock,
commonlyUsedRanges: [],
dateFormat: 'MM YYY',
}).execute({
@@ -159,7 +161,7 @@ test('Removing custom time range action resets embeddable back to container time
});
await nextTick();
- const openModal2 = (overlayMock.openModal as any).mock.calls[1][0];
+ const openModal2 = openModalMock.mock.calls[1][0];
const wrapper2 = mount(openModal2);
findTestSubject(wrapper2, 'removePerPanelTimeRangeButton').simulate('click');
@@ -209,11 +211,9 @@ test('Cancelling custom time range action leaves state alone', async done => {
const child1 = container.getChild('1');
const child2 = container.getChild('2');
- const start = coreMock.createStart();
- const overlayMock = start.overlays;
- overlayMock.openModal.mockClear();
+ const openModalMock = createOpenModalMock();
new CustomTimeRangeAction({
- openModal: start.overlays.openModal,
+ openModal: openModalMock,
commonlyUsedRanges: [],
dateFormat: 'MM YYY',
}).execute({
@@ -221,7 +221,7 @@ test('Cancelling custom time range action leaves state alone', async done => {
});
await nextTick();
- const openModal = overlayMock.openModal.mock.calls[0][0] as ReactElement;
+ const openModal = openModalMock.mock.calls[0][0] as ReactElement;
const wrapper = mount(openModal);
wrapper.setState({ timeRange: { from: 'now-300m', to: 'now-400m' } });
@@ -263,9 +263,9 @@ test(`badge is compatible with embeddable that inherits from parent`, async () =
const child = container.getChild('1');
- const start = coreMock.createStart();
+ const openModalMock = createOpenModalMock();
const compatible = await new CustomTimeRangeAction({
- openModal: start.overlays.openModal,
+ openModal: openModalMock,
commonlyUsedRanges: [],
dateFormat: 'MM YYY',
}).isCompatible({
@@ -333,9 +333,9 @@ test('Attempting to execute on incompatible embeddable throws an error', async (
const child = container.getChild('1');
- const start = coreMock.createStart();
+ const openModalMock = createOpenModalMock();
const action = await new CustomTimeRangeAction({
- openModal: start.overlays.openModal,
+ openModal: openModalMock,
dateFormat: 'MM YYYY',
commonlyUsedRanges: [],
});
diff --git a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts
index c6046c02f0833..d2b9fa9ac1655 100644
--- a/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts
+++ b/x-pack/plugins/advanced_ui_actions/public/custom_time_range_badge.test.ts
@@ -13,7 +13,6 @@ import { EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import { TimeRangeEmbeddable, TimeRangeContainer, TIME_RANGE_EMBEDDABLE } from './test_helpers';
import { TimeRangeEmbeddableFactory } from './test_helpers/time_range_embeddable_factory';
import { CustomTimeRangeBadge } from './custom_time_range_badge';
-import { coreMock } from '../../../../src/core/public/mocks';
import { ReactElement } from 'react';
import { nextTick } from 'test_utils/enzyme_helpers';
@@ -50,11 +49,11 @@ test('Removing custom time range from badge resets embeddable back to container
const child1 = container.getChild('1');
const child2 = container.getChild('2');
- const start = coreMock.createStart();
- const overlayMock = start.overlays;
- overlayMock.openModal.mockClear();
+ const openModalMock = jest.fn();
+ openModalMock.mockReturnValue({ close: jest.fn() });
+
new CustomTimeRangeBadge({
- openModal: start.overlays.openModal,
+ openModal: openModalMock,
dateFormat: 'MM YYYY',
commonlyUsedRanges: [],
}).execute({
@@ -62,7 +61,7 @@ test('Removing custom time range from badge resets embeddable back to container
});
await nextTick();
- const openModal = overlayMock.openModal.mock.calls[0][0] as ReactElement;
+ const openModal = openModalMock.mock.calls[0][0] as ReactElement;
const wrapper = mount(openModal);
findTestSubject(wrapper, 'removePerPanelTimeRangeButton').simulate('click');
@@ -102,9 +101,9 @@ test(`badge is not compatible with embeddable that inherits from parent`, async
const child = container.getChild('1');
- const start = coreMock.createStart();
+ const openModalMock = jest.fn();
const compatible = await new CustomTimeRangeBadge({
- openModal: start.overlays.openModal,
+ openModal: openModalMock,
dateFormat: 'MM YYYY',
commonlyUsedRanges: [],
}).isCompatible({
@@ -137,9 +136,9 @@ test(`badge is compatible with embeddable that has custom time range`, async ()
const child = container.getChild('1');
- const start = coreMock.createStart();
+ const openModalMock = jest.fn();
const compatible = await new CustomTimeRangeBadge({
- openModal: start.overlays.openModal,
+ openModal: openModalMock,
dateFormat: 'MM YYYY',
commonlyUsedRanges: [],
}).isCompatible({
@@ -171,9 +170,9 @@ test('Attempting to execute on incompatible embeddable throws an error', async (
const child = container.getChild('1');
- const start = coreMock.createStart();
+ const openModalMock = jest.fn();
const badge = await new CustomTimeRangeBadge({
- openModal: start.overlays.openModal,
+ openModal: openModalMock,
dateFormat: 'MM YYYY',
commonlyUsedRanges: [],
});
diff --git a/x-pack/plugins/advanced_ui_actions/public/plugin.ts b/x-pack/plugins/advanced_ui_actions/public/plugin.ts
index fc106cc8ec26b..e2d1892b1355e 100644
--- a/x-pack/plugins/advanced_ui_actions/public/plugin.ts
+++ b/x-pack/plugins/advanced_ui_actions/public/plugin.ts
@@ -10,6 +10,7 @@ import {
CoreStart,
Plugin,
} from '../../../../src/core/public';
+import { createReactOverlays } from '../../../../src/plugins/kibana_react/public';
import { IUiActionsStart, IUiActionsSetup } from '../../../../src/plugins/ui_actions/public';
import {
CONTEXT_MENU_TRIGGER,
@@ -44,8 +45,9 @@ export class AdvancedUiActionsPublicPlugin
public start(core: CoreStart, { uiActions }: StartDependencies): Start {
const dateFormat = core.uiSettings.get('dateFormat') as string;
const commonlyUsedRanges = core.uiSettings.get('timepicker:quickRanges') as CommonlyUsedRange[];
+ const { openModal } = createReactOverlays(core);
const timeRangeAction = new CustomTimeRangeAction({
- openModal: core.overlays.openModal,
+ openModal,
dateFormat,
commonlyUsedRanges,
});
@@ -53,7 +55,7 @@ export class AdvancedUiActionsPublicPlugin
uiActions.attachAction(CONTEXT_MENU_TRIGGER, timeRangeAction.id);
const timeRangeBadge = new CustomTimeRangeBadge({
- openModal: core.overlays.openModal,
+ openModal,
dateFormat,
commonlyUsedRanges,
});
diff --git a/x-pack/plugins/advanced_ui_actions/public/types.ts b/x-pack/plugins/advanced_ui_actions/public/types.ts
index bbd7c5528276f..313b09535b196 100644
--- a/x-pack/plugins/advanced_ui_actions/public/types.ts
+++ b/x-pack/plugins/advanced_ui_actions/public/types.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { OverlayRef } from '../../../../src/core/public';
+import { KibanaReactOverlays } from '../../../../src/plugins/kibana_react/public';
export interface CommonlyUsedRange {
from: string;
@@ -12,10 +12,4 @@ export interface CommonlyUsedRange {
display: string;
}
-export type OpenModal = (
- modalChildren: React.ReactNode,
- modalProps?: {
- closeButtonAriaLabel?: string;
- 'data-test-subj'?: string;
- }
-) => OverlayRef;
+export type OpenModal = KibanaReactOverlays['openModal'];