1
1
import { css } from '@emotion/css' ;
2
2
3
- import React , { createContext } from 'react' ;
3
+ import React , { createContext , useRef } from 'react' ;
4
4
import { debounceTime , throttleTime } from 'rxjs' ;
5
5
import { useObservableCallback , useSubscription } from 'observable-hooks'
6
6
7
+ import { useEventListener } from 'usehooks-ts'
8
+
7
9
import { CoreApp , Field , getDefaultTimeRange , GrafanaTheme2 , QueryEditorProps } from '@grafana/data' ;
8
10
import { InlineLabel , useStyles2 } from '@grafana/ui' ;
9
11
@@ -84,6 +86,15 @@ export const ElasticSearchQueryField = ({ value, onChange, onSubmit }: ElasticSe
84
86
} ;
85
87
86
88
const 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 )
87
98
88
99
const dispatch = useDispatch ( ) ;
89
100
const nextId = useNextId ( ) ;
@@ -108,8 +119,8 @@ const QueryEditorForm = ({ value, onRunQuery }: Props) => {
108
119
useSubscription ( submitted$ , onSubmit )
109
120
110
121
return (
111
- < >
112
- < div className = { styles . root } >
122
+ < div ref = { editorRef } >
123
+ < div className = { styles . root } >
113
124
< InlineLabel width = { 17 } > Query type</ InlineLabel >
114
125
< div className = { styles . queryItem } >
115
126
< QueryTypeSelector />
@@ -125,6 +136,6 @@ const QueryEditorForm = ({ value, onRunQuery }: Props) => {
125
136
126
137
< MetricAggregationsEditor nextId = { nextId } />
127
138
{ showBucketAggregationsEditor && < BucketAggregationsEditor nextId = { nextId } /> }
128
- </ >
139
+ </ div >
129
140
) ;
130
141
} ;
0 commit comments