mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Controls] [Portable Dashboards] Add control group renderer example plugin (#146189)
Closes https://github.com/elastic/kibana/issues/145429 ## Summary This PR expands on the control group building block by 1. Replacing the old `input` declarative API and replacing it with a `getCreationOptions` callback 2. Exposing the redux embeddable tools to the consumer As part of this, I created an example plugin to demonstrate some of the new functionality 👍  Also, to avoid some code duplication, I had to move some code to the generic `control_group_helpers.tsx` so that both the controls plugin and the new example plugin could use it. ### Checklist - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
dd86c7f25c
commit
bb91749f0c
13 changed files with 393 additions and 50 deletions
11
examples/controls_example/kibana.json
Normal file
11
examples/controls_example/kibana.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"id": "controlsExample",
|
||||
"owner": {
|
||||
"name": "Kibana Presentation",
|
||||
"githubTeam": "kibana-presentation"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"ui": true,
|
||||
"requiredPlugins": ["data", "developerExamples", "presentationUtil", "controls"]
|
||||
}
|
42
examples/controls_example/public/app.tsx
Normal file
42
examples/controls_example/public/app.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { AppMountParameters } from '@kbn/core/public';
|
||||
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
|
||||
import { ControlsExampleStartDeps } from './plugin';
|
||||
import { BasicReduxExample } from './basic_redux_example';
|
||||
|
||||
interface Props {
|
||||
dataView: DataView;
|
||||
}
|
||||
|
||||
const ControlsExamples = ({ dataView }: Props) => {
|
||||
return (
|
||||
<KibanaPageTemplate>
|
||||
<KibanaPageTemplate.Header pageTitle="Controls as a Building Block" />
|
||||
<KibanaPageTemplate.Section>
|
||||
<BasicReduxExample dataView={dataView} />
|
||||
</KibanaPageTemplate.Section>
|
||||
</KibanaPageTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export const renderApp = async (
|
||||
{ data }: ControlsExampleStartDeps,
|
||||
{ element }: AppMountParameters
|
||||
) => {
|
||||
const dataViews = await data.dataViews.find('kibana_sample_data_ecommerce');
|
||||
if (dataViews.length > 0) {
|
||||
ReactDOM.render(<ControlsExamples dataView={dataViews[0]} />, element);
|
||||
}
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
133
examples/controls_example/public/basic_redux_example.tsx
Normal file
133
examples/controls_example/public/basic_redux_example.tsx
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
LazyControlGroupRenderer,
|
||||
ControlGroupContainer,
|
||||
ControlGroupInput,
|
||||
useControlGroupContainerContext,
|
||||
ControlStyle,
|
||||
} from '@kbn/controls-plugin/public';
|
||||
import { withSuspense } from '@kbn/presentation-util-plugin/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import {
|
||||
EuiButtonGroup,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { getDefaultControlGroupInput } from '@kbn/controls-plugin/common';
|
||||
|
||||
interface Props {
|
||||
dataView: DataView;
|
||||
}
|
||||
const ControlGroupRenderer = withSuspense(LazyControlGroupRenderer);
|
||||
|
||||
export const BasicReduxExample = ({ dataView }: Props) => {
|
||||
const [myControlGroup, setControlGroup] = useState<ControlGroupContainer>();
|
||||
const [currentControlStyle, setCurrentControlStyle] = useState<ControlStyle>('oneLine');
|
||||
|
||||
const ControlGroupReduxWrapper = useMemo(() => {
|
||||
if (myControlGroup) return myControlGroup.getReduxEmbeddableTools().Wrapper;
|
||||
}, [myControlGroup]);
|
||||
|
||||
const ButtonControls = () => {
|
||||
const {
|
||||
useEmbeddableDispatch,
|
||||
actions: { setControlStyle },
|
||||
} = useControlGroupContainerContext();
|
||||
const dispatch = useEmbeddableDispatch();
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<p>Choose a style for your control group:</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButtonGroup
|
||||
legend="Text style"
|
||||
options={[
|
||||
{
|
||||
id: `oneLine`,
|
||||
label: 'One line',
|
||||
value: 'oneLine' as ControlStyle,
|
||||
},
|
||||
{
|
||||
id: `twoLine`,
|
||||
label: 'Two lines',
|
||||
value: 'twoLine' as ControlStyle,
|
||||
},
|
||||
]}
|
||||
idSelected={currentControlStyle}
|
||||
onChange={(id, value) => {
|
||||
setCurrentControlStyle(value);
|
||||
dispatch(setControlStyle(value));
|
||||
}}
|
||||
type="single"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTitle>
|
||||
<h2>Basic Redux Example</h2>
|
||||
</EuiTitle>
|
||||
<EuiText>
|
||||
<p>
|
||||
This example uses the redux context from the control group container in order to
|
||||
dynamically change the style of the control group.
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiPanel hasBorder={true}>
|
||||
{ControlGroupReduxWrapper && (
|
||||
<ControlGroupReduxWrapper>
|
||||
<ButtonControls />
|
||||
</ControlGroupReduxWrapper>
|
||||
)}
|
||||
|
||||
<ControlGroupRenderer
|
||||
onEmbeddableLoad={async (controlGroup) => {
|
||||
setControlGroup(controlGroup);
|
||||
}}
|
||||
getCreationOptions={async (controlGroupInputBuilder) => {
|
||||
const initialInput: Partial<ControlGroupInput> = {
|
||||
...getDefaultControlGroupInput(),
|
||||
defaultControlWidth: 'small',
|
||||
};
|
||||
await controlGroupInputBuilder.addDataControlFromField(initialInput, {
|
||||
dataViewId: dataView.id ?? 'kibana_sample_data_ecommerce',
|
||||
fieldName: 'customer_first_name.keyword',
|
||||
});
|
||||
await controlGroupInputBuilder.addDataControlFromField(initialInput, {
|
||||
dataViewId: dataView.id ?? 'kibana_sample_data_ecommerce',
|
||||
fieldName: 'customer_last_name.keyword',
|
||||
width: 'medium',
|
||||
grow: false,
|
||||
title: 'Last Name',
|
||||
});
|
||||
return initialInput;
|
||||
}}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
};
|
BIN
examples/controls_example/public/control_group_image.png
Normal file
BIN
examples/controls_example/public/control_group_image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
13
examples/controls_example/public/index.ts
Normal file
13
examples/controls_example/public/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ControlsExamplePlugin } from './plugin';
|
||||
|
||||
export function plugin() {
|
||||
return new ControlsExamplePlugin();
|
||||
}
|
54
examples/controls_example/public/plugin.tsx
Normal file
54
examples/controls_example/public/plugin.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
AppMountParameters,
|
||||
AppNavLinkStatus,
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Plugin,
|
||||
} from '@kbn/core/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
|
||||
import img from './control_group_image.png';
|
||||
|
||||
interface SetupDeps {
|
||||
developerExamples: DeveloperExamplesSetup;
|
||||
}
|
||||
|
||||
export interface ControlsExampleStartDeps {
|
||||
data: DataPublicPluginStart;
|
||||
}
|
||||
|
||||
export class ControlsExamplePlugin
|
||||
implements Plugin<void, void, SetupDeps, ControlsExampleStartDeps>
|
||||
{
|
||||
public setup(core: CoreSetup<ControlsExampleStartDeps>, { developerExamples }: SetupDeps) {
|
||||
core.application.register({
|
||||
id: 'controlsExamples',
|
||||
title: 'Controls examples',
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
async mount(params: AppMountParameters) {
|
||||
const [, depsStart] = await core.getStartServices();
|
||||
const { renderApp } = await import('./app');
|
||||
return renderApp(depsStart, params);
|
||||
},
|
||||
});
|
||||
|
||||
developerExamples.register({
|
||||
appId: 'controlsExamples',
|
||||
title: 'Controls as a Building Block',
|
||||
description: `Showcases different ways to embed a control group into your app`,
|
||||
image: img,
|
||||
});
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {}
|
||||
|
||||
public stop() {}
|
||||
}
|
22
examples/controls_example/tsconfig.json
Normal file
22
examples/controls_example/tsconfig.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target/types"
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"common/**/*.ts",
|
||||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"server/**/*.ts",
|
||||
"../../typings/**/*"
|
||||
],
|
||||
"exclude": [],
|
||||
"kbn_references": [
|
||||
{ "path": "../../src/core/tsconfig.json" },
|
||||
{ "path": "../developer_examples/tsconfig.json" },
|
||||
{ "path": "../../src/plugins/data/tsconfig.json" },
|
||||
{ "path": "../../src/plugins/controls/tsconfig.json" },
|
||||
{ "path": "../../src/plugins/presentation_util/tsconfig.json" }
|
||||
]
|
||||
}
|
|
@ -8,41 +8,87 @@
|
|||
|
||||
import uuid from 'uuid';
|
||||
import useLifecycles from 'react-use/lib/useLifecycles';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { IEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||
import { useReduxContainerContext } from '@kbn/presentation-util-plugin/public';
|
||||
|
||||
import { pluginServices } from '../services';
|
||||
import { getDefaultControlGroupInput } from '../../common';
|
||||
import { ControlGroupInput, ControlGroupOutput, CONTROL_GROUP_TYPE } from './types';
|
||||
import { ControlPanelState, getDefaultControlGroupInput } from '../../common';
|
||||
import {
|
||||
ControlGroupInput,
|
||||
ControlGroupOutput,
|
||||
ControlGroupReduxState,
|
||||
CONTROL_GROUP_TYPE,
|
||||
} from './types';
|
||||
import { ControlGroupContainer } from './embeddable/control_group_container';
|
||||
import { DataControlInput } from '../types';
|
||||
import { getCompatibleControlType, getNextPanelOrder } from './embeddable/control_group_helpers';
|
||||
import { controlGroupReducers } from './state/control_group_reducers';
|
||||
|
||||
const ControlGroupInputBuilder = {
|
||||
addDataControlFromField: async (
|
||||
initialInput: Partial<ControlGroupInput>,
|
||||
newPanelInput: {
|
||||
title?: string;
|
||||
panelId?: string;
|
||||
fieldName: string;
|
||||
dataViewId: string;
|
||||
} & Partial<ControlPanelState>
|
||||
) => {
|
||||
const { defaultControlGrow, defaultControlWidth } = getDefaultControlGroupInput();
|
||||
const controlGrow = initialInput.defaultControlGrow ?? defaultControlGrow;
|
||||
const controlWidth = initialInput.defaultControlWidth ?? defaultControlWidth;
|
||||
|
||||
const { panelId, dataViewId, fieldName, title, grow, width } = newPanelInput;
|
||||
const newPanelId = panelId || uuid.v4();
|
||||
const nextOrder = getNextPanelOrder(initialInput);
|
||||
const controlType = await getCompatibleControlType({ dataViewId, fieldName });
|
||||
|
||||
initialInput.panels = {
|
||||
...initialInput.panels,
|
||||
[newPanelId]: {
|
||||
order: nextOrder,
|
||||
type: controlType,
|
||||
grow: grow ?? controlGrow,
|
||||
width: width ?? controlWidth,
|
||||
explicitInput: { id: newPanelId, dataViewId, fieldName, title: title ?? fieldName },
|
||||
} as ControlPanelState<DataControlInput>,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export interface ControlGroupRendererProps {
|
||||
input?: Partial<Pick<ControlGroupInput, 'viewMode' | 'executionContext'>>;
|
||||
onEmbeddableLoad: (controlGroupContainer: ControlGroupContainer) => void;
|
||||
getCreationOptions: (
|
||||
builder: typeof ControlGroupInputBuilder
|
||||
) => Promise<Partial<ControlGroupInput>>;
|
||||
}
|
||||
|
||||
export const ControlGroupRenderer = ({ input, onEmbeddableLoad }: ControlGroupRendererProps) => {
|
||||
export const ControlGroupRenderer = ({
|
||||
onEmbeddableLoad,
|
||||
getCreationOptions,
|
||||
}: ControlGroupRendererProps) => {
|
||||
const controlsRoot = useRef(null);
|
||||
const [controlGroupContainer, setControlGroupContainer] = useState<ControlGroupContainer>();
|
||||
|
||||
const id = useMemo(() => uuid.v4(), []);
|
||||
|
||||
/**
|
||||
* Use Lifecycles to load initial control group container
|
||||
*/
|
||||
useLifecycles(
|
||||
() => {
|
||||
const { embeddable } = pluginServices.getServices();
|
||||
|
||||
(async () => {
|
||||
const container = (await embeddable
|
||||
.getEmbeddableFactory<
|
||||
ControlGroupInput,
|
||||
ControlGroupOutput,
|
||||
IEmbeddable<ControlGroupInput, ControlGroupOutput>
|
||||
>(CONTROL_GROUP_TYPE)
|
||||
?.create({ id, ...getDefaultControlGroupInput(), ...input })) as ControlGroupContainer;
|
||||
const factory = embeddable.getEmbeddableFactory<
|
||||
ControlGroupInput,
|
||||
ControlGroupOutput,
|
||||
IEmbeddable<ControlGroupInput, ControlGroupOutput>
|
||||
>(CONTROL_GROUP_TYPE);
|
||||
const container = (await factory?.create({
|
||||
id,
|
||||
...getDefaultControlGroupInput(),
|
||||
...(await getCreationOptions(ControlGroupInputBuilder)),
|
||||
})) as ControlGroupContainer;
|
||||
|
||||
if (controlsRoot.current) {
|
||||
container.render(controlsRoot.current);
|
||||
|
@ -56,29 +102,12 @@ export const ControlGroupRenderer = ({ input, onEmbeddableLoad }: ControlGroupRe
|
|||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Update embeddable input when props input changes
|
||||
*/
|
||||
useEffect(() => {
|
||||
let updateCanceled = false;
|
||||
(async () => {
|
||||
// check if applying input from props would result in any changes to the embeddable input
|
||||
const isInputEqual = await controlGroupContainer?.getExplicitInputIsEqual({
|
||||
...controlGroupContainer?.getInput(),
|
||||
...input,
|
||||
});
|
||||
if (!controlGroupContainer || isInputEqual || updateCanceled) return;
|
||||
controlGroupContainer.updateInput({ ...input });
|
||||
})();
|
||||
|
||||
return () => {
|
||||
updateCanceled = true;
|
||||
};
|
||||
}, [controlGroupContainer, input]);
|
||||
|
||||
return <div ref={controlsRoot} />;
|
||||
};
|
||||
|
||||
export const useControlGroupContainerContext = () =>
|
||||
useReduxContainerContext<ControlGroupReduxState, typeof controlGroupReducers>();
|
||||
|
||||
// required for dynamic import using React.lazy()
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ControlGroupRenderer;
|
||||
|
|
|
@ -44,7 +44,7 @@ import { ControlEmbeddable, ControlInput, ControlOutput, DataControlInput } from
|
|||
import { CreateControlButton, CreateControlButtonTypes } from '../editor/create_control';
|
||||
import { CreateTimeSliderControlButton } from '../editor/create_time_slider_control';
|
||||
import { TIME_SLIDER_CONTROL } from '../../time_slider';
|
||||
import { getDataControlFieldRegistry } from '../editor/data_control_editor_tools';
|
||||
import { getCompatibleControlType, getNextPanelOrder } from './control_group_helpers';
|
||||
|
||||
let flyoutRef: OverlayRef | undefined;
|
||||
export const setFlyoutRef = (newRef: OverlayRef | undefined) => {
|
||||
|
@ -87,6 +87,10 @@ export class ControlGroupContainer extends Container<
|
|||
return this.lastUsedDataViewId ?? this.relevantDataViewId;
|
||||
};
|
||||
|
||||
public getReduxEmbeddableTools = () => {
|
||||
return this.reduxEmbeddableTools;
|
||||
};
|
||||
|
||||
public closeAllFlyouts() {
|
||||
flyoutRef?.close();
|
||||
flyoutRef = undefined;
|
||||
|
@ -103,10 +107,7 @@ export class ControlGroupContainer extends Container<
|
|||
fieldName: string;
|
||||
title?: string;
|
||||
}) {
|
||||
const dataView = await pluginServices.getServices().dataViews.get(dataViewId);
|
||||
const fieldRegistry = await getDataControlFieldRegistry(dataView);
|
||||
const field = fieldRegistry[fieldName];
|
||||
return this.addNewEmbeddable(field.compatibleControlTypes[0], {
|
||||
return this.addNewEmbeddable(await getCompatibleControlType({ dataViewId, fieldName }), {
|
||||
id: uuid,
|
||||
dataViewId,
|
||||
fieldName,
|
||||
|
@ -316,14 +317,7 @@ export class ControlGroupContainer extends Container<
|
|||
partial: Partial<TEmbeddableInput> = {}
|
||||
): ControlPanelState<TEmbeddableInput> {
|
||||
const panelState = super.createNewPanelState(factory, partial);
|
||||
let nextOrder = 0;
|
||||
if (Object.keys(this.getInput().panels).length > 0) {
|
||||
nextOrder =
|
||||
Object.values(this.getInput().panels).reduce((highestSoFar, panel) => {
|
||||
if (panel.order > highestSoFar) highestSoFar = panel.order;
|
||||
return highestSoFar;
|
||||
}, 0) + 1;
|
||||
}
|
||||
const nextOrder = getNextPanelOrder(this.getInput());
|
||||
return {
|
||||
order: nextOrder,
|
||||
width:
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ControlGroupInput } from '../types';
|
||||
import { pluginServices } from '../../services';
|
||||
import { getDataControlFieldRegistry } from '../editor/data_control_editor_tools';
|
||||
|
||||
export const getNextPanelOrder = (initialInput: Partial<ControlGroupInput>) => {
|
||||
let nextOrder = 0;
|
||||
if (Object.keys(initialInput.panels ?? {}).length > 0) {
|
||||
nextOrder =
|
||||
Object.values(initialInput.panels ?? {}).reduce((highestSoFar, panel) => {
|
||||
if (panel.order > highestSoFar) highestSoFar = panel.order;
|
||||
return highestSoFar;
|
||||
}, 0) + 1;
|
||||
}
|
||||
return nextOrder;
|
||||
};
|
||||
|
||||
export const getCompatibleControlType = async ({
|
||||
dataViewId,
|
||||
fieldName,
|
||||
}: {
|
||||
dataViewId: string;
|
||||
fieldName: string;
|
||||
}) => {
|
||||
const dataView = await pluginServices.getServices().dataViews.get(dataViewId);
|
||||
const fieldRegistry = await getDataControlFieldRegistry(dataView);
|
||||
const field = fieldRegistry[fieldName];
|
||||
return field.compatibleControlTypes[0];
|
||||
};
|
|
@ -14,5 +14,8 @@ export type { ControlGroupInput, ControlGroupOutput } from './types';
|
|||
export { CONTROL_GROUP_TYPE } from './types';
|
||||
export { ControlGroupContainerFactory } from './embeddable/control_group_container_factory';
|
||||
|
||||
export type { ControlGroupRendererProps } from './control_group_renderer';
|
||||
export {
|
||||
type ControlGroupRendererProps,
|
||||
useControlGroupContainerContext,
|
||||
} from './control_group_renderer';
|
||||
export const LazyControlGroupRenderer = React.lazy(() => import('./control_group_renderer'));
|
||||
|
|
|
@ -51,7 +51,11 @@ export {
|
|||
} from './range_slider';
|
||||
|
||||
export { LazyControlsCallout, type CalloutProps } from './controls_callout';
|
||||
export { LazyControlGroupRenderer, type ControlGroupRendererProps } from './control_group';
|
||||
export {
|
||||
LazyControlGroupRenderer,
|
||||
useControlGroupContainerContext,
|
||||
type ControlGroupRendererProps,
|
||||
} from './control_group';
|
||||
|
||||
export function plugin() {
|
||||
return new ControlsPlugin();
|
||||
|
|
|
@ -734,6 +734,8 @@
|
|||
"@kbn/ml-string-hash/*": ["x-pack/packages/ml/string_hash/*"],
|
||||
"@kbn/bfetch-explorer-plugin": ["examples/bfetch_explorer"],
|
||||
"@kbn/bfetch-explorer-plugin/*": ["examples/bfetch_explorer/*"],
|
||||
"@kbn/controls-example-plugin": ["examples/controls_example"],
|
||||
"@kbn/controls-example-plugin/*": ["examples/controls_example/*"],
|
||||
"@kbn/dashboard-embeddable-examples-plugin": ["examples/dashboard_embeddable_examples"],
|
||||
"@kbn/dashboard-embeddable-examples-plugin/*": ["examples/dashboard_embeddable_examples/*"],
|
||||
"@kbn/data-view-field-editor-example-plugin": ["examples/data_view_field_editor_example"],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue