mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[Embeddable Rebuild] [Controls] Clean up styling + add clear selections to timeslider (#186656)
## Summary The primary goal of this PR is to clean up the styling of the `ControlPanel` component for the new React control renderer. Specifically, this fixes the following: - I switched the inline Emotion styling to CSS classes instead - I made it so that the timeslider control renders the drag handler in edit mode and **doesn't** render the empty icon for the drag handler in view mode <p align="center"><img width="600px" src="d5bf169b
-2106-4f88-9698-f00162809d0a"/><p> - I fixed the timeslider prepend so that it no longer wraps <p align="center"><img width="500px" src="7859d67b
-1454-45b5-b7d8-7000086641a7"/><p> - I moved the error component into the `EuiFormControlLayout` component, which ensures that the drag handler is rendered for when a control has a blocking error. I also fixed the styling for the error component: <p align="center"><img width="600px" src="13e0f041
-8c51-494c-9079-323ed518c87b"/><p> When I was working on these style changes, I noticed that the timeslider control wasn't implementing `CanClearSelections` which meant that it no longer had the clear selections action. This made me realize that this interface should probably be part of the `DefaultControlApi` rather than `DefaultDataControlApi` so, I moved it and added `clearSelections` to the timeslider API. <p align="center"><img width="600px" src="47f7b648
-bb2d-4158-b058-456bfdf5cdb5"/><p> ### Checklist - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [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)) ### 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) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
82d32a757f
commit
80f3c191ce
18 changed files with 269 additions and 246 deletions
|
@ -16,6 +16,7 @@ import {
|
||||||
EuiTab,
|
EuiTab,
|
||||||
EuiTabs,
|
EuiTabs,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
import { I18nProvider } from '@kbn/i18n-react';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ const App = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<I18nProvider>
|
||||||
<EuiPage>
|
<EuiPage>
|
||||||
<EuiPageBody>
|
<EuiPageBody>
|
||||||
<EuiPageSection>
|
<EuiPageSection>
|
||||||
|
@ -76,6 +78,7 @@ const App = ({
|
||||||
</EuiPageTemplate.Section>
|
</EuiPageTemplate.Section>
|
||||||
</EuiPageBody>
|
</EuiPageBody>
|
||||||
</EuiPage>
|
</EuiPage>
|
||||||
|
</I18nProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,16 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EuiButton,
|
EuiButton,
|
||||||
EuiButtonGroup,
|
EuiButtonGroup,
|
||||||
|
EuiCallOut,
|
||||||
EuiCodeBlock,
|
EuiCodeBlock,
|
||||||
EuiEmptyPrompt,
|
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
EuiLoadingSpinner,
|
|
||||||
EuiSpacer,
|
EuiSpacer,
|
||||||
EuiSuperDatePicker,
|
EuiSuperDatePicker,
|
||||||
OnTimeChangeProps,
|
OnTimeChangeProps,
|
||||||
|
@ -23,27 +25,20 @@ import { CoreStart } from '@kbn/core/public';
|
||||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||||
import { ReactEmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public';
|
import { ReactEmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public';
|
||||||
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
|
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
|
||||||
import { combineCompatibleChildrenApis, PresentationContainer } from '@kbn/presentation-containers';
|
import { combineCompatibleChildrenApis } from '@kbn/presentation-containers';
|
||||||
import {
|
import {
|
||||||
apiPublishesDataLoading,
|
apiPublishesDataLoading,
|
||||||
HasUniqueId,
|
HasUniqueId,
|
||||||
PublishesDataLoading,
|
PublishesDataLoading,
|
||||||
PublishesUnifiedSearch,
|
|
||||||
PublishesViewMode,
|
|
||||||
PublishingSubject,
|
|
||||||
useBatchedPublishingSubjects,
|
useBatchedPublishingSubjects,
|
||||||
useStateFromPublishingSubject,
|
|
||||||
ViewMode as ViewModeType,
|
ViewMode as ViewModeType,
|
||||||
} from '@kbn/presentation-publishing';
|
} from '@kbn/presentation-publishing';
|
||||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
|
||||||
import useAsync from 'react-use/lib/useAsync';
|
|
||||||
import useMount from 'react-use/lib/useMount';
|
|
||||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
|
||||||
import { ControlGroupApi } from '../react_controls/control_group/types';
|
import { ControlGroupApi } from '../react_controls/control_group/types';
|
||||||
|
import { RANGE_SLIDER_CONTROL_TYPE } from '../react_controls/data_controls/range_slider/types';
|
||||||
import { SEARCH_CONTROL_TYPE } from '../react_controls/data_controls/search_control/types';
|
import { SEARCH_CONTROL_TYPE } from '../react_controls/data_controls/search_control/types';
|
||||||
import { TIMESLIDER_CONTROL_TYPE } from '../react_controls/timeslider_control/types';
|
import { TIMESLIDER_CONTROL_TYPE } from '../react_controls/timeslider_control/types';
|
||||||
import { RANGE_SLIDER_CONTROL_TYPE } from '../react_controls/data_controls/range_slider/types';
|
|
||||||
|
|
||||||
const toggleViewButtons = [
|
const toggleViewButtons = [
|
||||||
{
|
{
|
||||||
|
@ -104,18 +99,7 @@ const controlGroupPanels = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const WEB_LOGS_DATA_VIEW_ID = '90943e30-9a47-11e8-b64d-95841ca0b247';
|
||||||
* I am mocking the dashboard API so that the data table embeddble responds to changes to the
|
|
||||||
* data view publishing subject from the control group
|
|
||||||
*/
|
|
||||||
type MockedDashboardApi = PresentationContainer &
|
|
||||||
PublishesDataLoading &
|
|
||||||
PublishesViewMode &
|
|
||||||
PublishesUnifiedSearch & {
|
|
||||||
setViewMode: (newViewMode: ViewMode) => void;
|
|
||||||
setChild: (child: HasUniqueId) => void;
|
|
||||||
unifiedSearchFilters$: PublishingSubject<Filter[] | undefined>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ReactControlExample = ({
|
export const ReactControlExample = ({
|
||||||
core,
|
core,
|
||||||
|
@ -145,28 +129,34 @@ export const ReactControlExample = ({
|
||||||
const timeslice$ = useMemo(() => {
|
const timeslice$ = useMemo(() => {
|
||||||
return new BehaviorSubject<[number, number] | undefined>(undefined);
|
return new BehaviorSubject<[number, number] | undefined>(undefined);
|
||||||
}, []);
|
}, []);
|
||||||
const [dataLoading, timeRange] = useBatchedPublishingSubjects(dataLoading$, timeRange$);
|
const viewMode$ = useMemo(() => {
|
||||||
|
return new BehaviorSubject<ViewModeType>(ViewMode.EDIT as ViewModeType);
|
||||||
|
}, []);
|
||||||
|
const [dataLoading, timeRange, viewMode] = useBatchedPublishingSubjects(
|
||||||
|
dataLoading$,
|
||||||
|
timeRange$,
|
||||||
|
viewMode$
|
||||||
|
);
|
||||||
|
|
||||||
const [dashboardApi, setDashboardApi] = useState<MockedDashboardApi | undefined>(undefined);
|
|
||||||
const [controlGroupApi, setControlGroupApi] = useState<ControlGroupApi | undefined>(undefined);
|
const [controlGroupApi, setControlGroupApi] = useState<ControlGroupApi | undefined>(undefined);
|
||||||
const viewModeSelected = useStateFromPublishingSubject(dashboardApi?.viewMode);
|
const [dataViewNotFound, setDataViewNotFound] = useState(false);
|
||||||
|
|
||||||
useMount(() => {
|
const dashboardApi = useMemo(() => {
|
||||||
const viewMode = new BehaviorSubject<ViewModeType>(ViewMode.EDIT as ViewModeType);
|
|
||||||
const query$ = new BehaviorSubject<Query | AggregateQuery | undefined>(undefined);
|
const query$ = new BehaviorSubject<Query | AggregateQuery | undefined>(undefined);
|
||||||
const children$ = new BehaviorSubject<{ [key: string]: unknown }>({});
|
const children$ = new BehaviorSubject<{ [key: string]: unknown }>({});
|
||||||
|
|
||||||
setDashboardApi({
|
return {
|
||||||
dataLoading: dataLoading$,
|
dataLoading: dataLoading$,
|
||||||
viewMode,
|
|
||||||
unifiedSearchFilters$,
|
unifiedSearchFilters$,
|
||||||
|
viewMode: viewMode$,
|
||||||
filters$,
|
filters$,
|
||||||
query$,
|
query$,
|
||||||
timeRange$,
|
timeRange$,
|
||||||
timeslice$,
|
timeslice$,
|
||||||
children$,
|
children$,
|
||||||
setViewMode: (newViewMode) => viewMode.next(newViewMode),
|
publishFilters: (newFilters: Filter[] | undefined) => filters$.next(newFilters),
|
||||||
setChild: (child) => children$.next({ ...children$.getValue(), [child.uuid]: child }),
|
setChild: (child: HasUniqueId) =>
|
||||||
|
children$.next({ ...children$.getValue(), [child.uuid]: child }),
|
||||||
removePanel: () => {},
|
removePanel: () => {},
|
||||||
replacePanel: () => {
|
replacePanel: () => {
|
||||||
return Promise.resolve('');
|
return Promise.resolve('');
|
||||||
|
@ -177,8 +167,9 @@ export const ReactControlExample = ({
|
||||||
addNewPanel: () => {
|
addNewPanel: () => {
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
});
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = combineCompatibleChildrenApis<PublishesDataLoading, boolean | undefined>(
|
const subscription = combineCompatibleChildrenApis<PublishesDataLoading, boolean | undefined>(
|
||||||
|
@ -199,13 +190,18 @@ export const ReactControlExample = ({
|
||||||
};
|
};
|
||||||
}, [dashboardApi, dataLoading$]);
|
}, [dashboardApi, dataLoading$]);
|
||||||
|
|
||||||
// TODO: Maybe remove `useAsync` - see https://github.com/elastic/kibana/pull/182842#discussion_r1624909709
|
useEffect(() => {
|
||||||
const {
|
let ignore = false;
|
||||||
loading,
|
dataViewsService.get(WEB_LOGS_DATA_VIEW_ID).catch(() => {
|
||||||
value: dataViews,
|
if (!ignore) {
|
||||||
error,
|
setDataViewNotFound(true);
|
||||||
} = useAsync(async () => {
|
}
|
||||||
return await dataViewsService.find('kibana_sample_data_logs');
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ignore = true;
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -244,20 +240,16 @@ export const ReactControlExample = ({
|
||||||
};
|
};
|
||||||
}, [controlGroupFilters$, filters$, unifiedSearchFilters$]);
|
}, [controlGroupFilters$, filters$, unifiedSearchFilters$]);
|
||||||
|
|
||||||
if (error || (!dataViews?.[0]?.id && !loading))
|
|
||||||
return (
|
return (
|
||||||
<EuiEmptyPrompt
|
|
||||||
iconType="error"
|
|
||||||
color="danger"
|
|
||||||
title={<h2>There was an error!</h2>}
|
|
||||||
body={<p>{error ? error.message : 'Please add at least one data view.'}</p>}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return loading ? (
|
|
||||||
<EuiLoadingSpinner />
|
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
|
{dataViewNotFound && (
|
||||||
|
<>
|
||||||
|
<EuiCallOut color="warning" iconType="warning">
|
||||||
|
<p>{`Install "Sample web logs" to run example`}</p>
|
||||||
|
</EuiCallOut>
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<EuiFlexGroup>
|
<EuiFlexGroup>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiButton
|
<EuiButton
|
||||||
|
@ -294,9 +286,9 @@ export const ReactControlExample = ({
|
||||||
<EuiButtonGroup
|
<EuiButtonGroup
|
||||||
legend="Change the view mode"
|
legend="Change the view mode"
|
||||||
options={toggleViewButtons}
|
options={toggleViewButtons}
|
||||||
idSelected={`viewModeToggle_${viewModeSelected}`}
|
idSelected={`viewModeToggle_${viewMode}`}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
dashboardApi?.setViewMode(value);
|
viewMode$.next(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
@ -336,12 +328,12 @@ export const ReactControlExample = ({
|
||||||
{
|
{
|
||||||
name: `controlGroup_${searchControlId}:${SEARCH_CONTROL_TYPE}DataView`,
|
name: `controlGroup_${searchControlId}:${SEARCH_CONTROL_TYPE}DataView`,
|
||||||
type: 'index-pattern',
|
type: 'index-pattern',
|
||||||
id: dataViews?.[0].id!,
|
id: WEB_LOGS_DATA_VIEW_ID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `controlGroup_${rangeSliderControlId}:${RANGE_SLIDER_CONTROL_TYPE}DataView`,
|
name: `controlGroup_${rangeSliderControlId}:${RANGE_SLIDER_CONTROL_TYPE}DataView`,
|
||||||
type: 'index-pattern',
|
type: 'index-pattern',
|
||||||
id: dataViews?.[0].id!,
|
id: WEB_LOGS_DATA_VIEW_ID,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { EuiButtonEmpty, EuiPopover } from '@elastic/eui';
|
import { EuiButtonEmpty, EuiPopover } from '@elastic/eui';
|
||||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { Markdown } from '@kbn/shared-ux-markdown';
|
import { Markdown } from '@kbn/shared-ux-markdown';
|
||||||
|
|
||||||
/** TODO: This file is duplicated from the controls plugin to avoid exporting it */
|
/** TODO: This file is duplicated from the controls plugin to avoid exporting it */
|
||||||
|
@ -24,13 +24,15 @@ export const ControlError = ({ error }: ControlErrorProps) => {
|
||||||
|
|
||||||
const popoverButton = (
|
const popoverButton = (
|
||||||
<EuiButtonEmpty
|
<EuiButtonEmpty
|
||||||
|
flush="left"
|
||||||
color="danger"
|
color="danger"
|
||||||
iconSize="m"
|
iconSize="m"
|
||||||
iconType="error"
|
iconType="error"
|
||||||
data-test-subj="control-frame-error"
|
data-test-subj="control-frame-error"
|
||||||
onClick={() => setPopoverOpen((open) => !open)}
|
onClick={() => setPopoverOpen((open) => !open)}
|
||||||
className={'errorEmbeddableCompact__button'}
|
className="errorEmbeddableCompact__button controlErrorButton"
|
||||||
textProps={{ className: 'errorEmbeddableCompact__text' }}
|
textProps={{ className: 'errorEmbeddableCompact__text' }}
|
||||||
|
contentProps={{ className: 'controlErrorButton--content' }}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="controls.frame.error.message"
|
id="controls.frame.error.message"
|
||||||
|
@ -40,17 +42,15 @@ export const ControlError = ({ error }: ControlErrorProps) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<I18nProvider>
|
|
||||||
<EuiPopover
|
<EuiPopover
|
||||||
button={popoverButton}
|
button={popoverButton}
|
||||||
isOpen={isPopoverOpen}
|
isOpen={isPopoverOpen}
|
||||||
className="errorEmbeddableCompact__popover"
|
className="controlPanel errorEmbeddableCompact__popover"
|
||||||
closePopover={() => setPopoverOpen(false)}
|
closePopover={() => setPopoverOpen(false)}
|
||||||
>
|
>
|
||||||
<Markdown data-test-subj="errorMessageMarkdown" readOnly>
|
<Markdown data-test-subj="errorMessageMarkdown" readOnly>
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</EuiPopover>
|
</EuiPopover>
|
||||||
</I18nProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -230,7 +230,7 @@ export const getControlGroupEmbeddableFactory = (services: {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
api,
|
api,
|
||||||
Component: (props, test) => {
|
Component: () => {
|
||||||
const controlsInOrder = useStateFromPublishingSubject(controlOrder);
|
const controlsInOrder = useStateFromPublishingSubject(controlOrder);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
.controlPanel {
|
||||||
|
width: 100%;
|
||||||
|
max-inline-size: 100% !important;
|
||||||
|
height: calc($euiButtonHeight - 2px);
|
||||||
|
box-shadow: none !important;
|
||||||
|
background-color: $euiFormBackgroundColor !important;
|
||||||
|
|
||||||
|
border-radius: 0 $euiBorderRadius $euiBorderRadius 0 !important;
|
||||||
|
&--roundedBorders {
|
||||||
|
border-radius: $euiBorderRadius !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--label {
|
||||||
|
@include euiTextTruncate;
|
||||||
|
max-width: 40%;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: $euiBorderRadius;
|
||||||
|
|
||||||
|
margin-left: 0 !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--hideComponent {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controlErrorButton {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0 $euiBorderRadius $euiBorderRadius 0 !important;
|
||||||
|
|
||||||
|
&--content {
|
||||||
|
justify-content: left;
|
||||||
|
padding-left: $euiSizeM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ import classNames from 'classnames';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { EuiFlexItem, EuiFormControlLayout, EuiFormLabel, EuiFormRow, EuiIcon } from '@elastic/eui';
|
import { EuiFlexItem, EuiFormControlLayout, EuiFormLabel, EuiFormRow, EuiIcon } from '@elastic/eui';
|
||||||
import { css } from '@emotion/react';
|
|
||||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import {
|
import {
|
||||||
|
@ -19,15 +18,24 @@ import {
|
||||||
useBatchedOptionalPublishingSubjects,
|
useBatchedOptionalPublishingSubjects,
|
||||||
} from '@kbn/presentation-publishing';
|
} from '@kbn/presentation-publishing';
|
||||||
import { FloatingActions } from '@kbn/presentation-util-plugin/public';
|
import { FloatingActions } from '@kbn/presentation-util-plugin/public';
|
||||||
import { euiThemeVars } from '@kbn/ui-theme';
|
|
||||||
|
|
||||||
import { ControlError } from './control_error_component';
|
import { ControlError } from './control_error_component';
|
||||||
import { ControlPanelProps, DefaultControlApi } from './types';
|
import { ControlPanelProps, DefaultControlApi } from './types';
|
||||||
|
|
||||||
|
import './control_panel.scss';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Handle dragging
|
* TODO: Handle dragging
|
||||||
*/
|
*/
|
||||||
const DragHandle = ({ isEditable, controlTitle }: { isEditable: boolean; controlTitle?: string }) =>
|
const DragHandle = ({
|
||||||
|
isEditable,
|
||||||
|
controlTitle,
|
||||||
|
hideEmptyDragHandle,
|
||||||
|
}: {
|
||||||
|
isEditable: boolean;
|
||||||
|
controlTitle?: string;
|
||||||
|
hideEmptyDragHandle: boolean;
|
||||||
|
}) =>
|
||||||
isEditable ? (
|
isEditable ? (
|
||||||
<button
|
<button
|
||||||
aria-label={i18n.translate('controls.controlGroup.ariaActions.moveControlButtonAction', {
|
aria-label={i18n.translate('controls.controlGroup.ariaActions.moveControlButtonAction', {
|
||||||
|
@ -38,7 +46,9 @@ const DragHandle = ({ isEditable, controlTitle }: { isEditable: boolean; control
|
||||||
>
|
>
|
||||||
<EuiIcon type="grabHorizontal" />
|
<EuiIcon type="grabHorizontal" />
|
||||||
</button>
|
</button>
|
||||||
) : null;
|
) : hideEmptyDragHandle ? null : (
|
||||||
|
<EuiIcon size="s" type="empty" />
|
||||||
|
);
|
||||||
|
|
||||||
export const ControlPanel = <ApiType extends DefaultControlApi = DefaultControlApi>({
|
export const ControlPanel = <ApiType extends DefaultControlApi = DefaultControlApi>({
|
||||||
Component,
|
Component,
|
||||||
|
@ -115,8 +125,28 @@ export const ControlPanel = <ApiType extends DefaultControlApi = DefaultControlA
|
||||||
fullWidth
|
fullWidth
|
||||||
label={usingTwoLineLayout ? panelTitle || defaultPanelTitle || '...' : undefined}
|
label={usingTwoLineLayout ? panelTitle || defaultPanelTitle || '...' : undefined}
|
||||||
>
|
>
|
||||||
{blockingError ? (
|
<EuiFormControlLayout
|
||||||
<EuiFormControlLayout>
|
fullWidth
|
||||||
|
isLoading={Boolean(dataLoading)}
|
||||||
|
prepend={
|
||||||
|
<>
|
||||||
|
<DragHandle
|
||||||
|
isEditable={isEditable}
|
||||||
|
controlTitle={panelTitle || defaultPanelTitle}
|
||||||
|
hideEmptyDragHandle={usingTwoLineLayout || Boolean(api?.CustomPrependComponent)}
|
||||||
|
/>
|
||||||
|
{api?.CustomPrependComponent ? (
|
||||||
|
<api.CustomPrependComponent />
|
||||||
|
) : usingTwoLineLayout ? null : (
|
||||||
|
<EuiFormLabel className="controlPanel--label">
|
||||||
|
{panelTitle || defaultPanelTitle}
|
||||||
|
</EuiFormLabel>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
{blockingError && (
|
||||||
<ControlError
|
<ControlError
|
||||||
error={
|
error={
|
||||||
blockingError ??
|
blockingError ??
|
||||||
|
@ -125,53 +155,19 @@ export const ControlPanel = <ApiType extends DefaultControlApi = DefaultControlA
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</EuiFormControlLayout>
|
)}
|
||||||
) : (
|
|
||||||
<EuiFormControlLayout
|
|
||||||
fullWidth
|
|
||||||
isLoading={Boolean(dataLoading)}
|
|
||||||
prepend={
|
|
||||||
api?.CustomPrependComponent ? (
|
|
||||||
<api.CustomPrependComponent />
|
|
||||||
) : usingTwoLineLayout ? (
|
|
||||||
<DragHandle
|
|
||||||
isEditable={isEditable}
|
|
||||||
controlTitle={panelTitle || defaultPanelTitle}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<DragHandle
|
|
||||||
isEditable={isEditable}
|
|
||||||
controlTitle={panelTitle || defaultPanelTitle}
|
|
||||||
/>{' '}
|
|
||||||
<EuiFormLabel
|
|
||||||
className="eui-textTruncate"
|
|
||||||
// TODO: Convert this to a class when replacing the legacy control group
|
|
||||||
css={css`
|
|
||||||
background-color: transparent !important;
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{panelTitle || defaultPanelTitle}
|
|
||||||
</EuiFormLabel>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Component
|
<Component
|
||||||
// TODO: Convert this to a class when replacing the legacy control group
|
className={classNames('controlPanel', {
|
||||||
css={css`
|
'controlPanel--roundedBorders':
|
||||||
height: calc(${euiThemeVars.euiButtonHeight} - 2px);
|
!api?.CustomPrependComponent && !isEditable && usingTwoLineLayout,
|
||||||
box-shadow: none !important;
|
'controlPanel--hideComponent': Boolean(blockingError), // don't want to unmount component on error; just hide it
|
||||||
${!isEditable && usingTwoLineLayout
|
})}
|
||||||
? `border-radius: ${euiThemeVars.euiBorderRadius} !important`
|
|
||||||
: ''};
|
|
||||||
`}
|
|
||||||
ref={(newApi) => {
|
ref={(newApi) => {
|
||||||
if (newApi && !api) setApi(newApi);
|
if (newApi && !api) setApi(newApi);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
</EuiFormControlLayout>
|
</EuiFormControlLayout>
|
||||||
)}
|
|
||||||
</EuiFormRow>
|
</EuiFormRow>
|
||||||
</FloatingActions>
|
</FloatingActions>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
|
|
@ -10,7 +10,6 @@ import React, { useImperativeHandle, useMemo } from 'react';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { v4 as generateId } from 'uuid';
|
import { v4 as generateId } from 'uuid';
|
||||||
|
|
||||||
import { SerializedStyles } from '@emotion/react';
|
|
||||||
import { StateComparators } from '@kbn/presentation-publishing';
|
import { StateComparators } from '@kbn/presentation-publishing';
|
||||||
|
|
||||||
import { getControlFactory } from './control_factory_registry';
|
import { getControlFactory } from './control_factory_registry';
|
||||||
|
@ -68,7 +67,7 @@ export const ControlRenderer = <
|
||||||
parentApi
|
parentApi
|
||||||
);
|
);
|
||||||
|
|
||||||
return React.forwardRef<typeof api, { css: SerializedStyles }>((props, ref) => {
|
return React.forwardRef<typeof api, { className: string }>((props, ref) => {
|
||||||
// expose the api into the imperative handle
|
// expose the api into the imperative handle
|
||||||
useImperativeHandle(ref, () => api, []);
|
useImperativeHandle(ref, () => api, []);
|
||||||
return <Component {...props} />;
|
return <Component {...props} />;
|
||||||
|
|
|
@ -1,25 +1,16 @@
|
||||||
.rangeSliderAnchor__button {
|
.rangeSliderAnchor__button {
|
||||||
.euiFormControlLayout {
|
.euiFormControlLayout {
|
||||||
align-items: center;
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background-color: transparent;
|
background-color: transparent !important;
|
||||||
|
|
||||||
.euiFormControlLayout__childrenWrapper {
|
|
||||||
background-color: transparent;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-top-right-radius: $euiBorderRadius;
|
|
||||||
border-bottom-right-radius: $euiBorderRadius;
|
|
||||||
|
|
||||||
.euiFormControlLayoutDelimited__delimiter, .euiFormControlLayoutIcons--static {
|
|
||||||
height: auto !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rangeSlider__invalidToken {
|
.rangeSlider__invalidToken {
|
||||||
height: $euiSizeS * 2;
|
|
||||||
padding: 0 $euiSizeS;
|
padding: 0 $euiSizeS;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.euiToolTipAnchor {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
.euiIcon {
|
.euiIcon {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
@ -32,9 +23,7 @@
|
||||||
|
|
||||||
.rangeSliderAnchor__fieldNumber {
|
.rangeSliderAnchor__fieldNumber {
|
||||||
font-weight: $euiFontWeightMedium;
|
font-weight: $euiFontWeightMedium;
|
||||||
box-shadow: none;
|
height: calc($euiButtonHeight - 3px) !important;
|
||||||
text-align: center;
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
&.rangeSliderAnchor__fieldNumber--valid:invalid:not(:focus) {
|
&.rangeSliderAnchor__fieldNumber--valid:invalid:not(:focus) {
|
||||||
background-image: none; // override the red underline for values between steps
|
background-image: none; // override the red underline for values between steps
|
||||||
|
|
|
@ -36,6 +36,7 @@ export const RangeSliderControl: FC<Props> = ({
|
||||||
step,
|
step,
|
||||||
value,
|
value,
|
||||||
uuid,
|
uuid,
|
||||||
|
...rest
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const rangeSliderRef = useRef<EuiDualRangeProps | null>(null);
|
const rangeSliderRef = useRef<EuiDualRangeProps | null>(null);
|
||||||
|
|
||||||
|
@ -178,6 +179,7 @@ export const RangeSliderControl: FC<Props> = ({
|
||||||
max={displayedMax}
|
max={displayedMax}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
inputPopoverProps={{
|
inputPopoverProps={{
|
||||||
|
...rest,
|
||||||
panelMinWidth: MIN_POPOVER_WIDTH,
|
panelMinWidth: MIN_POPOVER_WIDTH,
|
||||||
}}
|
}}
|
||||||
append={
|
append={
|
||||||
|
|
|
@ -209,7 +209,7 @@ export const getRangesliderControlFactory = (
|
||||||
|
|
||||||
return {
|
return {
|
||||||
api,
|
api,
|
||||||
Component: () => {
|
Component: (controlPanelClassNames) => {
|
||||||
const [dataLoading, dataViews, fieldName, max, min, selectionHasNotResults, step, value] =
|
const [dataLoading, dataViews, fieldName, max, min, selectionHasNotResults, step, value] =
|
||||||
useBatchedPublishingSubjects(
|
useBatchedPublishingSubjects(
|
||||||
dataLoading$,
|
dataLoading$,
|
||||||
|
@ -245,6 +245,7 @@ export const getRangesliderControlFactory = (
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RangeSliderControl
|
<RangeSliderControl
|
||||||
|
{...controlPanelClassNames}
|
||||||
fieldFormatter={fieldFormatter}
|
fieldFormatter={fieldFormatter}
|
||||||
isInvalid={selectionHasNotResults}
|
isInvalid={selectionHasNotResults}
|
||||||
isLoading={typeof dataLoading === 'boolean' ? dataLoading : false}
|
isLoading={typeof dataLoading === 'boolean' ? dataLoading : false}
|
||||||
|
|
|
@ -185,10 +185,10 @@ export const getSearchControlFactory = ({
|
||||||
return {
|
return {
|
||||||
api,
|
api,
|
||||||
/**
|
/**
|
||||||
* The `conrolStyleProps` prop is necessary because it contains the props from the generic
|
* The `controlPanelClassNamess` prop is necessary because it contains the class names from the generic
|
||||||
* ControlPanel that are necessary for styling
|
* ControlPanel that are necessary for styling
|
||||||
*/
|
*/
|
||||||
Component: (conrolStyleProps) => {
|
Component: (controlPanelClassNames) => {
|
||||||
const currentSearch = useStateFromPublishingSubject(searchString);
|
const currentSearch = useStateFromPublishingSubject(searchString);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -202,7 +202,7 @@ export const getSearchControlFactory = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFieldSearch
|
<EuiFieldSearch
|
||||||
{...conrolStyleProps}
|
{...controlPanelClassNames}
|
||||||
incremental={true}
|
incremental={true}
|
||||||
isClearable={false} // this will be handled by the clear floating action instead
|
isClearable={false} // this will be handled by the clear floating action instead
|
||||||
value={currentSearch ?? ''}
|
value={currentSearch ?? ''}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CanClearSelections } from '@kbn/controls-plugin/public';
|
|
||||||
import { DataViewField } from '@kbn/data-views-plugin/common';
|
import { DataViewField } from '@kbn/data-views-plugin/common';
|
||||||
import { Filter } from '@kbn/es-query';
|
import { Filter } from '@kbn/es-query';
|
||||||
import {
|
import {
|
||||||
|
@ -25,7 +24,6 @@ import {
|
||||||
export type DataControlApi = DefaultControlApi &
|
export type DataControlApi = DefaultControlApi &
|
||||||
Omit<PublishesPanelTitle, 'hidePanelTitle'> & // control titles cannot be hidden
|
Omit<PublishesPanelTitle, 'hidePanelTitle'> & // control titles cannot be hidden
|
||||||
HasEditCapabilities &
|
HasEditCapabilities &
|
||||||
CanClearSelections &
|
|
||||||
PublishesDataViews &
|
PublishesDataViews &
|
||||||
PublishesFilters & {
|
PublishesFilters & {
|
||||||
setOutputFilter: (filter: Filter | undefined) => void; // a control should only ever output a **single** filter
|
setOutputFilter: (filter: Filter | undefined) => void; // a control should only ever output a **single** filter
|
||||||
|
|
|
@ -1,23 +1,19 @@
|
||||||
|
|
||||||
.timeSlider__popoverOverride {
|
|
||||||
width: 100%;
|
|
||||||
max-inline-size: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeSlider-playToggle:enabled {
|
.timeSlider-playToggle:enabled {
|
||||||
background-color: $euiColorPrimary !important;
|
background-color: $euiColorPrimary !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeSlider-prependButton {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
.timeSlider__anchor {
|
.timeSlider__anchor {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@include euiFormControlSideBorderRadius($euiFormControlBorderRadius, $side: 'right', $internal: true);
|
|
||||||
|
|
||||||
.euiText {
|
.euiText {
|
||||||
background-color: $euiFormBackgroundColor !important;
|
background-color: transparent !important;
|
||||||
// background-color: transparent !important; TODO revert to this rule once control group provides background color
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
|
|
@ -6,13 +6,12 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EuiButtonIcon } from '@elastic/eui';
|
import { EuiButtonIcon, EuiFlexItem } from '@elastic/eui';
|
||||||
import React, { FC, useCallback, useState } from 'react';
|
|
||||||
import { Observable, Subscription } from 'rxjs';
|
|
||||||
import { first } from 'rxjs';
|
|
||||||
import { ViewMode } from '@kbn/presentation-publishing';
|
import { ViewMode } from '@kbn/presentation-publishing';
|
||||||
import { TimeSliderStrings } from './time_slider_strings';
|
import React, { FC, useCallback, useState } from 'react';
|
||||||
|
import { first, Observable, Subscription } from 'rxjs';
|
||||||
import { PlayButton } from './play_button';
|
import { PlayButton } from './play_button';
|
||||||
|
import { TimeSliderStrings } from './time_slider_strings';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onNext: () => void;
|
onNext: () => void;
|
||||||
|
@ -66,7 +65,8 @@ export const TimeSliderPrepend: FC<Props> = (props: Props) => {
|
||||||
}, [props, subscription, timeoutId]);
|
}, [props, subscription, timeoutId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
<EuiButtonIcon
|
<EuiButtonIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onPause();
|
onPause();
|
||||||
|
@ -74,9 +74,12 @@ export const TimeSliderPrepend: FC<Props> = (props: Props) => {
|
||||||
}}
|
}}
|
||||||
iconType="framePrevious"
|
iconType="framePrevious"
|
||||||
color="text"
|
color="text"
|
||||||
|
className={'timeSlider-prependButton'}
|
||||||
aria-label={TimeSliderStrings.control.getPreviousButtonAriaLabel()}
|
aria-label={TimeSliderStrings.control.getPreviousButtonAriaLabel()}
|
||||||
data-test-subj="timeSlider-previousTimeWindow"
|
data-test-subj="timeSlider-previousTimeWindow"
|
||||||
/>
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
<PlayButton
|
<PlayButton
|
||||||
onPlay={onPlay}
|
onPlay={onPlay}
|
||||||
onPause={onPause}
|
onPause={onPause}
|
||||||
|
@ -85,6 +88,8 @@ export const TimeSliderPrepend: FC<Props> = (props: Props) => {
|
||||||
disablePlayButton={props.disablePlayButton}
|
disablePlayButton={props.disablePlayButton}
|
||||||
isPaused={isPaused}
|
isPaused={isPaused}
|
||||||
/>
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
<EuiButtonIcon
|
<EuiButtonIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onPause();
|
onPause();
|
||||||
|
@ -92,9 +97,11 @@ export const TimeSliderPrepend: FC<Props> = (props: Props) => {
|
||||||
}}
|
}}
|
||||||
iconType="frameNext"
|
iconType="frameNext"
|
||||||
color="text"
|
color="text"
|
||||||
|
className={'timeSlider-prependButton'}
|
||||||
aria-label={TimeSliderStrings.control.getNextButtonAriaLabel()}
|
aria-label={TimeSliderStrings.control.getNextButtonAriaLabel()}
|
||||||
data-test-subj="timeSlider-nextTimeWindow"
|
data-test-subj="timeSlider-nextTimeWindow"
|
||||||
/>
|
/>
|
||||||
</div>
|
</EuiFlexItem>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -53,6 +53,14 @@ export const getTimesliderControlFactory = (
|
||||||
const { timeRangeMeta$, formatDate, cleanupTimeRangeSubscription } =
|
const { timeRangeMeta$, formatDate, cleanupTimeRangeSubscription } =
|
||||||
initTimeRangeSubscription(controlGroupApi, services);
|
initTimeRangeSubscription(controlGroupApi, services);
|
||||||
const timeslice$ = new BehaviorSubject<[number, number] | undefined>(undefined);
|
const timeslice$ = new BehaviorSubject<[number, number] | undefined>(undefined);
|
||||||
|
const isAnchored$ = new BehaviorSubject<boolean | undefined>(initialState.isAnchored);
|
||||||
|
const isPopoverOpen$ = new BehaviorSubject(false);
|
||||||
|
|
||||||
|
const timeRangePercentage = initTimeRangePercentage(
|
||||||
|
initialState,
|
||||||
|
syncTimesliceWithTimeRangePercentage
|
||||||
|
);
|
||||||
|
|
||||||
function syncTimesliceWithTimeRangePercentage(
|
function syncTimesliceWithTimeRangePercentage(
|
||||||
startPercentage: number | undefined,
|
startPercentage: number | undefined,
|
||||||
endPercentage: number | undefined
|
endPercentage: number | undefined
|
||||||
|
@ -73,18 +81,16 @@ export const getTimesliderControlFactory = (
|
||||||
]);
|
]);
|
||||||
setSelectedRange(to - from);
|
setSelectedRange(to - from);
|
||||||
}
|
}
|
||||||
const timeRangePercentage = initTimeRangePercentage(
|
|
||||||
initialState,
|
|
||||||
syncTimesliceWithTimeRangePercentage
|
|
||||||
);
|
|
||||||
function setTimeslice(timeslice?: Timeslice) {
|
function setTimeslice(timeslice?: Timeslice) {
|
||||||
timeRangePercentage.setTimeRangePercentage(timeslice, timeRangeMeta$.value);
|
timeRangePercentage.setTimeRangePercentage(timeslice, timeRangeMeta$.value);
|
||||||
timeslice$.next(timeslice);
|
timeslice$.next(timeslice);
|
||||||
}
|
}
|
||||||
const isAnchored$ = new BehaviorSubject<boolean | undefined>(initialState.isAnchored);
|
|
||||||
function setIsAnchored(isAnchored: boolean | undefined) {
|
function setIsAnchored(isAnchored: boolean | undefined) {
|
||||||
isAnchored$.next(isAnchored);
|
isAnchored$.next(isAnchored);
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectedRange: number | undefined;
|
let selectedRange: number | undefined;
|
||||||
function setSelectedRange(nextSelectedRange?: number) {
|
function setSelectedRange(nextSelectedRange?: number) {
|
||||||
selectedRange =
|
selectedRange =
|
||||||
|
@ -176,10 +182,6 @@ export const getTimesliderControlFactory = (
|
||||||
setTimeslice([from, Math.min(to, timeRangeMax)]);
|
setTimeslice([from, Math.min(to, timeRangeMax)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPopoverOpen$ = new BehaviorSubject(false);
|
|
||||||
function setIsPopoverOpen(value: boolean) {
|
|
||||||
isPopoverOpen$.next(value);
|
|
||||||
}
|
|
||||||
const viewModeSubject =
|
const viewModeSubject =
|
||||||
getViewModeSubject(controlGroupApi) ?? new BehaviorSubject('view' as ViewMode);
|
getViewModeSubject(controlGroupApi) ?? new BehaviorSubject('view' as ViewMode);
|
||||||
|
|
||||||
|
@ -217,6 +219,9 @@ export const getTimesliderControlFactory = (
|
||||||
references: [],
|
references: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
clearSelections: () => {
|
||||||
|
setTimeslice(undefined);
|
||||||
|
},
|
||||||
CustomPrependComponent: () => {
|
CustomPrependComponent: () => {
|
||||||
const [autoApplySelections, viewMode] = useBatchedPublishingSubjects(
|
const [autoApplySelections, viewMode] = useBatchedPublishingSubjects(
|
||||||
controlGroupApi.autoApplySelections$,
|
controlGroupApi.autoApplySelections$,
|
||||||
|
@ -229,7 +234,7 @@ export const getTimesliderControlFactory = (
|
||||||
onPrevious={onPrevious}
|
onPrevious={onPrevious}
|
||||||
viewMode={viewMode}
|
viewMode={viewMode}
|
||||||
disablePlayButton={!autoApplySelections}
|
disablePlayButton={!autoApplySelections}
|
||||||
setIsPopoverOpen={setIsPopoverOpen}
|
setIsPopoverOpen={(value) => isPopoverOpen$.next(value)}
|
||||||
waitForControlOutputConsumersToLoad$={waitForDashboardPanelsToLoad$}
|
waitForControlOutputConsumersToLoad$={waitForDashboardPanelsToLoad$}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -253,7 +258,7 @@ export const getTimesliderControlFactory = (
|
||||||
|
|
||||||
return {
|
return {
|
||||||
api,
|
api,
|
||||||
Component: (controlStyleProps) => {
|
Component: (controlPanelClassNames) => {
|
||||||
const [isAnchored, isPopoverOpen, timeRangeMeta, timeslice] =
|
const [isAnchored, isPopoverOpen, timeRangeMeta, timeslice] =
|
||||||
useBatchedPublishingSubjects(isAnchored$, isPopoverOpen$, timeRangeMeta$, timeslice$);
|
useBatchedPublishingSubjects(isAnchored$, isPopoverOpen$, timeRangeMeta$, timeslice$);
|
||||||
|
|
||||||
|
@ -273,13 +278,12 @@ export const getTimesliderControlFactory = (
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiInputPopover
|
<EuiInputPopover
|
||||||
{...controlStyleProps}
|
{...controlPanelClassNames}
|
||||||
className="timeSlider__popoverOverride"
|
|
||||||
panelClassName="timeSlider__panelOverride"
|
panelClassName="timeSlider__panelOverride"
|
||||||
input={
|
input={
|
||||||
<TimeSliderPopoverButton
|
<TimeSliderPopoverButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsPopoverOpen(!isPopoverOpen);
|
isPopoverOpen$.next(!isPopoverOpen);
|
||||||
}}
|
}}
|
||||||
formatDate={formatDate}
|
formatDate={formatDate}
|
||||||
from={from}
|
from={from}
|
||||||
|
@ -287,7 +291,7 @@ export const getTimesliderControlFactory = (
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
isOpen={isPopoverOpen}
|
isOpen={isPopoverOpen}
|
||||||
closePopover={() => setIsPopoverOpen(false)}
|
closePopover={() => isPopoverOpen$.next(false)}
|
||||||
panelPaddingSize="s"
|
panelPaddingSize="s"
|
||||||
>
|
>
|
||||||
<TimeSliderPopoverContent
|
<TimeSliderPopoverContent
|
||||||
|
|
|
@ -8,8 +8,7 @@
|
||||||
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { SerializedStyles } from '@emotion/react';
|
import { CanClearSelections, ControlWidth } from '@kbn/controls-plugin/public/types';
|
||||||
import { ControlWidth } from '@kbn/controls-plugin/public/types';
|
|
||||||
import { HasSerializableState } from '@kbn/presentation-containers';
|
import { HasSerializableState } from '@kbn/presentation-containers';
|
||||||
import { PanelCompatibleComponent } from '@kbn/presentation-panel-plugin/public/panel_component/types';
|
import { PanelCompatibleComponent } from '@kbn/presentation-panel-plugin/public/panel_component/types';
|
||||||
import {
|
import {
|
||||||
|
@ -41,10 +40,12 @@ export type DefaultControlApi = PublishesDataLoading &
|
||||||
PublishesUnsavedChanges &
|
PublishesUnsavedChanges &
|
||||||
PublishesControlDisplaySettings &
|
PublishesControlDisplaySettings &
|
||||||
Partial<PublishesPanelTitle & PublishesDisabledActionIds & HasCustomPrepend> &
|
Partial<PublishesPanelTitle & PublishesDisabledActionIds & HasCustomPrepend> &
|
||||||
|
CanClearSelections &
|
||||||
HasType &
|
HasType &
|
||||||
HasUniqueId &
|
HasUniqueId &
|
||||||
HasSerializableState &
|
HasSerializableState &
|
||||||
HasParentApi<ControlGroupApi> & {
|
HasParentApi<ControlGroupApi> & {
|
||||||
|
/** TODO: Make these non-public as part of https://github.com/elastic/kibana/issues/174961 */
|
||||||
setDataLoading: (loading: boolean) => void;
|
setDataLoading: (loading: boolean) => void;
|
||||||
setBlockingError: (error: Error | undefined) => void;
|
setBlockingError: (error: Error | undefined) => void;
|
||||||
};
|
};
|
||||||
|
@ -90,7 +91,7 @@ export type ControlStateManager<State extends object = object> = {
|
||||||
|
|
||||||
export interface ControlPanelProps<
|
export interface ControlPanelProps<
|
||||||
ApiType extends DefaultControlApi = DefaultControlApi,
|
ApiType extends DefaultControlApi = DefaultControlApi,
|
||||||
PropsType extends {} = { css: SerializedStyles }
|
PropsType extends {} = { className: string }
|
||||||
> {
|
> {
|
||||||
Component: PanelCompatibleComponent<ApiType, PropsType>;
|
Component: PanelCompatibleComponent<ApiType, PropsType>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
"@kbn/react-kibana-mount",
|
"@kbn/react-kibana-mount",
|
||||||
"@kbn/content-management-utils",
|
"@kbn/content-management-utils",
|
||||||
"@kbn/presentation-util-plugin",
|
"@kbn/presentation-util-plugin",
|
||||||
"@kbn/ui-theme",
|
|
||||||
"@kbn/core-lifecycle-browser",
|
"@kbn/core-lifecycle-browser",
|
||||||
"@kbn/presentation-panel-plugin",
|
"@kbn/presentation-panel-plugin",
|
||||||
"@kbn/datemath",
|
"@kbn/datemath",
|
||||||
|
|
|
@ -13,7 +13,7 @@ import type { DataControlInput } from '../types';
|
||||||
import { OptionsListSearchTechnique } from './suggestions_searching';
|
import { OptionsListSearchTechnique } from './suggestions_searching';
|
||||||
import type { OptionsListSortingType } from './suggestions_sorting';
|
import type { OptionsListSortingType } from './suggestions_sorting';
|
||||||
|
|
||||||
export const OPTIONS_LIST_CONTROL = 'optionsListControl';
|
export const OPTIONS_LIST_CONTROL = 'optionsListControl'; // TODO: Replace with OPTIONS_LIST_CONTROL_TYPE
|
||||||
|
|
||||||
export interface OptionsListEmbeddableInput extends DataControlInput {
|
export interface OptionsListEmbeddableInput extends DataControlInput {
|
||||||
searchTechnique?: OptionsListSearchTechnique;
|
searchTechnique?: OptionsListSearchTechnique;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue