mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* move/rename overlay mount and unmount types from banner to parent module Signed-off-by: pgayvallet <pierre.gayvallet@elastic.co> * migrate openModal / modalService Signed-off-by: pgayvallet <pierre.gayvallet@elastic.co> fix I18nProvider import path Signed-off-by: pgayvallet <pierre.gayvallet@elastic.co> * updates core doc Signed-off-by: pgayvallet <pierre.gayvallet@elastic.co> update doc bis Signed-off-by: pgayvallet <pierre.gayvallet@elastic.co> * migrate openFlyout / flyout service Signed-off-by: pgayvallet <pierre.gayvallet@elastic.co> * remove CoreStart export from kibana-react Signed-off-by: pgayvallet <pierre.gayvallet@elastic.co> * adapt some calls to new signature Signed-off-by: pgayvallet <pierre.gayvallet@elastic.co> * adapt new calls Signed-off-by: pgayvallet <pierre.gayvallet@elastic.co> * adapt js call Signed-off-by: pgayvallet <pierre.gayvallet@elastic.co> * add flex layout on mountWrapper component to avoid losing scroll on overlays Signed-off-by: pgayvallet <pierre.gayvallet@elastic.co> * create proper flyout/modal services * update generated doc * update snapshot on data/query_bar * use ReactNode instead of ReactElement * rename mountForComponent to reactMount * change reactMount usages to toMountPoint * remove duplicate MountPoint type in overlays * remove duplicate mount utilities from overlays * allow to specify custom class name to MountWrapper * Allow to specialize MountPoint on HTMLElement subtypes * updates generated doc * undeprecates openFlyout/openModal & remove direct subservice access from overlayService * adapt toast api to get i18n context from service * use MountPoint instead of inline definition
This commit is contained in:
parent
27102c2404
commit
e5eddc8abc
63 changed files with 1031 additions and 675 deletions
|
@ -15,11 +15,6 @@ export interface ChromeNavControl
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [mount](./kibana-plugin-public.chromenavcontrol.mount.md) | <code>MountPoint</code> | |
|
||||
| [order](./kibana-plugin-public.chromenavcontrol.order.md) | <code>number</code> | |
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [mount(targetDomElement)](./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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
mount(targetDomElement: HTMLElement): () => void;
|
||||
mount: MountPoint;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| targetDomElement | <code>HTMLElement</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`() => void`
|
||||
|
||||
|
|
|
@ -9,5 +9,5 @@ A function that should mount DOM content inside the provided container element a
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type MountPoint = (element: HTMLElement) => UnmountCallback;
|
||||
export declare type MountPoint<T extends HTMLElement = HTMLElement> = (element: T) => UnmountCallback;
|
||||
```
|
||||
|
|
|
@ -16,6 +16,6 @@ export interface OverlayStart
|
|||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [banners](./kibana-plugin-public.overlaystart.banners.md) | <code>OverlayBannersStart</code> | [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) |
|
||||
| [openFlyout](./kibana-plugin-public.overlaystart.openflyout.md) | <code>(flyoutChildren: React.ReactNode, flyoutProps?: {</code><br/><code> closeButtonAriaLabel?: string;</code><br/><code> 'data-test-subj'?: string;</code><br/><code> }) => OverlayRef</code> | |
|
||||
| [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | <code>(modalChildren: React.ReactNode, modalProps?: {</code><br/><code> className?: string;</code><br/><code> closeButtonAriaLabel?: string;</code><br/><code> 'data-test-subj'?: string;</code><br/><code> }) => OverlayRef</code> | |
|
||||
| [openFlyout](./kibana-plugin-public.overlaystart.openflyout.md) | <code>OverlayFlyoutStart['open']</code> | |
|
||||
| [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | <code>OverlayModalStart['open']</code> | |
|
||||
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
|
||||
## OverlayStart.openFlyout property
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
openFlyout: (flyoutChildren: React.ReactNode, flyoutProps?: {
|
||||
closeButtonAriaLabel?: string;
|
||||
'data-test-subj'?: string;
|
||||
}) => OverlayRef;
|
||||
openFlyout: OverlayFlyoutStart['open'];
|
||||
```
|
||||
|
|
|
@ -4,12 +4,9 @@
|
|||
|
||||
## OverlayStart.openModal property
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
openModal: (modalChildren: React.ReactNode, modalProps?: {
|
||||
className?: string;
|
||||
closeButtonAriaLabel?: string;
|
||||
'data-test-subj'?: string;
|
||||
}) => OverlayRef;
|
||||
openModal: OverlayModalStart['open'];
|
||||
```
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,9 +18,10 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MountPoint } from '../../../types';
|
||||
|
||||
interface Props {
|
||||
extension?: (el: HTMLDivElement) => () => void;
|
||||
extension?: MountPoint<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export class HeaderExtension extends React.Component<Props> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 }) => <React.Fragment>{children}</React.Fragment>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<ErrorToastProps, 'error' | 'title' | 'openModal'>) {
|
||||
i18nContext,
|
||||
}: Pick<ErrorToastProps, 'error' | 'title' | 'openModal' | 'i18nContext'>) {
|
||||
const I18nContext = i18nContext();
|
||||
const modal = openModal(
|
||||
<React.Fragment>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>{title}</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<EuiCallOut size="s" color="danger" iconType="alert" title={error.message} />
|
||||
{error.stack && (
|
||||
<React.Fragment>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCodeBlock isCopyable={true} paddingSize="s">
|
||||
{error.stack}
|
||||
</EuiCodeBlock>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter>
|
||||
<EuiButton onClick={() => modal.close()} fill>
|
||||
<FormattedMessage id="core.notifications.errorToast.closeModal" defaultMessage="Close" />
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</React.Fragment>
|
||||
mount(
|
||||
<React.Fragment>
|
||||
<I18nContext>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>{title}</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<EuiCallOut size="s" color="danger" iconType="alert" title={error.message} />
|
||||
{error.stack && (
|
||||
<React.Fragment>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCodeBlock isCopyable={true} paddingSize="s">
|
||||
{error.stack}
|
||||
</EuiCodeBlock>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter>
|
||||
<EuiButton onClick={() => modal.close()} fill>
|
||||
<FormattedMessage
|
||||
id="core.notifications.errorToast.closeModal"
|
||||
defaultMessage="Close"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</I18nContext>
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function ErrorToast({ title, error, toastMessage, openModal }: ErrorToastProps) {
|
||||
export function ErrorToast({
|
||||
title,
|
||||
error,
|
||||
toastMessage,
|
||||
openModal,
|
||||
i18nContext,
|
||||
}: ErrorToastProps) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<p data-test-subj="errorToastMessage">{toastMessage}</p>
|
||||
|
@ -84,7 +102,7 @@ export function ErrorToast({ title, error, toastMessage, openModal }: ErrorToast
|
|||
<EuiButton
|
||||
size="s"
|
||||
color="danger"
|
||||
onClick={() => showErrorDialog({ title, error, openModal })}
|
||||
onClick={() => showErrorDialog({ title, error, openModal, i18nContext })}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="core.toasts.errorToast.seeFullError"
|
||||
|
@ -95,3 +113,8 @@ export function ErrorToast({ title, error, toastMessage, openModal }: ErrorToast
|
|||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const mount = (component: React.ReactElement) => (container: HTMLElement) => {
|
||||
ReactDOM.render(component, container);
|
||||
return () => ReactDOM.unmountComponentAtNode(container);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 [
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`FlyoutService openFlyout() renders a flyout to the DOM 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<mockConstructor>
|
||||
<EuiFlyout
|
||||
closeButtonAriaLabel="Closes this dialog"
|
||||
hideCloseButton={false}
|
||||
maxWidth={false}
|
||||
onClose={[Function]}
|
||||
ownFocus={false}
|
||||
size="m"
|
||||
>
|
||||
<span>
|
||||
Flyout content
|
||||
</span>
|
||||
</EuiFlyout>
|
||||
</mockConstructor>,
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<mockConstructor>
|
||||
<EuiFlyout
|
||||
closeButtonAriaLabel="Closes this dialog"
|
||||
hideCloseButton={false}
|
||||
maxWidth={false}
|
||||
onClose={[Function]}
|
||||
ownFocus={false}
|
||||
size="m"
|
||||
>
|
||||
<span>
|
||||
Flyout content 1
|
||||
</span>
|
||||
</EuiFlyout>
|
||||
</mockConstructor>,
|
||||
<div />,
|
||||
],
|
||||
Array [
|
||||
<mockConstructor>
|
||||
<EuiFlyout
|
||||
closeButtonAriaLabel="Closes this dialog"
|
||||
hideCloseButton={false}
|
||||
maxWidth={false}
|
||||
onClose={[Function]}
|
||||
ownFocus={false}
|
||||
size="m"
|
||||
>
|
||||
<span>
|
||||
Flyout content 2
|
||||
</span>
|
||||
</EuiFlyout>
|
||||
</mockConstructor>,
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
|
@ -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 [
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`ModalService openModal() renders a modal to the DOM 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<EuiOverlayMask>
|
||||
<mockConstructor>
|
||||
<EuiModal
|
||||
maxWidth={true}
|
||||
onClose={[Function]}
|
||||
>
|
||||
<span>
|
||||
Modal content
|
||||
</span>
|
||||
</EuiModal>
|
||||
</mockConstructor>
|
||||
</EuiOverlayMask>,
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<EuiOverlayMask>
|
||||
<mockConstructor>
|
||||
<EuiModal
|
||||
maxWidth={true}
|
||||
onClose={[Function]}
|
||||
>
|
||||
<span>
|
||||
Modal content 1
|
||||
</span>
|
||||
</EuiModal>
|
||||
</mockConstructor>
|
||||
</EuiOverlayMask>,
|
||||
<div />,
|
||||
],
|
||||
Array [
|
||||
<EuiOverlayMask>
|
||||
<mockConstructor>
|
||||
<EuiModal
|
||||
maxWidth={true}
|
||||
onClose={[Function]}
|
||||
>
|
||||
<span>
|
||||
Flyout content 2
|
||||
</span>
|
||||
</EuiModal>
|
||||
</mockConstructor>
|
||||
</EuiOverlayMask>,
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
|
@ -1 +1,2 @@
|
|||
@import './banners/index';
|
||||
@import './mount_wrapper';
|
||||
|
|
5
src/core/public/overlays/_mount_wrapper.scss
Normal file
5
src/core/public/overlays/_mount_wrapper.scss
Normal file
|
@ -0,0 +1,5 @@
|
|||
.kbnOverlayMountWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
|
@ -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;
|
||||
},
|
||||
|
|
77
src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
generated
Normal file
77
src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
generated
Normal file
|
@ -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 [
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`FlyoutService openFlyout() renders a flyout to the DOM 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<mockConstructor>
|
||||
<EuiFlyout
|
||||
closeButtonAriaLabel="Closes this dialog"
|
||||
hideCloseButton={false}
|
||||
maxWidth={false}
|
||||
onClose={[Function]}
|
||||
ownFocus={false}
|
||||
size="m"
|
||||
>
|
||||
<MountWrapper
|
||||
className="kbnOverlayMountWrapper"
|
||||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</mockConstructor>,
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"<span><div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\"><div role=\\"dialog\\" class=\\"euiFlyout euiFlyout--medium\\" tabindex=\\"0\\"><button class=\\"euiButtonIcon euiButtonIcon--text euiFlyout__closeButton\\" type=\\"button\\" aria-label=\\"Closes this dialog\\" data-test-subj=\\"euiFlyoutCloseButton\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"></svg></button><div class=\\"kbnOverlayMountWrapper\\"><span>Flyout content</span></div></div></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div></div></span>"`;
|
||||
|
||||
exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<mockConstructor>
|
||||
<EuiFlyout
|
||||
closeButtonAriaLabel="Closes this dialog"
|
||||
hideCloseButton={false}
|
||||
maxWidth={false}
|
||||
onClose={[Function]}
|
||||
ownFocus={false}
|
||||
size="m"
|
||||
>
|
||||
<MountWrapper
|
||||
className="kbnOverlayMountWrapper"
|
||||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</mockConstructor>,
|
||||
<div />,
|
||||
],
|
||||
Array [
|
||||
<mockConstructor>
|
||||
<EuiFlyout
|
||||
closeButtonAriaLabel="Closes this dialog"
|
||||
hideCloseButton={false}
|
||||
maxWidth={false}
|
||||
onClose={[Function]}
|
||||
ownFocus={false}
|
||||
size="m"
|
||||
>
|
||||
<MountWrapper
|
||||
className="kbnOverlayMountWrapper"
|
||||
mount={[Function]}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
</mockConstructor>,
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"<span><div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\"><div role=\\"dialog\\" class=\\"euiFlyout euiFlyout--medium\\" tabindex=\\"0\\"><button class=\\"euiButtonIcon euiButtonIcon--text euiFlyout__closeButton\\" type=\\"button\\" aria-label=\\"Closes this dialog\\" data-test-subj=\\"euiFlyoutCloseButton\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"></svg></button><div class=\\"kbnOverlayMountWrapper\\"><span>Flyout content 2</span></div></div></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div></div></span>"`;
|
43
src/core/public/overlays/flyout/flyout_service.mock.ts
Normal file
43
src/core/public/overlays/flyout/flyout_service.mock.ts
Normal file
|
@ -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<OverlayFlyoutStart> = {
|
||||
open: jest.fn().mockReturnValue({
|
||||
close: jest.fn(),
|
||||
onClose: Promise.resolve(),
|
||||
}),
|
||||
};
|
||||
return startContract;
|
||||
};
|
||||
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<PublicMethodsOf<FlyoutService>> = {
|
||||
start: jest.fn(),
|
||||
};
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const overlayFlyoutServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -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, <span>Flyout content</span>);
|
||||
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, <span>Flyout content 1</span>);
|
||||
ref1 = flyouts.open(mountText('Flyout content'));
|
||||
});
|
||||
it('replaces the current flyout with a new one', () => {
|
||||
flyoutService.openFlyout(i18nMock, <span>Flyout content 2</span>);
|
||||
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, <span>Flyout content 2</span>);
|
||||
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, <span>Flyout content</span>);
|
||||
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, <span>Flyout content</span>);
|
||||
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, <span>Flyout content 1</span>);
|
||||
const ref2 = flyoutService.openFlyout(i18nMock, <span>Flyout content 2</span>);
|
||||
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();
|
|
@ -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(
|
||||
<i18n.Context>
|
||||
<EuiFlyout {...flyoutProps} onClose={() => flyout.close()}>
|
||||
{flyoutChildren}
|
||||
</EuiFlyout>
|
||||
</i18n.Context>,
|
||||
this.targetDomElement
|
||||
);
|
||||
render(
|
||||
<i18n.Context>
|
||||
<EuiFlyout {...options} onClose={() => flyout.close()}>
|
||||
<MountWrapper mount={mount} className="kbnOverlayMountWrapper" />
|
||||
</EuiFlyout>
|
||||
</i18n.Context>,
|
||||
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;
|
||||
}
|
||||
}
|
20
src/core/public/overlays/flyout/index.ts
Normal file
20
src/core/public/overlays/flyout/index.ts
Normal file
|
@ -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';
|
|
@ -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';
|
||||
|
|
|
@ -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<void>;
|
||||
|
||||
private closeSubject = new Subject<void>();
|
||||
|
||||
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<void> {
|
||||
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(
|
||||
<EuiOverlayMask>
|
||||
<i18n.Context>
|
||||
<EuiModal {...modalProps} onClose={() => modal.close()}>
|
||||
{modalChildren}
|
||||
</EuiModal>
|
||||
</i18n.Context>
|
||||
</EuiOverlayMask>,
|
||||
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;
|
||||
}
|
||||
}
|
69
src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
generated
Normal file
69
src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
generated
Normal file
|
@ -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 [
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`ModalService openModal() renders a modal to the DOM 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<EuiOverlayMask>
|
||||
<mockConstructor>
|
||||
<EuiModal
|
||||
maxWidth={true}
|
||||
onClose={[Function]}
|
||||
>
|
||||
<MountWrapper
|
||||
className="kbnOverlayMountWrapper"
|
||||
mount={[Function]}
|
||||
/>
|
||||
</EuiModal>
|
||||
</mockConstructor>
|
||||
</EuiOverlayMask>,
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`ModalService openModal() renders a modal to the DOM 2`] = `"<div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\"><div class=\\"euiModal euiModal--maxWidth-default\\" tabindex=\\"0\\"><button class=\\"euiButtonIcon euiButtonIcon--text euiModal__closeIcon\\" type=\\"button\\" aria-label=\\"Closes this modal window\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"></svg></button><div class=\\"euiModal__flex\\"><div class=\\"kbnOverlayMountWrapper\\"><span>Modal content</span></div></div></div></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div>"`;
|
||||
|
||||
exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
<EuiOverlayMask>
|
||||
<mockConstructor>
|
||||
<EuiModal
|
||||
maxWidth={true}
|
||||
onClose={[Function]}
|
||||
>
|
||||
<MountWrapper
|
||||
className="kbnOverlayMountWrapper"
|
||||
mount={[Function]}
|
||||
/>
|
||||
</EuiModal>
|
||||
</mockConstructor>
|
||||
</EuiOverlayMask>,
|
||||
<div />,
|
||||
],
|
||||
Array [
|
||||
<EuiOverlayMask>
|
||||
<mockConstructor>
|
||||
<EuiModal
|
||||
maxWidth={true}
|
||||
onClose={[Function]}
|
||||
>
|
||||
<MountWrapper
|
||||
className="kbnOverlayMountWrapper"
|
||||
mount={[Function]}
|
||||
/>
|
||||
</EuiModal>
|
||||
</mockConstructor>
|
||||
</EuiOverlayMask>,
|
||||
<div />,
|
||||
],
|
||||
]
|
||||
`;
|
20
src/core/public/overlays/modal/index.ts
Normal file
20
src/core/public/overlays/modal/index.ts
Normal file
|
@ -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';
|
43
src/core/public/overlays/modal/modal_service.mock.ts
Normal file
43
src/core/public/overlays/modal/modal_service.mock.ts
Normal file
|
@ -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<OverlayModalStart> = {
|
||||
open: jest.fn().mockReturnValue({
|
||||
close: jest.fn(),
|
||||
onClose: Promise.resolve(),
|
||||
}),
|
||||
};
|
||||
return startContract;
|
||||
};
|
||||
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<PublicMethodsOf<ModalService>> = {
|
||||
start: jest.fn(),
|
||||
};
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const overlayModalServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -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, <span>Modal content</span>);
|
||||
expect(mockReactDomRender.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
describe('with a currently active modal', () => {
|
||||
let target: HTMLElement;
|
||||
let modalService: ModalService;
|
||||
let ref1: ModalRef;
|
||||
beforeEach(() => {
|
||||
target = document.createElement('div');
|
||||
modalService = new ModalService(target);
|
||||
ref1 = modalService.openModal(i18nMock, <span>Modal content 1</span>);
|
||||
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 ref1: OverlayRef;
|
||||
|
||||
beforeEach(() => {
|
||||
ref1 = modals.open(mountReactNode(<span>Modal content 1</span>));
|
||||
});
|
||||
|
||||
it('replaces the current modal with a new one', () => {
|
||||
modalService.openModal(i18nMock, <span>Flyout content 2</span>);
|
||||
modals.open(mountReactNode(<span>Flyout content 2</span>));
|
||||
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, <span>Flyout content 2</span>);
|
||||
modals.open(mountReactNode(<span>Flyout content 2</span>));
|
||||
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, <span>Flyout content</span>);
|
||||
const ref = modals.open(mountReactNode(<span>Flyout content</span>));
|
||||
|
||||
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, <span>Flyout content</span>);
|
||||
const ref = modals.open(mountReactNode(<span>Flyout content</span>));
|
||||
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, <span>Modal content 1</span>);
|
||||
const ref2 = modalService.openModal(i18nMock, <span>Modal content 2</span>);
|
||||
const ref1 = modals.open(mountReactNode(<span>Modal content 1</span>));
|
||||
const ref2 = modals.open(mountReactNode(<span>Modal content 2</span>));
|
||||
const onCloseComplete = jest.fn();
|
||||
ref2.onClose.then(onCloseComplete);
|
||||
mockReactDomUnmount.mockClear();
|
148
src/core/public/overlays/modal/modal_service.tsx
Normal file
148
src/core/public/overlays/modal/modal_service.tsx
Normal file
|
@ -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<void>;
|
||||
|
||||
private closeSubject = new Subject<void>();
|
||||
|
||||
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<void> {
|
||||
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(
|
||||
<EuiOverlayMask>
|
||||
<i18n.Context>
|
||||
<EuiModal {...options} onClose={() => modal.close()}>
|
||||
<MountWrapper mount={mount} className="kbnOverlayMountWrapper" />
|
||||
</EuiModal>
|
||||
</i18n.Context>
|
||||
</EuiOverlayMask>,
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}));
|
|
@ -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<OverlayStart> = {
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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<void>;
|
||||
|
||||
/**
|
||||
* 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<void>;
|
||||
}
|
||||
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'];
|
||||
}
|
||||
|
|
39
src/core/public/overlays/types.ts
Normal file
39
src/core/public/overlays/types.ts
Normal file
|
@ -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<void>;
|
||||
|
||||
/**
|
||||
* 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<void>;
|
||||
}
|
|
@ -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<T extends HTMLElement = HTMLElement> = (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<Toast[]>;
|
||||
// @internal (undocumented)
|
||||
registerOverlays(overlays: OverlayStart): void;
|
||||
remove(toastOrId: Toast | string): void;
|
||||
// @internal (undocumented)
|
||||
start({ overlays, i18n }: {
|
||||
overlays: OverlayStart;
|
||||
i18n: I18nStart;
|
||||
}): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
export type MountPoint = (element: HTMLElement) => UnmountCallback;
|
||||
export type MountPoint<T extends HTMLElement = HTMLElement> = (element: T) => UnmountCallback;
|
||||
|
||||
/**
|
||||
* A function that will unmount the element previously mounted by
|
||||
|
|
79
src/core/public/utils/mount.test.tsx
Normal file
79
src/core/public/utils/mount.test.tsx
Normal file
|
@ -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 = <MountWrapper mount={mountPoint} />;
|
||||
const container = mount(wrapper);
|
||||
expect(container.html()).toMatchInlineSnapshot(
|
||||
`"<div class=\\"kbnMountWrapper\\"><p class=\\"bar\\">hello</p></div>"`
|
||||
);
|
||||
});
|
||||
|
||||
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 = <MountWrapper mount={mountPoint} />;
|
||||
const container = mount(wrapper);
|
||||
expect(container.html()).toMatchInlineSnapshot(
|
||||
`"<div class=\\"kbnMountWrapper\\"><p>initial</p></div>"`
|
||||
);
|
||||
|
||||
el.textContent = 'changed';
|
||||
container.update();
|
||||
expect(container.html()).toMatchInlineSnapshot(
|
||||
`"<div class=\\"kbnMountWrapper\\"><p>changed</p></div>"`
|
||||
);
|
||||
});
|
||||
|
||||
it('can render a detached react component', () => {
|
||||
const mountPoint = mountReactNode(<span>detached</span>);
|
||||
const wrapper = <MountWrapper mount={mountPoint} />;
|
||||
const container = mount(wrapper);
|
||||
expect(container.html()).toMatchInlineSnapshot(
|
||||
`"<div class=\\"kbnMountWrapper\\"><span>detached</span></div>"`
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts a className prop to override default className', () => {
|
||||
const mountPoint = mountReactNode(<span>detached</span>);
|
||||
const wrapper = <MountWrapper mount={mountPoint} className="customClass" />;
|
||||
const container = mount(wrapper);
|
||||
expect(container.html()).toMatchInlineSnapshot(
|
||||
`"<div class=\\"customClass\\"><span>detached</span></div>"`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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 <div className="kbnMountWrapper" ref={element} />;
|
||||
return <div className={className} ref={element} />;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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(<I18nProvider>{component}</I18nProvider>, element);
|
||||
export const mountReactNode = (node: React.ReactNode): MountPoint => (element: HTMLElement) => {
|
||||
render(<I18nProvider>{node}</I18nProvider>, element);
|
||||
return () => unmountComponentAtNode(element);
|
||||
};
|
||||
|
|
|
@ -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<esFilters.Filter[]> = 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',
|
||||
|
|
|
@ -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(
|
||||
<ShardFailureModal
|
||||
request={request}
|
||||
response={response}
|
||||
title={title}
|
||||
onClose={() => modal.close()}
|
||||
/>,
|
||||
toMountPoint(
|
||||
<ShardFailureModal
|
||||
request={request}
|
||||
response={response}
|
||||
title={title}
|
||||
onClose={() => modal.close()}
|
||||
/>
|
||||
),
|
||||
{
|
||||
className: 'shardFailureModal',
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
<ReplacePanelFlyout
|
||||
container={embeddable}
|
||||
onClose={() => {
|
||||
if (flyoutSession) {
|
||||
flyoutSession.close();
|
||||
}
|
||||
}}
|
||||
panelToRemove={panelToRemove}
|
||||
savedObjectsFinder={savedObjectFinder}
|
||||
notifications={notifications}
|
||||
getEmbeddableFactories={getEmbeddableFactories}
|
||||
/>,
|
||||
toMountPoint(
|
||||
<ReplacePanelFlyout
|
||||
container={embeddable}
|
||||
onClose={() => {
|
||||
if (flyoutSession) {
|
||||
flyoutSession.close();
|
||||
}
|
||||
}}
|
||||
panelToRemove={panelToRemove}
|
||||
savedObjectsFinder={savedObjectFinder}
|
||||
notifications={notifications}
|
||||
getEmbeddableFactories={getEmbeddableFactories}
|
||||
/>
|
||||
),
|
||||
{
|
||||
'data-test-subj': 'replacePanelFlyout',
|
||||
}
|
||||
|
|
|
@ -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<Props, State> {
|
|||
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(
|
||||
<CustomizePanelModal
|
||||
embeddable={context.embeddable}
|
||||
updateTitle={title => {
|
||||
session.close();
|
||||
resolve({ title });
|
||||
}}
|
||||
/>,
|
||||
toMountPoint(
|
||||
<CustomizePanelModal
|
||||
embeddable={context.embeddable}
|
||||
updateTitle={title => {
|
||||
session.close();
|
||||
resolve({ title });
|
||||
}}
|
||||
/>
|
||||
),
|
||||
{
|
||||
'data-test-subj': 'customizePanel',
|
||||
}
|
||||
|
|
|
@ -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<ActionContext> {
|
|||
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<any>
|
||||
) {}
|
||||
|
|
|
@ -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<any>;
|
||||
}) {
|
||||
|
@ -40,18 +40,20 @@ export async function openAddPanelFlyout(options: {
|
|||
SavedObjectFinder,
|
||||
} = options;
|
||||
const flyoutSession = overlays.openFlyout(
|
||||
<AddPanelFlyout
|
||||
container={embeddable}
|
||||
onClose={() => {
|
||||
if (flyoutSession) {
|
||||
flyoutSession.close();
|
||||
}
|
||||
}}
|
||||
getFactory={getFactory}
|
||||
getAllFactories={getAllFactories}
|
||||
notifications={notifications}
|
||||
SavedObjectFinder={SavedObjectFinder}
|
||||
/>,
|
||||
toMountPoint(
|
||||
<AddPanelFlyout
|
||||
container={embeddable}
|
||||
onClose={() => {
|
||||
if (flyoutSession) {
|
||||
flyoutSession.close();
|
||||
}
|
||||
}}
|
||||
getFactory={getFactory}
|
||||
getAllFactories={getAllFactories}
|
||||
notifications={notifications}
|
||||
SavedObjectFinder={SavedObjectFinder}
|
||||
/>
|
||||
),
|
||||
{
|
||||
'data-test-subj': 'addPanelFlyout',
|
||||
}
|
||||
|
|
|
@ -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(<EuiFlyoutBody>{content}</EuiFlyoutBody>);
|
||||
overlays.openFlyout(toMountPoint(<EuiFlyoutBody>{content}</EuiFlyoutBody>));
|
||||
};
|
||||
|
||||
return createAction<ActionContext>({
|
||||
|
@ -51,13 +52,15 @@ export function createSendMessageAction(overlays: CoreStart['overlays']) {
|
|||
}
|
||||
|
||||
const modal = overlays.openModal(
|
||||
<GetMessageModal
|
||||
onCancel={() => modal.close()}
|
||||
onDone={message => {
|
||||
modal.close();
|
||||
sendMessage(context, message);
|
||||
}}
|
||||
/>
|
||||
toMountPoint(
|
||||
<GetMessageModal
|
||||
onCancel={() => modal.close()}
|
||||
onDone={message => {
|
||||
modal.close();
|
||||
sendMessage(context, message);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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<ContactCardE
|
|||
public getExplicitInput(): Promise<Partial<ContactCardEmbeddableInput>> {
|
||||
return new Promise(resolve => {
|
||||
const modalSession = this.overlays.openModal(
|
||||
<ContactCardInitializer
|
||||
onCancel={() => {
|
||||
modalSession.close();
|
||||
resolve(undefined);
|
||||
}}
|
||||
onCreate={(input: { firstName: string; lastName?: string }) => {
|
||||
modalSession.close();
|
||||
resolve(input);
|
||||
}}
|
||||
/>,
|
||||
toMountPoint(
|
||||
<ContactCardInitializer
|
||||
onCancel={() => {
|
||||
modalSession.close();
|
||||
resolve(undefined);
|
||||
}}
|
||||
onCreate={(input: { firstName: string; lastName?: string }) => {
|
||||
modalSession.close();
|
||||
resolve(input);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
{
|
||||
'data-test-subj': 'createContactCardEmbeddable',
|
||||
}
|
||||
|
|
|
@ -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<Setup, Start> {
|
|||
}
|
||||
|
||||
return core.overlays.openFlyout(
|
||||
<InspectorPanel views={views} adapters={adapters} title={options.title} />,
|
||||
toMountPoint(<InspectorPanel views={views} adapters={adapters} title={options.title} />),
|
||||
{
|
||||
'data-test-subj': 'inspectorPanel',
|
||||
closeButtonAriaLabel: closeButtonLabel,
|
||||
|
|
|
@ -48,13 +48,11 @@ test('can open flyout with React element', () => {
|
|||
overlays.openFlyout(<div>foo</div>);
|
||||
|
||||
expect(coreOverlays.openFlyout).toHaveBeenCalledTimes(1);
|
||||
expect(coreOverlays.openFlyout.mock.calls[0][0]).toMatchInlineSnapshot(`
|
||||
<React.Fragment>
|
||||
<div>
|
||||
foo
|
||||
</div>
|
||||
</React.Fragment>
|
||||
`);
|
||||
|
||||
const container = document.createElement('div');
|
||||
const mount = coreOverlays.openFlyout.mock.calls[0][0];
|
||||
mount(container);
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(`"<div>foo</div>"`);
|
||||
});
|
||||
|
||||
test('can open modal with React element', () => {
|
||||
|
@ -68,13 +66,10 @@ test('can open modal with React element', () => {
|
|||
overlays.openModal(<div>bar</div>);
|
||||
|
||||
expect(coreOverlays.openModal).toHaveBeenCalledTimes(1);
|
||||
expect(coreOverlays.openModal.mock.calls[0][0]).toMatchInlineSnapshot(`
|
||||
<React.Fragment>
|
||||
<div>
|
||||
bar
|
||||
</div>
|
||||
</React.Fragment>
|
||||
`);
|
||||
const container = document.createElement('div');
|
||||
const mount = coreOverlays.openModal.mock.calls[0][0];
|
||||
mount(container);
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(`"<div>bar</div>"`);
|
||||
});
|
||||
|
||||
test('passes through flyout options when opening flyout', () => {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -27,6 +27,6 @@ export interface KibanaReactOverlays {
|
|||
) => ReturnType<CoreStart['overlays']['openFlyout']>;
|
||||
openModal: (
|
||||
node: React.ReactNode,
|
||||
options?: Parameters<CoreStart['overlays']['openFlyout']>['1']
|
||||
options?: Parameters<CoreStart['overlays']['openModal']>['1']
|
||||
) => ReturnType<CoreStart['overlays']['openModal']>;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
<EuiFlyout ownFocus onClose={() => flyoutSession && flyoutSession.close()}>
|
||||
Hello World, I am a hello world action!
|
||||
</EuiFlyout>,
|
||||
toMountPoint(
|
||||
<EuiFlyout ownFocus onClose={() => flyoutSession && flyoutSession.close()}>
|
||||
Hello World, I am a hello world action!
|
||||
</EuiFlyout>
|
||||
),
|
||||
{
|
||||
'data-test-subj': 'helloWorldAction',
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
<EuiFlyout ownFocus onClose={() => flyoutSession && flyoutSession.close()}>
|
||||
this.getDisplayName(context)
|
||||
</EuiFlyout>,
|
||||
toMountPoint(
|
||||
<EuiFlyout ownFocus onClose={() => flyoutSession && flyoutSession.close()}>
|
||||
this.getDisplayName(context)
|
||||
</EuiFlyout>
|
||||
),
|
||||
{
|
||||
'data-test-subj': 'sayHelloAction',
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
<React.Fragment>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="m" data-test-subj="samplePanelActionTitle">
|
||||
<h1>{embeddable.getTitle()}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<h3 data-test-subj="samplePanelActionBody">This is a sample action</h3>
|
||||
</EuiFlyoutBody>
|
||||
</React.Fragment>,
|
||||
toMountPoint(
|
||||
<React.Fragment>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="m" data-test-subj="samplePanelActionTitle">
|
||||
<h1>{embeddable.getTitle()}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<h3 data-test-subj="samplePanelActionBody">This is a sample action</h3>
|
||||
</EuiFlyoutBody>
|
||||
</React.Fragment>
|
||||
),
|
||||
{
|
||||
'data-test-subj': 'samplePanelActionFlyout',
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
<Provider store={store}>
|
||||
<Settings observable={settingsObservable} />
|
||||
</Provider>, {
|
||||
toMountPoint(
|
||||
<Provider store={store}>
|
||||
<Settings observable={settingsObservable} />
|
||||
</Provider>
|
||||
), {
|
||||
size: 'm',
|
||||
closeButtonAriaLabel: i18n.translate('xpack.graph.settings.closeLabel', { defaultMessage: 'Close' }),
|
||||
'data-test-subj': 'graphSettingsFlyout',
|
||||
|
|
|
@ -80,7 +80,8 @@ function GuidancePanelComponent(props: GuidancePanelProps) {
|
|||
} = props;
|
||||
|
||||
const kibana = useKibana<IDataPluginServices>();
|
||||
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 = () => {
|
||||
|
|
|
@ -86,7 +86,8 @@ export function SearchBarComponent(props: SearchBarProps) {
|
|||
}, [currentDatasource]);
|
||||
|
||||
const kibana = useKibana<IDataPluginServices>();
|
||||
const { overlays, savedObjects, uiSettings } = kibana.services;
|
||||
const { services, overlays } = kibana;
|
||||
const { savedObjects, uiSettings } = services;
|
||||
if (!overlays) return null;
|
||||
|
||||
return (
|
||||
|
|
|
@ -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'];
|
||||
},
|
||||
|
|
|
@ -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(
|
||||
<EuiModal onClose={() => modal.close()}>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>
|
||||
{i18n.translate('xpack.transform.toastText.modalTitle', {
|
||||
defaultMessage: 'Error details',
|
||||
})}
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<EuiCodeBlock language="json" fontSize="m" paddingSize="s" isCopyable>
|
||||
{formattedText}
|
||||
</EuiCodeBlock>
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter>
|
||||
<EuiButtonEmpty onClick={() => modal.close()}>
|
||||
{i18n.translate('xpack.transform.toastText.closeModalButtonText', {
|
||||
defaultMessage: 'Close',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
toMountPoint(
|
||||
<EuiModal onClose={() => modal.close()}>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>
|
||||
{i18n.translate('xpack.transform.toastText.modalTitle', {
|
||||
defaultMessage: 'Error details',
|
||||
})}
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<EuiCodeBlock language="json" fontSize="m" paddingSize="s" isCopyable>
|
||||
{formattedText}
|
||||
</EuiCodeBlock>
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter>
|
||||
<EuiButtonEmpty onClick={() => modal.close()}>
|
||||
{i18n.translate('xpack.transform.toastText.closeModalButtonText', {
|
||||
defaultMessage: 'Close',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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<string, EmbeddableFactory>();
|
||||
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<TimeRangeEmbeddable>('1');
|
||||
const child2 = container.getChild<TimeRangeEmbeddable>('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<TimeRangeEmbeddable>('1');
|
||||
const child2 = container.getChild<TimeRangeEmbeddable>('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<TimeRangeEmbeddable>('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<HelloWorldEmbeddable>('1');
|
||||
|
||||
const start = coreMock.createStart();
|
||||
const openModalMock = createOpenModalMock();
|
||||
const action = await new CustomTimeRangeAction({
|
||||
openModal: start.overlays.openModal,
|
||||
openModal: openModalMock,
|
||||
dateFormat: 'MM YYYY',
|
||||
commonlyUsedRanges: [],
|
||||
});
|
||||
|
|
|
@ -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<TimeRangeEmbeddable>('1');
|
||||
const child2 = container.getChild<TimeRangeEmbeddable>('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<TimeRangeEmbeddable>('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<TimeRangeEmbeddable>('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<TimeRangeEmbeddable>('1');
|
||||
|
||||
const start = coreMock.createStart();
|
||||
const openModalMock = jest.fn();
|
||||
const badge = await new CustomTimeRangeBadge({
|
||||
openModal: start.overlays.openModal,
|
||||
openModal: openModalMock,
|
||||
dateFormat: 'MM YYYY',
|
||||
commonlyUsedRanges: [],
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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'];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue