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,15 @@ export const ElasticSearchQueryField = ({ value, onChange, onSubmit }: ElasticSe
8486} ;
8587
8688const QueryEditorForm = ( { value, onRunQuery } : Props ) => {
89+ const editorRef = useRef < HTMLDivElement > ( null )
90+ const handleKeyBindings = ( e : KeyboardEvent ) => {
91+ // Shift+Enter triggers onRunQuery if the active element is inside the editor
92+ if ( e . key === "Enter" && e . shiftKey && editorRef . current ?. contains ( document . activeElement ) ) {
93+ onRunQuery ( )
94+ }
95+ e . stopPropagation ( ) ;
96+ }
97+ useEventListener ( "keypress" , handleKeyBindings )
8798
8899 const dispatch = useDispatch ( ) ;
89100 const nextId = useNextId ( ) ;
@@ -108,8 +119,8 @@ const QueryEditorForm = ({ value, onRunQuery }: Props) => {
108119 useSubscription ( submitted$ , onSubmit )
109120
110121 return (
111- < >
112- < div className = { styles . root } >
122+ < div ref = { editorRef } >
123+ < div className = { styles . root } >
113124 < InlineLabel width = { 17 } > Query type</ InlineLabel >
114125 < div className = { styles . queryItem } >
115126 < QueryTypeSelector />
@@ -125,6 +136,6 @@ const QueryEditorForm = ({ value, onRunQuery }: Props) => {
125136
126137 < MetricAggregationsEditor nextId = { nextId } />
127138 { showBucketAggregationsEditor && < BucketAggregationsEditor nextId = { nextId } /> }
128- </ >
139+ </ div >
129140 ) ;
130141} ;
0 commit comments