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,17 @@ export const ElasticSearchQueryField = ({ value, onChange, onSubmit }: ElasticSe
84
86
} ;
85
87
86
88
const 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
+
87
100
const dispatch = useDispatch ( ) ;
88
101
const nextId = useNextId ( ) ;
89
102
const styles = useStyles2 ( getStyles ) ;
@@ -107,8 +120,8 @@ const QueryEditorForm = ({ value, onRunQuery }: Props) => {
107
120
useSubscription ( submitted$ , onSubmit )
108
121
109
122
return (
110
- < >
111
- < div className = { styles . root } >
123
+ < div ref = { editorRef } >
124
+ < div className = { styles . root } >
112
125
< InlineLabel width = { 17 } > Query type</ InlineLabel >
113
126
< div className = { styles . queryItem } >
114
127
< QueryTypeSelector />
@@ -124,6 +137,6 @@ const QueryEditorForm = ({ value, onRunQuery }: Props) => {
124
137
125
138
< MetricAggregationsEditor nextId = { nextId } />
126
139
{ showBucketAggregationsEditor && < BucketAggregationsEditor nextId = { nextId } /> }
127
- </ >
140
+ </ div >
128
141
) ;
129
142
} ;
0 commit comments