Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Selection } from 'prosemirror-state';
import { isCellSelection } from '@extensions/table/tableHelpers/isCellSelection.js';
import { ContextMenuPluginKey } from '../../extensions/context-menu/context-menu.js';
import { getPropsByItemId, resolveContextMenuCommandEditor } from './utils.js';
import { shouldBypassContextMenu } from '../../utils/contextmenu-helpers.js';
import { shouldBypassContextMenu, isWithinResizeOverlay } from '../../utils/contextmenu-helpers.js';
import { moveCursorToMouseEvent } from '../cursor-helpers.js';
import { getEditorSurfaceElement } from '../../core/helpers/editorSurface.js';
import { getItems } from './menuItems.js';
Expand Down Expand Up @@ -367,6 +367,13 @@ const isEventWithinContextMenuTargets = (event) => {
return false;
}

// The table/image resize overlays render as siblings of the editor surface, not inside it.
// Right-clicks landing on their handles must still open the editor context menu instead of
// falling through to the browser's native menu.
if (isWithinResizeOverlay(target)) {
return true;
Comment on lines +373 to +374

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope resize overlay checks to this editor

When multiple SuperDoc editors are mounted, each ContextMenu instance has document-level contextmenu listeners, but this new early return treats any .superdoc-table-resize-overlay or .superdoc-image-resize-overlay in the document as belonging to this editor. Right-clicking a resize handle in editor A will therefore also make editor B's handler run, call preventDefault(), dispatch/open against editor B using editor A's coordinates, and potentially focus or show a stale menu for the wrong instance. Please only accept overlays associated with this ContextMenu's own .super-editor/surface before returning true.

Useful? React with 👍 / 👎.

}

return getContextMenuTargets().some(
(surface) => surface === target || (typeof surface?.contains === 'function' && surface.contains(target)),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { describe, it, expect } from 'vitest';
import { shouldBypassContextMenu, shouldAllowNativeContextMenu } from '../../../utils/contextmenu-helpers.js';
import {
shouldBypassContextMenu,
shouldAllowNativeContextMenu,
isWithinResizeOverlay,
} from '../../../utils/contextmenu-helpers.js';

describe('context menu helpers', () => {
it('returns false for standard right click', () => {
Expand Down Expand Up @@ -47,3 +51,35 @@ describe('context menu helpers', () => {
expect(shouldAllowNativeContextMenu(event)).toBe(true);
});
});

describe('isWithinResizeOverlay', () => {
it('returns true for a target inside the table resize overlay', () => {
const overlay = document.createElement('div');
overlay.className = 'superdoc-table-resize-overlay';
const handle = document.createElement('div');
handle.className = 'resize-handle';
overlay.appendChild(handle);

expect(isWithinResizeOverlay(handle)).toBe(true);
expect(isWithinResizeOverlay(overlay)).toBe(true);
});

it('returns true for a target inside the image resize overlay', () => {
const overlay = document.createElement('div');
overlay.className = 'superdoc-image-resize-overlay';
const handle = document.createElement('div');
overlay.appendChild(handle);

expect(isWithinResizeOverlay(handle)).toBe(true);
});

it('returns false for editor content and non-element targets', () => {
const paragraph = document.createElement('p');
paragraph.textContent = 'hello';

expect(isWithinResizeOverlay(paragraph)).toBe(false);
expect(isWithinResizeOverlay(document.createTextNode('hello'))).toBe(false);
expect(isWithinResizeOverlay(null)).toBe(false);
expect(isWithinResizeOverlay(undefined)).toBe(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,18 @@ export { shouldAllowNativeContextMenu };
export const shouldBypassContextMenu = shouldAllowNativeContextMenu;

export const shouldUseNativeContextMenu = shouldAllowNativeContextMenu;

/**
* The table and image resize overlays render as siblings of the editor surface rather than
* inside it. A right-click that lands on one of their handles therefore falls outside the
* normal context-menu target surfaces, which would let the browser's native menu appear.
* This identifies those overlay targets so the editor can still own the context menu.
* @param {EventTarget | null | undefined} target
* @returns {boolean}
*/
export const isWithinResizeOverlay = (target) => {
return (
typeof target?.closest === 'function' &&
Boolean(target.closest('.superdoc-table-resize-overlay, .superdoc-image-resize-overlay'))
);
};
Loading