dbeaver/pro#6018 fix: context menu disappears on cells with an intera… (#3538)

* dbeaver/pro#6018 fix: context menu disappears on cells with an interactive formatter

add menu visibility handling to cell formatters

* dbeaver/pro#6018 refactor: rename handleStateSwitch to handleCellMenuStateSwitch for clarity

* dbeaver/pro#6018 refactor: rename menu visibility properties for clarity

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
Sychev Andrey
2025-06-24 17:57:35 +08:00
committed by GitHub
co-authored by Evgenia
parent beecc84d14
commit 646ec5c901
5 changed files with 31 additions and 11 deletions
@@ -4,11 +4,12 @@ import { Checkbox, clsx, Focusable } from '@dbeaver/ui-kit';
interface Props {
value: boolean | null;
className?: string;
focusable?: boolean;
onClick?: React.MouseEventHandler<HTMLElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLElement>;
}
export function BooleanFormatter({ value, className, onClick, onKeyDown }: Props) {
export function BooleanFormatter({ value, className, onClick, onKeyDown, focusable }: Props) {
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
if (event.code === 'Enter' || event.code === 'Space') {
event.preventDefault();
@@ -17,6 +18,7 @@ export function BooleanFormatter({ value, className, onClick, onKeyDown }: Props
};
return (
<Focusable
focusable={focusable}
className={clsx('tw:flex tw:items-center tw:outline-none tw:hover:cursor-pointer', className)}
onKeyDown={handleKeyDown}
onClick={onClick}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@ export interface ICellContext {
cell: IResultSetElementKey | undefined;
position: ICellPosition;
editionState: DatabaseEditChangeType | null;
isMenuVisible: boolean;
setMenuVisibility(visible: boolean): void;
}
export const CellContext = createContext<ICellContext>(undefined as any);
@@ -5,7 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { computed, observable } from 'mobx';
import { computed, observable, action } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useContext, type HTMLAttributes } from 'react';
@@ -35,6 +35,7 @@ export const CellRenderer = observer<Props>(function CellRenderer({ rowIdx, colI
const cellContext = useObservableRef(
() => ({
isHovered: false,
isMenuVisible: false,
get position(): ICellPosition {
return { colIdx: this.colIdx, rowIdx: this.rowIdx };
},
@@ -70,9 +71,14 @@ export const CellRenderer = observer<Props>(function CellRenderer({ rowIdx, colI
return this.tableDataContext.getEditionState(this.cell);
},
setMenuVisibility(visibility: boolean): void {
this.isMenuVisible = visibility;
},
}),
{
isHovered: observable.ref,
isMenuVisible: observable.ref,
setMenuVisibility: action,
colIdx: observable.ref,
rowIdx: observable.ref,
row: computed,
@@ -1,12 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { use, useContext, useDeferredValue, useState } from 'react';
import { use, useContext } from 'react';
import { DataGridCellInnerContext } from '@cloudbeaver/plugin-data-grid';
import { getComputed, s, useObjectRef, useS } from '@cloudbeaver/core-blocks';
@@ -29,11 +29,11 @@ export const CellFormatter = observer<Props>(function CellFormatter({ rowIdx, co
const tableDataContext = useContext(TableDataContext);
const innerCellContext = use(DataGridCellInnerContext);
const cellContext = useContext(CellContext);
const [menuVisible, setMenuVisible] = useState(false);
const cell = cellContext.cell;
const showCellMenu = getComputed(() => !!cell && (innerCellContext?.isFocused || cellContext.isFocused || cellContext.isHovered || menuVisible));
const showCellMenuDeferred = useDeferredValue(showCellMenu, showCellMenu);
const showCellMenu = getComputed(
() => !!cell && (innerCellContext?.isFocused || cellContext.isFocused || cellContext.isHovered || cellContext.isMenuVisible),
);
const styles = useS(style);
const spreadsheetActions = useObjectRef<IDataPresentationActions<IResultSetElementKey>>({
@@ -47,12 +47,16 @@ export const CellFormatter = observer<Props>(function CellFormatter({ rowIdx, co
},
});
function handleCellMenuStateSwitch(visible: boolean): void {
cellContext.setMenuVisibility(visible);
}
return (
<div className={s(styles, { wrapper: true })}>
<div className={s(styles, { container: true })}>
<CellFormatterFactory rowIdx={rowIdx} colIdx={colIdx} />
</div>
{showCellMenuDeferred && (
{showCellMenu && (
<div className={s(styles, { menuContainer: true })}>
<CellMenu
cellKey={cell!}
@@ -61,7 +65,7 @@ export const CellFormatter = observer<Props>(function CellFormatter({ rowIdx, co
spreadsheetActions={spreadsheetActions}
resultIndex={context.resultIndex}
simple={context.simple}
onStateSwitch={setMenuVisible}
onStateSwitch={handleCellMenuStateSwitch}
/>
</div>
)}
@@ -53,6 +53,12 @@ export const BooleanFormatter = observer<ICellFormatterProps>(function BooleanFo
}
return (
<GridBooleanFormatter className={s(style, { formatter: true })} value={value as boolean | null} onClick={toggleValue} onKeyDown={toggleValue} />
<GridBooleanFormatter
focusable={!cellContext.isMenuVisible}
className={s(style, { formatter: true })}
value={value as boolean | null}
onClick={toggleValue}
onKeyDown={toggleValue}
/>
);
});