11import { css } from '@emotion/css' ;
22
3- import React , { createContext } from 'react' ;
3+ import React , { createContext , useRef } from 'react' ;
44import { debounceTime , throttleTime } from 'rxjs' ;
55import { useObservableCallback , useSubscription } from 'observable-hooks'
66
7+ import { useEventListener } from 'usehooks-ts'
8+
79import { CoreApp , Field , getDefaultTimeRange , GrafanaTheme2 , QueryEditorProps } from '@grafana/data' ;
810import { InlineLabel , useStyles2 } from '@grafana/ui' ;
911
@@ -84,6 +86,17 @@ export const ElasticSearchQueryField = ({ value, onChange, onSubmit }: ElasticSe
8486} ;
8587
8688const QueryEditorForm = ( { value, onRunQuery } : Props ) => {
89+
90+ const editorRef = useRef < HTMLDivElement > ( null )
91+ const handleKeyBindings = ( e : KeyboardEvent ) => {
92+ // Shift+Enter triggers onRunQuery if the active element is inside the editor
93+ if ( e . key === "Enter" && e . shiftKey && editorRef . current ?. contains ( document . activeElement ) ) {
94+ onRunQuery ( )
95+ }
96+ e . stopPropagation ( ) ;
97+ }
98+ useEventListener ( "keypress" , handleKeyBindings )
99+
87100 const dispatch = useDispatch ( ) ;
88101 const nextId = useNextId ( ) ;
89102 const styles = useStyles2 ( getStyles ) ;
@@ -107,8 +120,8 @@ const QueryEditorForm = ({ value, onRunQuery }: Props) => {
107120 useSubscription ( submitted$ , onSubmit )
108121
109122 return (
110- < >
111- < div className = { styles . root } >
123+ < div ref = { editorRef } >
124+ < div className = { styles . root } >
112125 < InlineLabel width = { 17 } > Query type</ InlineLabel >
113126 < div className = { styles . queryItem } >
114127 < QueryTypeSelector />
@@ -124,6 +137,6 @@ const QueryEditorForm = ({ value, onRunQuery }: Props) => {
124137
125138 < MetricAggregationsEditor nextId = { nextId } />
126139 { showBucketAggregationsEditor && < BucketAggregationsEditor nextId = { nextId } /> }
127- </ >
140+ </ div >
128141 ) ;
129142} ;
0 commit comments