Skip to content

[CB] Render data viewer context menu on the root level - #4363

Merged
devnaumov merged 40 commits into
develfrom
9308-cb-render-data-viewer-context-menu-on-the-root-level
Jun 9, 2026
Merged

[CB] Render data viewer context menu on the root level#4363
devnaumov merged 40 commits into
develfrom
9308-cb-render-data-viewer-context-menu-on-the-root-level

Conversation

@sergeyteleshev

Copy link
Copy Markdown
Contributor

@codacy-production

codacy-production Bot commented May 26, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 23 complexity

Metric Results
Complexity 23

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Comment thread webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx Outdated
Comment thread webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR moves the data viewer context menu rendering to the DataGrid root level so the menu can be positioned globally (rather than per-cell) and re-open reliably on a single right-click (fixing issue #9308).

Changes:

  • Introduces a grid-level TableMenuContext + DataGridMenuContextProvider to manage active cell + menu coordinates.
  • Replaces the per-cell embedded context menu with a lightweight trigger button and a single root-level CellMenu.
  • Adds a new useViewLeave hook to close the menu when the originating cell leaves the viewport, and adds a right-click mousedown propagation blocker to avoid premature dismiss.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Menu/DataGridMenuContextProvider.tsx Adds grid-level provider to centralize context-menu state and rendering.
webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Menu/CellMenu.tsx Implements a single root-level context menu bound to the active cell via context.
webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Menu/CellMenu.module.css Hides the menu anchor element since menu is positioned via coordinates.
webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatter.tsx Replaces embedded menu with a trigger button that opens the root-level menu.
webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatter.module.css Styles the new menu trigger button.
webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx Wraps the grid in the new menu provider and blocks right-click dismiss behavior.
webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/TableMenuContext.ts Introduces shared menu state contract for cell/menu integration.
webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx Routes menu open/close through the shared context and closes on viewport leave.
webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellContext.ts Extends setMenuVisibility to accept an optional position.
webapp/packages/core-blocks/src/useViewLeave.ts Adds a new hook to detect when an element leaves the viewport.
webapp/packages/core-blocks/src/index.ts Re-exports useViewLeave.
webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.tsx Removes the old per-cell menu implementation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread webapp/packages/core-blocks/src/useViewLeave.ts Outdated
menuPosition,
openMenu(cellKey: IGridDataKey, x: number, y: number) {
this.activeCellKey = cellKey;
this.menuPosition.position = { x, y };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use menuPosition.open(event)

editionState: DatabaseEditChangeType | null;
isMenuVisible: boolean;
setMenuVisibility(visible: boolean): void;
setMenuVisibility(visible: boolean, position?: { x: number; y: number }): void;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This api should be reworked. There should be openMenu handler that takes event and cell id. This handler should call useContextMenuPosition. Because position is only needed when you open menu and you dont need it when close it

cellContext.setMenuVisibility(visible);
function handleMenuTriggerClick(event: React.MouseEvent<HTMLButtonElement>) {
const rect = event.currentTarget.getBoundingClientRect();
cellContext.setMenuVisibility(true, { x: rect.right - 20, y: rect.bottom });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cell formatter should not make any calculactions with coordinates. openMenu(event) should

focusable={!cellContext.isMenuVisible}
className={s(style, { formatter: true })}
value={cellHolder.value as boolean | null}
focusable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still should have the way to check whether menu is opened, please keep old behaviour

onStateSwitch={handleCellMenuStateSwitch}
/>
</div>
<button

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please never user native buttons, use our components from ariakit


const { activeCellKey, menuPosition } = tableMenuContext;

useEffect(() => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this logic should not be inside cellMenu, it should be on MenuContextProvider level


function syncFocus(data: DatabaseDataSelectActionsData<Partial<IGridDataKey>>) {
if (data.type === 'focus') {
const colIdx = data.key?.column ? tableData.getColumnIndexFromColumnKey(data.key.column) : -1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not modify this method

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you sure? we are going to loose this behavior that we can re-open on right click the new cell (it was before so this is gonna be a regression). the thing is this focus sync steals the focus which causes the bug

Screen.Recording.2026-05-27.at.14.59.13.mov

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this focus is weird. i dont like its defered behavior with setTimeout
something went wrong when we have added it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdym by something went wrong? Is something works incorrectly? As I remember we always had some async logic but in the different place

import type { IGridDataKey } from '@cloudbeaver/plugin-data-viewer';

export interface ITableMenuContext {
activeCellKey: IGridDataKey | null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need activeCellKey? we already have cell and position inside cellContext, we should use them?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we moved menu outside of cell context so we need to track active cell id to pass it here:

useDataContextLink(menu.context, (context, id) => {
context.set(DATA_CONTEXT_DV_DDM, dataGridContext.model, id);
context.set(DATA_CONTEXT_DV_DDM_RESULT_INDEX, dataGridContext.resultIndex, id);
context.set(DATA_CONTEXT_DV_SIMPLE, dataGridContext.simple, id);
context.set(DATA_CONTEXT_DV_ACTIONS, dataGridContext.actions, id);
context.set(DATA_CONTEXT_DV_PRESENTATION_ACTIONS, spreadsheetActions, id);
context.set(DATA_CONTEXT_DV_RESULT_KEY, tableMenuContext.activeCell, id);
});

import type { IContextMenuPosition } from '@cloudbeaver/core-blocks';
import type { IGridDataKey } from '@cloudbeaver/plugin-data-viewer';

export interface ITableMenuContext {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably dont need this. You have all 3 fields returned from useContextMenuPosition hook already that lets you open and close menu.

@sergeyteleshev sergeyteleshev May 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to track active cell key and is opened status

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can use Popover component from aria-kit and PopoverProvider?

Then we could use PopoverAnchor in CellFormatter that will position our menu positioned relative to the anchor element

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.


this.isMenuVisible = visibility;
openMenu(event: React.MouseEvent): void {
this.tableMenuContext.openMenu(this.cell, event);
Comment on lines +81 to +85
context.set(DATA_CONTEXT_DV_SIMPLE, dataGridContext.simple, id);
context.set(DATA_CONTEXT_DV_ACTIONS, dataGridContext.actions, id);
context.set(DATA_CONTEXT_DV_PRESENTATION_ACTIONS, spreadsheetActions, id);
context.set(DATA_CONTEXT_DV_RESULT_KEY, tableMenuContext.activeCell, id);
});
};
this.openAt(x, y);
},
openAt(x: number, y: number) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

openAt is not needed anymore

@sergeyteleshev
sergeyteleshev requested a review from devnaumov June 3, 2026 13:43
SychevAndrey
SychevAndrey previously approved these changes Jun 4, 2026
Comment thread webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Menu/useDataGridMenu.ts Outdated
['getAnchorRect', 'handleVisibleSwitch'],
);

const showAtPosition = !!contextMenuPosition?.position;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please explain why do we need such changes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case:

  1. open menu via right click
  2. do not close it
  3. open another menu in another cell via rightclick

expected behavior: menu reopens using new position
current behavior without changes: menu stays on the same position, only items of the menu changes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it changes position only if in the tree above react rerendered something. then its gonna rerender and change menu position

const gridContext = useContext(DataGridContext);
const container = gridContext.getScrollContainer();

function handleScroll() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function should be created inside effect to mantain the reference and avoid memory leaks

const handleScroll = () => menu.closeMenu();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used useCallback to keep the link still

return;
}

container.addEventListener('scroll', handleScroll, { capture: true });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add some delta value, so user can scroll a little before closing menu, also add throttle

position: IContextMenuPosition;
id: string;
openMenu(activeCell: IGridDataKey, event: React.MouseEvent | React.KeyboardEvent): void;
closeMenu(): void;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we dont need this method, you can call position.close, also we delete context on menu opening

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this means the context the whole time is gonna be there even if menu is closed
this can cause some issues, i don't think it is expected

moreover it doesn't work this way. menu doesn't close at all

open/close menu on this level API is fine so I'll keep it, there is no biggie here

simple: boolean;
isGridInFocus: () => boolean;
getDataGridApi: () => DataGridRef | null;
getScrollContainer: () => HTMLDivElement | null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets call it just getContainer, its only one for now

devnaumov
devnaumov previously approved these changes Jun 8, 2026
@devnaumov
devnaumov merged commit 6cdc6b2 into devel Jun 9, 2026
10 checks passed
@devnaumov
devnaumov deleted the 9308-cb-render-data-viewer-context-menu-on-the-root-level branch June 9, 2026 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants