11/*
22 * CloudBeaver - Cloud Database Manager
3- * Copyright (C) 2020-2025 DBeaver Corp and others
3+ * Copyright (C) 2020-2026 DBeaver Corp and others
44 *
55 * Licensed under the Apache License, Version 2.0.
66 * you may not use this file except in compliance with the License.
77 */
88import { observer } from 'mobx-react-lite' ;
99import { useContext } from 'react' ;
1010
11- import { s , useFocus , useHotkeys , useS } from '@cloudbeaver/core-blocks' ;
11+ import { s , useFocus , useHotkeys , useMergeRefs , useS } from '@cloudbeaver/core-blocks' ;
1212import { useService } from '@cloudbeaver/core-di' ;
13+ import { EventContext , EventStopPropagationFlag } from '@cloudbeaver/core-events' ;
1314import { isObjectsEqual } from '@cloudbeaver/core-utils' ;
1415
1516import { ActionService } from '../Action/ActionService.js' ;
@@ -32,24 +33,26 @@ export const CaptureView = observer<React.PropsWithChildren<ICaptureViewProps>>(
3233 const viewContext = useViewContext ( view , parentContext ) ;
3334 const actionService = useService ( ActionService ) ;
3435 const activeView = useActiveView ( view ) ;
35- const [ ref , state ] = useFocus < HTMLDivElement > ( { onFocus : activeView . focusView , onBlur : activeView . blurView } ) ;
36+ const [ ref ] = useFocus < HTMLDivElement > ( { onFocus : activeView . focusView , onBlur : activeView . blurView } ) ;
3637 const style = useS ( styles ) ;
3738
38- const actionItems = view . actions
39- . map ( action => actionService . getAction ( viewContext , action ) )
40- . filter ( action => action ?. binding && ! action . isDisabled ( ) )
41- . filter ( Boolean ) as IActionItem [ ] ;
39+ const allActionItems = view . actions . map ( action => actionService . getAction ( viewContext , action ) ) . filter ( Boolean ) as IActionItem [ ] ;
40+ const enabledActionItems = allActionItems . filter ( action => action ?. binding && ! action . isDisabled ( ) ) . filter ( Boolean ) as IActionItem [ ] ;
4241
43- const keys = actionItems . map ( item => getCommonAndOSSpecificKeys ( item . binding ?. binding ) ) . flat ( ) ;
42+ const allKeys = allActionItems . map ( item => getCommonAndOSSpecificKeys ( item . binding ?. binding ) ) . flat ( ) ;
4443
45- useHotkeys (
46- keys ,
44+ const divRef = useHotkeys (
45+ allKeys ,
4746 ( event , handler ) => {
48- if ( ! event . isTrusted || ! state . reference ?. contains ( document . activeElement ) ) {
47+ /**
48+ * isTrusted - to prevent double handling of the event
49+ * EventContext.has - to prevent handling the event if it was already handled by a child view
50+ */
51+ if ( ! event . isTrusted || EventContext . has ( event , EventStopPropagationFlag ) ) {
4952 return ;
5053 }
5154
52- const action = actionItems . find ( action => {
55+ const action = enabledActionItems . find ( action => {
5356 const commonAndSpecificKeys = getCommonAndOSSpecificKeys ( action . binding ?. binding ) ;
5457 return commonAndSpecificKeys . some ( key => {
5558 const hotkey = parseHotkey ( key ) ;
@@ -58,31 +61,38 @@ export const CaptureView = observer<React.PropsWithChildren<ICaptureViewProps>>(
5861 } ) ;
5962 } ) ;
6063
64+ EventContext . set ( event , EventStopPropagationFlag ) ;
6165 action ?. activate ( true ) ;
6266 } ,
6367 {
64- enabled : keys . length > 0 ,
68+ enabled : allKeys . length > 0 ,
6569 enableOnFormTags : [ 'INPUT' , 'SELECT' , 'TEXTAREA' ] ,
6670 preventDefault ( event , handler ) {
67- const action = actionItems . find ( action => {
71+ // Don't prevent default if event was already handled by a child view
72+ if ( EventContext . has ( event , EventStopPropagationFlag ) ) {
73+ return false ;
74+ }
75+
76+ const action = enabledActionItems . find ( action => {
6877 const commonAndSpecificKeys = getCommonAndOSSpecificKeys ( action . binding ?. binding ) ;
6978 return commonAndSpecificKeys . some ( key => {
7079 const hotkey = parseHotkey ( key ) ;
7180
7281 return isObjectsEqual ( hotkey , handler ) ;
7382 } ) ;
7483 } ) ;
75- const isAppliedInSelectedScope = Boolean ( state . reference ?. contains ( document . activeElement ) ) ;
7684
77- return isAppliedInSelectedScope && action ?. binding ?. binding . preventDefault === true ;
85+ return action ?. binding ?. binding . preventDefault === true ;
7886 } ,
7987 enableOnContentEditable : true ,
8088 } ,
8189 ) ;
8290
91+ const mergedRef = useMergeRefs ( ref , divRef ) ;
92+
8393 return (
8494 < CaptureViewContext . Provider value = { viewContext } >
85- < div ref = { ref } className = { s ( style , { container : true } , className ) } tabIndex = { 0 } >
95+ < div ref = { mergedRef } className = { s ( style , { container : true } , className ) } tabIndex = { 0 } >
8696 { children }
8797 </ div >
8898 </ CaptureViewContext . Provider >
0 commit comments