From 09a599900c658f0d0bb9d9471f20a6eee27a2760 Mon Sep 17 00:00:00 2001 From: Andrej Ocenas Date: Wed, 16 Oct 2019 09:57:51 +0200 Subject: [PATCH] DataLinks: Fix url field not releasing focus (#19804) --- .../components/DataLinks/DataLinkInput.tsx | 9 ++-- packages/grafana-ui/src/types/panel.ts | 6 ++- .../panel_editor/VisualizationTab.tsx | 4 +- .../plugins/panel/gauge/GaugePanelEditor.tsx | 45 ++++++++++++------- 4 files changed, 43 insertions(+), 21 deletions(-) diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx index 61d2822cba744..932ec26b394e2 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo, useContext, useRef, RefObject } from 'react'; +import React, { useState, useMemo, useContext, useRef, RefObject, memo } from 'react'; import { VariableSuggestion, VariableOrigin, DataLinkSuggestions } from './DataLinkSuggestions'; import { ThemeContext, DataLinkBuiltInVars, makeValue } from '../../index'; import { SelectionReference } from './SelectionReference'; @@ -41,7 +41,9 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({ `, })); -export const DataLinkInput: React.FC = ({ value, onChange, suggestions }) => { +// This memoised also because rerendering the slate editor grabs focus which created problem in some cases this +// was used and changes to different state were propagated here. +export const DataLinkInput: React.FC = memo(({ value, onChange, suggestions }) => { const editorRef = useRef() as RefObject; const theme = useContext(ThemeContext); const styles = getStyles(theme); @@ -91,6 +93,7 @@ export const DataLinkInput: React.FC = ({ value, onChange, s const onUrlBlur = React.useCallback((event: Event, editor: CoreEditor, next: () => any) => { // Callback needed for blur to work correctly stateRef.current.onChange(Plain.serialize(stateRef.current.linkUrl), () => { + // This needs to be called after state is updated. editorRef.current!.blur(); }); }, []); @@ -161,6 +164,6 @@ export const DataLinkInput: React.FC = ({ value, onChange, s ); -}; +}); DataLinkInput.displayName = 'DataLinkInput'; diff --git a/packages/grafana-ui/src/types/panel.ts b/packages/grafana-ui/src/types/panel.ts index 618bc3d8dae1f..6be690bb7a31f 100644 --- a/packages/grafana-ui/src/types/panel.ts +++ b/packages/grafana-ui/src/types/panel.ts @@ -37,7 +37,11 @@ export interface PanelProps { export interface PanelEditorProps { options: T; - onOptionsChange: (options: T) => void; + onOptionsChange: ( + options: T, + // callback can be used to run something right after update. + callback?: () => void + ) => void; data: PanelData; } diff --git a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx index 691d34f4cbcaa..470df708e0c42 100644 --- a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx +++ b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx @@ -165,9 +165,9 @@ export class VisualizationTab extends PureComponent { this.setState({ searchQuery: '' }); }; - onPanelOptionsChanged = (options: any) => { + onPanelOptionsChanged = (options: any, callback?: () => void) => { this.props.panel.updateOptions(options); - this.forceUpdate(); + this.forceUpdate(callback); }; onOpenVizPicker = () => { diff --git a/public/app/plugins/panel/gauge/GaugePanelEditor.tsx b/public/app/plugins/panel/gauge/GaugePanelEditor.tsx index c98bbc2a7da7b..7e7f53b6b530c 100644 --- a/public/app/plugins/panel/gauge/GaugePanelEditor.tsx +++ b/public/app/plugins/panel/gauge/GaugePanelEditor.tsx @@ -48,24 +48,39 @@ export class GaugePanelEditor extends PureComponent - this.props.onOptionsChange({ - ...this.props.options, - fieldOptions, - }); + onDisplayOptionsChanged = ( + fieldOptions: FieldDisplayOptions, + event?: React.SyntheticEvent, + callback?: () => void + ) => + this.props.onOptionsChange( + { + ...this.props.options, + fieldOptions, + }, + callback + ); - onDefaultsChange = (field: FieldConfig) => { - this.onDisplayOptionsChanged({ - ...this.props.options.fieldOptions, - defaults: field, - }); + onDefaultsChange = (field: FieldConfig, event?: React.SyntheticEvent, callback?: () => void) => { + this.onDisplayOptionsChanged( + { + ...this.props.options.fieldOptions, + defaults: field, + }, + event, + callback + ); }; - onDataLinksChanged = (links: DataLink[]) => { - this.onDefaultsChange({ - ...this.props.options.fieldOptions.defaults, - links, - }); + onDataLinksChanged = (links: DataLink[], callback?: () => void) => { + this.onDefaultsChange( + { + ...this.props.options.fieldOptions.defaults, + links, + }, + undefined, + callback + ); }; render() {