From 75e2c50b0146b04077374f467ec6686401045cf5 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 26 Jan 2026 07:05:48 +0100 Subject: [PATCH 01/11] fix --- .../ContextMenu/ContextMenu.react.js | 4 +- .../Data/Browser/DataBrowser.react.js | 67 +++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/components/ContextMenu/ContextMenu.react.js b/src/components/ContextMenu/ContextMenu.react.js index 23e2e10045..385bbdff24 100644 --- a/src/components/ContextMenu/ContextMenu.react.js +++ b/src/components/ContextMenu/ContextMenu.react.js @@ -156,7 +156,7 @@ const MenuSection = ({ level, items, path, setPath, hide, hoveredItemOffset }) = ); }; -const ContextMenu = ({ x, y, items }) => { +const ContextMenu = ({ x, y, items, onHide }) => { const [path, setPath] = useState([0]); // Track the pixel offset of the hovered item for each level const [hoveredOffsets, setHoveredOffsets] = useState([0]); @@ -171,6 +171,7 @@ const ContextMenu = ({ x, y, items }) => { setVisible(false); setPath([0]); setHoveredOffsets([0]); + onHide?.(); }; // Combined setter that updates both path and offsets @@ -235,6 +236,7 @@ ContextMenu.propTypes = { x: PropTypes.number.isRequired.describe('X context menu position.'), y: PropTypes.number.isRequired.describe('Y context menu position.'), items: PropTypes.array.isRequired.describe('Array with tree representation of context menu items.'), + onHide: PropTypes.func.describe('Callback when context menu is hidden.'), }; export default ContextMenu; diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 3168e28c75..7cf63842ab 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -190,6 +190,7 @@ export default class DataBrowser extends React.Component { recordingScrollStart: null, // Timestamp when scroll recording started recordingScrollEnd: null, // Timestamp when scrolling ended (before Option key release) recordedScrollDelta: 0, // Accumulated scroll delta during recording + nativeContextMenuOpen: false, // Whether the browser's native context menu is open }; this.handleResizeDiv = this.handleResizeDiv.bind(this); @@ -245,6 +246,8 @@ export default class DataBrowser extends React.Component { this.stopAutoScroll = this.stopAutoScroll.bind(this); this.performAutoScrollStep = this.performAutoScrollStep.bind(this); this.pauseAutoScrollWithResume = this.pauseAutoScrollWithResume.bind(this); + this.handleNativeContextMenu = this.handleNativeContextMenu.bind(this); + this.handleNativeContextMenuClose = this.handleNativeContextMenuClose.bind(this); this.saveOrderTimeout = null; this.aggregationPanelRef = React.createRef(); this.autoScrollIntervalId = null; @@ -333,6 +336,12 @@ export default class DataBrowser extends React.Component { // Auto-scroll event listeners document.body.addEventListener('keydown', this.handleAutoScrollKeyDown); document.body.addEventListener('keyup', this.handleAutoScrollKeyUp); + // Native context menu detection for auto-scroll pause + // Use capture phase to ensure we detect the event before the menu handles it + document.addEventListener('contextmenu', this.handleNativeContextMenu, true); + // Listen for click (fires after menu closes) and keydown (Escape closes menu) + document.addEventListener('click', this.handleNativeContextMenuClose, true); + document.addEventListener('keydown', this.handleNativeContextMenuClose, true); // Load keyboard shortcuts from server try { @@ -371,6 +380,9 @@ export default class DataBrowser extends React.Component { // Auto-scroll cleanup document.body.removeEventListener('keydown', this.handleAutoScrollKeyDown); document.body.removeEventListener('keyup', this.handleAutoScrollKeyUp); + document.removeEventListener('contextmenu', this.handleNativeContextMenu, true); + document.removeEventListener('click', this.handleNativeContextMenuClose, true); + document.removeEventListener('keydown', this.handleNativeContextMenuClose, true); if (this.autoScrollTimeoutId) { clearTimeout(this.autoScrollTimeoutId); } @@ -1385,6 +1397,34 @@ export default class DataBrowser extends React.Component { }); } + /** + * Checks if auto-scroll should be blocked due to user interactions. + * Auto-scroll pauses when: + * - A modal is displayed (script confirmation, graph dialog) + * - A context menu is displayed (custom or native browser menu) + * - The user is editing a cell in the databrowser table + * - Manual scroll pause is active + */ + isAutoScrollBlocked() { + const { + autoScrollPaused, + editing, + contextMenuItems, + showScriptConfirmationDialog, + showGraphDialog, + nativeContextMenuOpen, + } = this.state; + + return ( + autoScrollPaused || + editing || + (contextMenuItems && contextMenuItems.length > 0) || + showScriptConfirmationDialog || + showGraphDialog || + nativeContextMenuOpen + ); + } + toggleAutoScroll() { this.setState(prevState => { const newAutoScroll = !prevState.autoScrollEnabled; @@ -1479,6 +1519,24 @@ export default class DataBrowser extends React.Component { }, 500); } + handleNativeContextMenu() { + // Pause auto-scroll when native browser context menu is opened + if (this.state.isAutoScrolling && !this.state.nativeContextMenuOpen) { + this.setState({ nativeContextMenuOpen: true }); + } + } + + handleNativeContextMenuClose(e) { + // Resume auto-scroll when native browser context menu is closed + // For keydown events, only handle Escape key + if (e.type === 'keydown' && e.key !== 'Escape') { + return; + } + if (this.state.nativeContextMenuOpen) { + this.setState({ nativeContextMenuOpen: false }); + } + } + startAutoScroll() { if (this.state.isAutoScrolling) { return; @@ -1517,8 +1575,8 @@ export default class DataBrowser extends React.Component { return; } - if (this.state.autoScrollPaused) { - // When paused, keep checking but don't scroll + if (this.isAutoScrollBlocked()) { + // When blocked (modal, context menu, editing, or manual pause), keep checking but don't scroll this.autoScrollTimeoutId = setTimeout(() => { this.performAutoScrollStep(); }, 100); @@ -1554,8 +1612,8 @@ export default class DataBrowser extends React.Component { } const animateScroll = (currentTime) => { - if (!this.state.isAutoScrolling || this.state.autoScrollPaused) { - // If stopped or paused during animation, schedule next check + if (!this.state.isAutoScrolling || this.isAutoScrollBlocked()) { + // If stopped or blocked during animation, schedule next check this.autoScrollTimeoutId = setTimeout(() => { this.performAutoScrollStep(); }, 100); @@ -2583,6 +2641,7 @@ export default class DataBrowser extends React.Component { x={this.state.contextMenuX} y={this.state.contextMenuY} items={this.state.contextMenuItems} + onHide={() => this.setState({ contextMenuX: null, contextMenuY: null, contextMenuItems: null })} /> )} {this.state.showScriptConfirmationDialog && ( From 578489d98c808e8ef31c2ed0f8c71c5e4f8627f4 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 26 Jan 2026 07:29:02 +0100 Subject: [PATCH 02/11] fix --- .../Data/Browser/DataBrowser.react.js | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 7cf63842ab..487536bdfd 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -339,9 +339,15 @@ export default class DataBrowser extends React.Component { // Native context menu detection for auto-scroll pause // Use capture phase to ensure we detect the event before the menu handles it document.addEventListener('contextmenu', this.handleNativeContextMenu, true); - // Listen for click (fires after menu closes) and keydown (Escape closes menu) - document.addEventListener('click', this.handleNativeContextMenuClose, true); - document.addEventListener('keydown', this.handleNativeContextMenuClose, true); + // Listen for events that indicate the context menu was closed: + // - mousemove: user moved mouse after menu closed (most reliable) + // - keydown: Escape key closes the menu + // - blur: switching windows/tabs closes the menu + // NOTE: click/mousedown don't fire while native context menu is open + // NOTE: scroll is not used because auto-scroll itself triggers it + window.addEventListener('mousemove', this.handleNativeContextMenuClose); + window.addEventListener('keydown', this.handleNativeContextMenuClose, true); + window.addEventListener('blur', this.handleNativeContextMenuClose); // Load keyboard shortcuts from server try { @@ -381,8 +387,9 @@ export default class DataBrowser extends React.Component { document.body.removeEventListener('keydown', this.handleAutoScrollKeyDown); document.body.removeEventListener('keyup', this.handleAutoScrollKeyUp); document.removeEventListener('contextmenu', this.handleNativeContextMenu, true); - document.removeEventListener('click', this.handleNativeContextMenuClose, true); - document.removeEventListener('keydown', this.handleNativeContextMenuClose, true); + window.removeEventListener('mousemove', this.handleNativeContextMenuClose); + window.removeEventListener('keydown', this.handleNativeContextMenuClose, true); + window.removeEventListener('blur', this.handleNativeContextMenuClose); if (this.autoScrollTimeoutId) { clearTimeout(this.autoScrollTimeoutId); } @@ -1415,7 +1422,7 @@ export default class DataBrowser extends React.Component { nativeContextMenuOpen, } = this.state; - return ( + const blocked = ( autoScrollPaused || editing || (contextMenuItems && contextMenuItems.length > 0) || @@ -1423,6 +1430,19 @@ export default class DataBrowser extends React.Component { showGraphDialog || nativeContextMenuOpen ); + + if (blocked) { + console.log('[AutoScroll] isAutoScrollBlocked = true', { + autoScrollPaused, + editing, + contextMenuItems: contextMenuItems?.length || 0, + showScriptConfirmationDialog, + showGraphDialog, + nativeContextMenuOpen, + }); + } + + return blocked; } toggleAutoScroll() { @@ -1520,21 +1540,33 @@ export default class DataBrowser extends React.Component { } handleNativeContextMenu() { + console.log('[AutoScroll] handleNativeContextMenu called', { + isAutoScrolling: this.state.isAutoScrolling, + nativeContextMenuOpen: this.state.nativeContextMenuOpen, + }); // Pause auto-scroll when native browser context menu is opened if (this.state.isAutoScrolling && !this.state.nativeContextMenuOpen) { + console.log('[AutoScroll] Setting nativeContextMenuOpen: true'); this.setState({ nativeContextMenuOpen: true }); } } handleNativeContextMenuClose(e) { - // Resume auto-scroll when native browser context menu is closed - // For keydown events, only handle Escape key - if (e.type === 'keydown' && e.key !== 'Escape') { + // Only process if native context menu is open + if (!this.state.nativeContextMenuOpen) { return; } - if (this.state.nativeContextMenuOpen) { - this.setState({ nativeContextMenuOpen: false }); + + // For keydown events, only handle Escape key + if (e && e.type === 'keydown' && e.key !== 'Escape') { + return; } + + // mousemove, Escape key, or blur all indicate the menu is closed + console.log('[AutoScroll] handleNativeContextMenuClose - clearing nativeContextMenuOpen', { + eventType: e?.type, + }); + this.setState({ nativeContextMenuOpen: false }); } startAutoScroll() { From 5f786a10d5757d848f5bec07ad5886c87a02ecba Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 26 Jan 2026 07:44:13 +0100 Subject: [PATCH 03/11] scroll pause --- README.md | 6 +-- .../Data/Browser/DataBrowser.react.js | 39 +++++-------------- 2 files changed, 12 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 3a5599cc33..59a858c3e0 100644 --- a/README.md +++ b/README.md @@ -1453,14 +1453,14 @@ The info panel supports automatic scrolling, which is useful for hands-free brow **How to use:** 1. Enable auto-scroll via *Settings > Info Panel > Auto-scroll*. -2. Hold the **Option** (Alt) key while scrolling in the panel to record the scroll amount. -3. Release the Option key after pausing for the desired interval between scrolls. +2. Hold the **Command** (⌘) key while scrolling in the panel to record the scroll amount. +3. Release the Command key after pausing for the desired interval between scrolls. 4. Auto-scrolling begins automatically, repeating the recorded scroll amount and pause interval. **Controls:** - **Escape key**: Stop auto-scrolling. -- **Option key**: Stop current auto-scroll and start recording a new scroll pattern. +- **Command key**: Stop current auto-scroll and start recording a new scroll pattern. - **Manual scroll**: Temporarily pauses auto-scrolling, which resumes after inactivity. - **Auto-scroll button**: Click the highlighted "Auto-scroll" button in the toolbar to stop. diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 487536bdfd..89a2dee5af 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -183,12 +183,12 @@ export default class DataBrowser extends React.Component { // Auto-scroll feature state autoScrollEnabled: storedAutoScroll, // Whether auto-scroll feature is enabled (menu setting) isAutoScrolling: false, // Whether auto-scroll is currently active - isRecordingAutoScroll: false, // Whether we're recording (Option key held during scroll) + isRecordingAutoScroll: false, // Whether we're recording (Command key held during scroll) autoScrollAmount: 0, // The registered scroll amount (pixels) autoScrollDelay: 1000, // The registered wait time (ms) autoScrollPaused: false, // Whether auto-scroll is currently paused recordingScrollStart: null, // Timestamp when scroll recording started - recordingScrollEnd: null, // Timestamp when scrolling ended (before Option key release) + recordingScrollEnd: null, // Timestamp when scrolling ended (before Command key release) recordedScrollDelta: 0, // Accumulated scroll delta during recording nativeContextMenuOpen: false, // Whether the browser's native context menu is open }; @@ -1422,7 +1422,7 @@ export default class DataBrowser extends React.Component { nativeContextMenuOpen, } = this.state; - const blocked = ( + return ( autoScrollPaused || editing || (contextMenuItems && contextMenuItems.length > 0) || @@ -1430,19 +1430,6 @@ export default class DataBrowser extends React.Component { showGraphDialog || nativeContextMenuOpen ); - - if (blocked) { - console.log('[AutoScroll] isAutoScrollBlocked = true', { - autoScrollPaused, - editing, - contextMenuItems: contextMenuItems?.length || 0, - showScriptConfirmationDialog, - showGraphDialog, - nativeContextMenuOpen, - }); - } - - return blocked; } toggleAutoScroll() { @@ -1458,9 +1445,9 @@ export default class DataBrowser extends React.Component { } handleAutoScrollKeyDown(e) { - // Option/Alt key = keyCode 18 + // Command/Meta key = keyCode 91 (left) or 93 (right) // Only detect when panels are visible and auto-scroll is enabled - if (e.keyCode === 18 && this.state.autoScrollEnabled && this.state.isPanelVisible && !this.state.isRecordingAutoScroll) { + if ((e.keyCode === 91 || e.keyCode === 93) && this.state.autoScrollEnabled && this.state.isPanelVisible && !this.state.isRecordingAutoScroll) { // Stop any existing auto-scroll first if (this.state.isAutoScrolling) { this.stopAutoScroll(); @@ -1475,8 +1462,8 @@ export default class DataBrowser extends React.Component { } handleAutoScrollKeyUp(e) { - // Option/Alt key = keyCode 18 - if (e.keyCode === 18 && this.state.isRecordingAutoScroll) { + // Command/Meta key = keyCode 91 (left) or 93 (right) + if ((e.keyCode === 91 || e.keyCode === 93) && this.state.isRecordingAutoScroll) { const { recordedScrollDelta, recordingScrollStart, recordingScrollEnd } = this.state; // Only start auto-scroll if we actually recorded some scrolling @@ -1531,22 +1518,17 @@ export default class DataBrowser extends React.Component { this.setState({ autoScrollPaused: true }); } - // Schedule resume after 500ms of inactivity + // Schedule resume after 1000ms of inactivity this.autoScrollResumeTimeoutId = setTimeout(() => { if (this.state.isAutoScrolling && this.state.autoScrollPaused) { this.setState({ autoScrollPaused: false }); } - }, 500); + }, 1000); } handleNativeContextMenu() { - console.log('[AutoScroll] handleNativeContextMenu called', { - isAutoScrolling: this.state.isAutoScrolling, - nativeContextMenuOpen: this.state.nativeContextMenuOpen, - }); // Pause auto-scroll when native browser context menu is opened if (this.state.isAutoScrolling && !this.state.nativeContextMenuOpen) { - console.log('[AutoScroll] Setting nativeContextMenuOpen: true'); this.setState({ nativeContextMenuOpen: true }); } } @@ -1563,9 +1545,6 @@ export default class DataBrowser extends React.Component { } // mousemove, Escape key, or blur all indicate the menu is closed - console.log('[AutoScroll] handleNativeContextMenuClose - clearing nativeContextMenuOpen', { - eventType: e?.type, - }); this.setState({ nativeContextMenuOpen: false }); } From 975594aa918fc4d682a8225c589a09acfc848000 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 26 Jan 2026 08:03:56 +0100 Subject: [PATCH 04/11] modal add entry --- src/dashboard/Data/Browser/Browser.react.js | 3 ++- src/dashboard/Data/Browser/DataBrowser.react.js | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 93b6732434..80463c074c 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -2032,7 +2032,8 @@ class Browser extends DashboardView { this.state.showCloneSelectedRowsDialog || this.state.showEditRowDialog || this.state.showPermissionsDialog || - this.state.showExportSelectedRowsDialog + this.state.showExportSelectedRowsDialog || + this.state.showAddToConfigDialog ); } diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 89a2dee5af..8c3a26299a 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -1422,13 +1422,17 @@ export default class DataBrowser extends React.Component { nativeContextMenuOpen, } = this.state; + // disableKeyControls is true when parent Browser has a modal open + const { disableKeyControls } = this.props; + return ( autoScrollPaused || editing || (contextMenuItems && contextMenuItems.length > 0) || showScriptConfirmationDialog || showGraphDialog || - nativeContextMenuOpen + nativeContextMenuOpen || + disableKeyControls ); } From 01671b70cd61904faf5eba03645055a7daf8b729 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 26 Jan 2026 08:35:30 +0100 Subject: [PATCH 05/11] pause on header hover --- .../Data/Browser/DataBrowser.react.js | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 8c3a26299a..1ac49a3d42 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -191,6 +191,8 @@ export default class DataBrowser extends React.Component { recordingScrollEnd: null, // Timestamp when scrolling ended (before Command key release) recordedScrollDelta: 0, // Accumulated scroll delta during recording nativeContextMenuOpen: false, // Whether the browser's native context menu is open + mouseOutsidePanel: true, // Whether the mouse is outside the AggregationPanel + mouseOverPanelHeader: false, // Whether the mouse is over the panel header row }; this.handleResizeDiv = this.handleResizeDiv.bind(this); @@ -248,12 +250,17 @@ export default class DataBrowser extends React.Component { this.pauseAutoScrollWithResume = this.pauseAutoScrollWithResume.bind(this); this.handleNativeContextMenu = this.handleNativeContextMenu.bind(this); this.handleNativeContextMenuClose = this.handleNativeContextMenuClose.bind(this); + this.handlePanelMouseEnter = this.handlePanelMouseEnter.bind(this); + this.handlePanelMouseLeave = this.handlePanelMouseLeave.bind(this); + this.handlePanelHeaderMouseEnter = this.handlePanelHeaderMouseEnter.bind(this); + this.handlePanelHeaderMouseLeave = this.handlePanelHeaderMouseLeave.bind(this); this.saveOrderTimeout = null; this.aggregationPanelRef = React.createRef(); this.autoScrollIntervalId = null; this.autoScrollTimeoutId = null; this.autoScrollResumeTimeoutId = null; this.autoScrollAnimationId = null; + this.panelHeaderLeaveTimeoutId = null; this.panelColumnRefs = []; this.activePanelIndex = -1; this.isWheelScrolling = false; @@ -399,6 +406,9 @@ export default class DataBrowser extends React.Component { if (this.autoScrollAnimationId) { cancelAnimationFrame(this.autoScrollAnimationId); } + if (this.panelHeaderLeaveTimeoutId) { + clearTimeout(this.panelHeaderLeaveTimeoutId); + } } async componentDidUpdate(prevProps, prevState) { @@ -1420,6 +1430,8 @@ export default class DataBrowser extends React.Component { showScriptConfirmationDialog, showGraphDialog, nativeContextMenuOpen, + mouseOutsidePanel, + mouseOverPanelHeader, } = this.state; // disableKeyControls is true when parent Browser has a modal open @@ -1432,7 +1444,9 @@ export default class DataBrowser extends React.Component { showScriptConfirmationDialog || showGraphDialog || nativeContextMenuOpen || - disableKeyControls + disableKeyControls || + mouseOutsidePanel || + mouseOverPanelHeader ); } @@ -1552,6 +1566,42 @@ export default class DataBrowser extends React.Component { this.setState({ nativeContextMenuOpen: false }); } + handlePanelMouseEnter() { + if (this.state.mouseOutsidePanel) { + this.setState({ mouseOutsidePanel: false }); + } + } + + handlePanelMouseLeave() { + if (!this.state.mouseOutsidePanel) { + this.setState({ mouseOutsidePanel: true }); + } + } + + handlePanelHeaderMouseEnter() { + // Cancel any pending leave timeout + if (this.panelHeaderLeaveTimeoutId) { + clearTimeout(this.panelHeaderLeaveTimeoutId); + this.panelHeaderLeaveTimeoutId = null; + } + if (!this.state.mouseOverPanelHeader) { + this.setState({ mouseOverPanelHeader: true }); + } + } + + handlePanelHeaderMouseLeave() { + // Use a small delay to allow moving between adjacent headers without resuming scroll + if (this.panelHeaderLeaveTimeoutId) { + clearTimeout(this.panelHeaderLeaveTimeoutId); + } + this.panelHeaderLeaveTimeoutId = setTimeout(() => { + this.panelHeaderLeaveTimeoutId = null; + if (this.state.mouseOverPanelHeader) { + this.setState({ mouseOverPanelHeader: false }); + } + }, 50); + } + startAutoScroll() { if (this.state.isAutoScrolling) { return; @@ -2446,6 +2496,8 @@ export default class DataBrowser extends React.Component { className={styles.aggregationPanelContainer} ref={this.aggregationPanelRef} onWheel={this.handleAutoScrollWheel} + onMouseEnter={this.handlePanelMouseEnter} + onMouseLeave={this.handlePanelMouseLeave} > {this.state.panelCount > 1 ? (
this.onMouseEnterPanelCheckBox(objectId)} + onMouseEnter={() => { + this.onMouseEnterPanelCheckBox(objectId); + this.handlePanelHeaderMouseEnter(); + }} + onMouseLeave={this.handlePanelHeaderMouseLeave} onContextMenu={(e) => { e.preventDefault(); this.handlePanelHeaderContextMenu(e, objectId); From cbb57aa7e358bf9baf1a39067e3fd6bce572ecfb Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 26 Jan 2026 08:45:17 +0100 Subject: [PATCH 06/11] hover menu --- .../Data/Browser/BrowserToolbar.react.js | 22 +++++++++++++++++++ .../Data/Browser/DataBrowser.react.js | 22 +++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js index 323de52e55..423973f6b5 100644 --- a/src/dashboard/Data/Browser/BrowserToolbar.react.js +++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js @@ -98,6 +98,8 @@ const BrowserToolbar = ({ toggleShowPanelCheckbox, autoScrollEnabled, toggleAutoScroll, + autoScrollRequireHover, + toggleAutoScrollRequireHover, isAutoScrolling, stopAutoScroll, toggleGraphPanel, @@ -485,6 +487,7 @@ const BrowserToolbar = ({ toggleShowPanelCheckbox(); }} /> + @@ -504,6 +507,25 @@ const BrowserToolbar = ({ toggleAutoScroll(); }} /> + + {autoScrollRequireHover && ( + + )} + Auto-scroll requires hover + + } + onClick={() => { + toggleAutoScrollRequireHover(); + }} + />
diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 1ac49a3d42..41cbc9d4ef 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -39,6 +39,7 @@ const AGGREGATION_PANEL_COUNT = 'aggregationPanelCount'; const GRAPH_PANEL_VISIBLE = 'graphPanelVisible'; const GRAPH_PANEL_WIDTH = 'graphPanelWidth'; const AGGREGATION_PANEL_AUTO_SCROLL = 'aggregationPanelAutoScroll'; +const AGGREGATION_PANEL_AUTO_SCROLL_REQUIRE_HOVER = 'aggregationPanelAutoScrollRequireHover'; function formatValueForCopy(value, type) { if (value === undefined) { @@ -123,6 +124,8 @@ export default class DataBrowser extends React.Component { ]?.[props.className]; const storedAutoScroll = window.localStorage?.getItem(AGGREGATION_PANEL_AUTO_SCROLL) === 'true'; + const storedAutoScrollRequireHover = + window.localStorage?.getItem(AGGREGATION_PANEL_AUTO_SCROLL_REQUIRE_HOVER) !== 'false'; const storedGraphPanelVisible = window.localStorage?.getItem(GRAPH_PANEL_VISIBLE) === 'true'; const storedGraphPanelWidth = window.localStorage?.getItem(GRAPH_PANEL_WIDTH); @@ -182,6 +185,7 @@ export default class DataBrowser extends React.Component { isCreatingNewGraph: false, // Auto-scroll feature state autoScrollEnabled: storedAutoScroll, // Whether auto-scroll feature is enabled (menu setting) + autoScrollRequireHover: storedAutoScrollRequireHover, // Whether auto-scroll requires mouse hover over panel isAutoScrolling: false, // Whether auto-scroll is currently active isRecordingAutoScroll: false, // Whether we're recording (Command key held during scroll) autoScrollAmount: 0, // The registered scroll amount (pixels) @@ -241,6 +245,7 @@ export default class DataBrowser extends React.Component { this.deleteGraphConfig = this.deleteGraphConfig.bind(this); this.selectGraph = this.selectGraph.bind(this); this.toggleAutoScroll = this.toggleAutoScroll.bind(this); + this.toggleAutoScrollRequireHover = this.toggleAutoScrollRequireHover.bind(this); this.handleAutoScrollKeyDown = this.handleAutoScrollKeyDown.bind(this); this.handleAutoScrollKeyUp = this.handleAutoScrollKeyUp.bind(this); this.handleAutoScrollWheel = this.handleAutoScrollWheel.bind(this); @@ -1432,11 +1437,15 @@ export default class DataBrowser extends React.Component { nativeContextMenuOpen, mouseOutsidePanel, mouseOverPanelHeader, + autoScrollRequireHover, } = this.state; // disableKeyControls is true when parent Browser has a modal open const { disableKeyControls } = this.props; + // Check hover-related blocking only if autoScrollRequireHover is enabled + const hoverBlocked = autoScrollRequireHover && (mouseOutsidePanel || mouseOverPanelHeader); + return ( autoScrollPaused || editing || @@ -1445,8 +1454,7 @@ export default class DataBrowser extends React.Component { showGraphDialog || nativeContextMenuOpen || disableKeyControls || - mouseOutsidePanel || - mouseOverPanelHeader + hoverBlocked ); } @@ -1462,6 +1470,14 @@ export default class DataBrowser extends React.Component { }); } + toggleAutoScrollRequireHover() { + this.setState(prevState => { + const newRequireHover = !prevState.autoScrollRequireHover; + window.localStorage?.setItem(AGGREGATION_PANEL_AUTO_SCROLL_REQUIRE_HOVER, String(newRequireHover)); + return { autoScrollRequireHover: newRequireHover }; + }); + } + handleAutoScrollKeyDown(e) { // Command/Meta key = keyCode 91 (left) or 93 (right) // Only detect when panels are visible and auto-scroll is enabled @@ -2699,6 +2715,8 @@ export default class DataBrowser extends React.Component { toggleShowPanelCheckbox={this.toggleShowPanelCheckbox} autoScrollEnabled={this.state.autoScrollEnabled} toggleAutoScroll={this.toggleAutoScroll} + autoScrollRequireHover={this.state.autoScrollRequireHover} + toggleAutoScrollRequireHover={this.toggleAutoScrollRequireHover} isAutoScrolling={this.state.isAutoScrolling} stopAutoScroll={this.stopAutoScroll} toggleGraphPanel={this.toggleGraphPanelVisibility} From b822ef59fe3c91b04652c7464d031a5af43dea24 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:04:18 +0100 Subject: [PATCH 07/11] sub menu --- .../BrowserMenu/BrowserMenu.react.js | 47 +++++------ src/components/BrowserMenu/BrowserMenu.scss | 37 ++++++++- .../Data/Browser/BrowserToolbar.react.js | 78 ++++++++++--------- 3 files changed, 99 insertions(+), 63 deletions(-) diff --git a/src/components/BrowserMenu/BrowserMenu.react.js b/src/components/BrowserMenu/BrowserMenu.react.js index 9f4dcb0919..594f5f574c 100644 --- a/src/components/BrowserMenu/BrowserMenu.react.js +++ b/src/components/BrowserMenu/BrowserMenu.react.js @@ -54,18 +54,24 @@ export default class BrowserMenu extends React.Component { : styles.body } style={{ - minWidth: this.wrapRef.current.clientWidth, + // Only apply minWidth for top-level menus, not submenus ...(isSubmenu - ? { - top: 0, - left: this.state.openToLeft - ? 0 - : `${this.wrapRef.current.clientWidth - 3}px`, - transform: this.state.openToLeft - ? 'translateX(calc(-100% + 3px))' - : undefined, - } - : {}), + ? (() => { + // Find the parent menu container to get its width for proper positioning + const parentMenuBody = this.wrapRef.current.closest(`.${styles.subMenuBody}`) || + this.wrapRef.current.closest(`.${styles.body}`); + const parentWidth = parentMenuBody ? parentMenuBody.clientWidth : this.wrapRef.current.clientWidth; + return { + top: 0, + left: this.state.openToLeft + ? 0 + : `${parentWidth - 3}px`, + transform: this.state.openToLeft + ? 'translateX(calc(-100% + 3px))' + : undefined, + }; + })() + : { minWidth: this.wrapRef.current.clientWidth }), }} > {React.Children.map(this.props.children, (child) => { @@ -106,9 +112,12 @@ export default class BrowserMenu extends React.Component { if (!this.props.disabled) { if (isSubmenu) { entryEvents.onMouseEnter = () => { - const rect = this.wrapRef.current.getBoundingClientRect(); - const width = this.wrapRef.current.clientWidth; - const openToLeft = rect.right + width > window.innerWidth; + // Find the parent menu container to get its right edge for proper positioning + const parentMenuBody = this.wrapRef.current.closest(`.${styles.subMenuBody}`) || + this.wrapRef.current.closest(`.${styles.body}`); + const parentRect = parentMenuBody ? parentMenuBody.getBoundingClientRect() : this.wrapRef.current.getBoundingClientRect(); + const estimatedSubmenuWidth = 150; // Estimate for edge detection + const openToLeft = parentRect.right + estimatedSubmenuWidth > window.innerWidth; this.setState({ open: true, openToLeft }); this.props.setCurrent?.(null); }; @@ -124,14 +133,8 @@ export default class BrowserMenu extends React.Component {
{this.props.icon && } {this.props.title} - {isSubmenu && - React.Children.toArray(this.props.children).some(c => React.isValidElement(c) && c.type === BrowserMenu) && ( - + {isSubmenu && this.props.children && ( + )}
{menu} diff --git a/src/components/BrowserMenu/BrowserMenu.scss b/src/components/BrowserMenu/BrowserMenu.scss index 1cbf889244..299d82165a 100644 --- a/src/components/BrowserMenu/BrowserMenu.scss +++ b/src/components/BrowserMenu/BrowserMenu.scss @@ -11,6 +11,25 @@ display: inline-block; } +.menu { + position: relative; +} + +// Make submenu entries stretch to full width within menu bodies +.body, .subMenuBody, .subMenuBodyLeft { + > .wrap { + display: block; + + > .entry { + display: flex; + align-items: center; + justify-content: space-between; + padding: 4px 14px; + white-space: nowrap; + } + } +} + .entry { height: 30px; padding: 8px; @@ -113,15 +132,23 @@ } .submenuArrow { - margin-left: 4px; - fill: #66637A; + margin-left: 12px; + flex-shrink: 0; + font-size: 18px; + line-height: 1; + color: rgba(255, 255, 255, 0.5); + + &::after { + content: '›'; + } } .entry:hover .submenuArrow { - fill: white; + color: white; } .item { + position: relative; padding: 4px 14px; white-space: nowrap; cursor: pointer; @@ -129,6 +156,10 @@ &:hover { background: $blue; + + .submenuArrow { + border-left-color: white; + } } &.disabled { diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js index 423973f6b5..efce7aad0e 100644 --- a/src/dashboard/Data/Browser/BrowserToolbar.react.js +++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js @@ -488,44 +488,46 @@ const BrowserToolbar = ({ }} /> - - {autoScrollEnabled && ( - - )} - Auto-scroll - - } - onClick={() => { - toggleAutoScroll(); - }} - /> - - {autoScrollRequireHover && ( - - )} - Auto-scroll requires hover - - } - onClick={() => { - toggleAutoScrollRequireHover(); - }} - /> + + + {autoScrollEnabled && ( + + )} + Enabled + + } + onClick={() => { + toggleAutoScroll(); + }} + /> + + {autoScrollRequireHover && ( + + )} + Require hover + + } + onClick={() => { + toggleAutoScrollRequireHover(); + }} + /> +
From 6f0f569f999453a1f94f04c7ff3cfafa8458892d Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:22:58 +0100 Subject: [PATCH 08/11] menu hover --- .../BrowserMenu/BrowserMenu.react.js | 89 ++++++++++++++++++- src/components/BrowserMenu/MenuItem.react.js | 6 +- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/components/BrowserMenu/BrowserMenu.react.js b/src/components/BrowserMenu/BrowserMenu.react.js index 594f5f574c..d564d96d01 100644 --- a/src/components/BrowserMenu/BrowserMenu.react.js +++ b/src/components/BrowserMenu/BrowserMenu.react.js @@ -16,10 +16,32 @@ export default class BrowserMenu extends React.Component { constructor() { super(); - this.state = { open: false, openToLeft: false }; + this.state = { open: false, openToLeft: false, openChildKey: null, closeAllTrigger: 0 }; this.wrapRef = React.createRef(); } + componentDidUpdate(prevProps) { + // Close this submenu if parent signals it should close + console.log('[BrowserMenu] componentDidUpdate', { + title: this.props.title, + shouldClose: this.props.shouldClose, + prevShouldClose: prevProps.shouldClose, + closeAllTrigger: this.props.closeAllTrigger, + prevCloseAllTrigger: prevProps.closeAllTrigger, + open: this.state.open, + childKey: this.props.childKey, + }); + // Close if shouldClose changed to true, OR if closeAllTrigger incremented + const shouldCloseChanged = this.props.shouldClose && !prevProps.shouldClose; + const closeAllTriggered = this.props.closeAllTrigger !== undefined && + prevProps.closeAllTrigger !== undefined && + this.props.closeAllTrigger !== prevProps.closeAllTrigger; + if ((shouldCloseChanged || closeAllTriggered) && this.state.open) { + console.log('[BrowserMenu] Closing submenu', this.props.title, { shouldCloseChanged, closeAllTriggered }); + this.setState({ open: false }); + } + } + render() { let menu = null; const isSubmenu = !!this.props.parentClose; @@ -74,24 +96,54 @@ export default class BrowserMenu extends React.Component { : { minWidth: this.wrapRef.current.clientWidth }), }} > - {React.Children.map(this.props.children, (child) => { + {React.Children.map(this.props.children, (child, index) => { if (React.isValidElement(child)) { if (child.type === BrowserMenu) { + const childKey = `submenu-${index}`; + const shouldClose = this.state.openChildKey !== null && this.state.openChildKey !== childKey; + console.log('[BrowserMenu] Rendering child submenu', { + parentTitle: this.props.title, + childTitle: child.props.title, + childKey, + openChildKey: this.state.openChildKey, + shouldClose, + closeAllTrigger: this.state.closeAllTrigger, + }); return React.cloneElement(child, { ...child.props, parentClose: () => { this.setState({ open: false }); this.props.parentClose?.(); }, + childKey, + shouldClose, + closeAllTrigger: this.state.closeAllTrigger, + onSubmenuOpen: (key) => { + console.log('[BrowserMenu] onSubmenuOpen called', { + parentTitle: this.props.title, + key, + }); + this.setState({ openChildKey: key }); + }, }); } - // Pass closeMenu prop to all other children (like MenuItem) + // Pass closeMenu and onItemHover props to all other children (like MenuItem) return React.cloneElement(child, { ...child.props, closeMenu: () => { this.setState({ open: false }); this.props.parentClose?.(); }, + onItemHover: () => { + console.log('[BrowserMenu] onItemHover called, incrementing closeAllTrigger', { + parentTitle: this.props.title, + currentCloseAllTrigger: this.state.closeAllTrigger, + }); + this.setState(prev => ({ + openChildKey: null, + closeAllTrigger: prev.closeAllTrigger + 1, + })); + }, }); } return child; @@ -112,6 +164,11 @@ export default class BrowserMenu extends React.Component { if (!this.props.disabled) { if (isSubmenu) { entryEvents.onMouseEnter = () => { + console.log('[BrowserMenu] Entry onMouseEnter', { + title: this.props.title, + childKey: this.props.childKey, + hasOnSubmenuOpen: !!this.props.onSubmenuOpen, + }); // Find the parent menu container to get its right edge for proper positioning const parentMenuBody = this.wrapRef.current.closest(`.${styles.subMenuBody}`) || this.wrapRef.current.closest(`.${styles.body}`); @@ -120,6 +177,8 @@ export default class BrowserMenu extends React.Component { const openToLeft = parentRect.right + estimatedSubmenuWidth > window.innerWidth; this.setState({ open: true, openToLeft }); this.props.setCurrent?.(null); + // Notify parent that this submenu is now open (to close sibling submenus) + this.props.onSubmenuOpen?.(this.props.childKey); }; } else { entryEvents.onClick = () => { @@ -128,8 +187,30 @@ export default class BrowserMenu extends React.Component { }; } } + const wrapEvents = {}; + if (isSubmenu && !this.props.disabled) { + wrapEvents.onMouseLeave = (event) => { + // Only close submenu if mouse is moving to a sibling item in the parent menu + // Don't close if moving outside the menu entirely + const relatedTarget = event.relatedTarget; + if (!relatedTarget) { + return; + } + // Find the parent menu body that contains this submenu + const parentMenuBody = this.wrapRef.current.closest(`.${styles.subMenuBody}`) || + this.wrapRef.current.closest(`.${styles.subMenuBodyLeft}`) || + this.wrapRef.current.closest(`.${styles.body}`); + // Check if mouse is moving to another item in the same parent menu (sibling) + const isMovingToSibling = parentMenuBody && + parentMenuBody.contains(relatedTarget) && + !this.wrapRef.current.contains(relatedTarget); + if (isMovingToSibling) { + this.setState({ open: false }); + } + }; + } return ( -
+
{this.props.icon && } {this.props.title} diff --git a/src/components/BrowserMenu/MenuItem.react.js b/src/components/BrowserMenu/MenuItem.react.js index 8460c9282e..83f93c1b43 100644 --- a/src/components/BrowserMenu/MenuItem.react.js +++ b/src/components/BrowserMenu/MenuItem.react.js @@ -8,7 +8,7 @@ import React from 'react'; import styles from 'components/BrowserMenu/BrowserMenu.scss'; -const MenuItem = ({ text, shortcut, disabled, active, greenActive, onClick, disableMouseDown = false, closeMenu }) => { +const MenuItem = ({ text, shortcut, disabled, active, greenActive, onClick, disableMouseDown = false, closeMenu, onItemHover }) => { const classes = [styles.item]; if (disabled) { classes.push(styles.disabled); @@ -35,6 +35,10 @@ const MenuItem = ({ text, shortcut, disabled, active, greenActive, onClick, disa className={classes.join(' ')} onClick={handleClick} onMouseDown={disableMouseDown ? undefined : handleClick} // This is needed - onClick alone doesn't work in this context + onMouseEnter={() => { + console.log('[MenuItem] onMouseEnter', { text, hasOnItemHover: !!onItemHover }); + onItemHover?.(); + }} style={{ position: 'relative', zIndex: 9999, From 1e3918839f1eada5527e88ed313db4c5233ed694 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:24:26 +0100 Subject: [PATCH 09/11] clean-up --- .../BrowserMenu/BrowserMenu.react.js | 39 ++----------------- src/components/BrowserMenu/MenuItem.react.js | 5 +-- 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/src/components/BrowserMenu/BrowserMenu.react.js b/src/components/BrowserMenu/BrowserMenu.react.js index d564d96d01..75d9b9ec31 100644 --- a/src/components/BrowserMenu/BrowserMenu.react.js +++ b/src/components/BrowserMenu/BrowserMenu.react.js @@ -21,23 +21,13 @@ export default class BrowserMenu extends React.Component { } componentDidUpdate(prevProps) { - // Close this submenu if parent signals it should close - console.log('[BrowserMenu] componentDidUpdate', { - title: this.props.title, - shouldClose: this.props.shouldClose, - prevShouldClose: prevProps.shouldClose, - closeAllTrigger: this.props.closeAllTrigger, - prevCloseAllTrigger: prevProps.closeAllTrigger, - open: this.state.open, - childKey: this.props.childKey, - }); - // Close if shouldClose changed to true, OR if closeAllTrigger incremented + // Close if shouldClose changed to true (sibling submenu opened), + // OR if closeAllTrigger incremented (MenuItem was hovered) const shouldCloseChanged = this.props.shouldClose && !prevProps.shouldClose; const closeAllTriggered = this.props.closeAllTrigger !== undefined && prevProps.closeAllTrigger !== undefined && this.props.closeAllTrigger !== prevProps.closeAllTrigger; if ((shouldCloseChanged || closeAllTriggered) && this.state.open) { - console.log('[BrowserMenu] Closing submenu', this.props.title, { shouldCloseChanged, closeAllTriggered }); this.setState({ open: false }); } } @@ -101,14 +91,6 @@ export default class BrowserMenu extends React.Component { if (child.type === BrowserMenu) { const childKey = `submenu-${index}`; const shouldClose = this.state.openChildKey !== null && this.state.openChildKey !== childKey; - console.log('[BrowserMenu] Rendering child submenu', { - parentTitle: this.props.title, - childTitle: child.props.title, - childKey, - openChildKey: this.state.openChildKey, - shouldClose, - closeAllTrigger: this.state.closeAllTrigger, - }); return React.cloneElement(child, { ...child.props, parentClose: () => { @@ -118,13 +100,7 @@ export default class BrowserMenu extends React.Component { childKey, shouldClose, closeAllTrigger: this.state.closeAllTrigger, - onSubmenuOpen: (key) => { - console.log('[BrowserMenu] onSubmenuOpen called', { - parentTitle: this.props.title, - key, - }); - this.setState({ openChildKey: key }); - }, + onSubmenuOpen: (key) => this.setState({ openChildKey: key }), }); } // Pass closeMenu and onItemHover props to all other children (like MenuItem) @@ -135,10 +111,6 @@ export default class BrowserMenu extends React.Component { this.props.parentClose?.(); }, onItemHover: () => { - console.log('[BrowserMenu] onItemHover called, incrementing closeAllTrigger', { - parentTitle: this.props.title, - currentCloseAllTrigger: this.state.closeAllTrigger, - }); this.setState(prev => ({ openChildKey: null, closeAllTrigger: prev.closeAllTrigger + 1, @@ -164,11 +136,6 @@ export default class BrowserMenu extends React.Component { if (!this.props.disabled) { if (isSubmenu) { entryEvents.onMouseEnter = () => { - console.log('[BrowserMenu] Entry onMouseEnter', { - title: this.props.title, - childKey: this.props.childKey, - hasOnSubmenuOpen: !!this.props.onSubmenuOpen, - }); // Find the parent menu container to get its right edge for proper positioning const parentMenuBody = this.wrapRef.current.closest(`.${styles.subMenuBody}`) || this.wrapRef.current.closest(`.${styles.body}`); diff --git a/src/components/BrowserMenu/MenuItem.react.js b/src/components/BrowserMenu/MenuItem.react.js index 83f93c1b43..5b0a8267cc 100644 --- a/src/components/BrowserMenu/MenuItem.react.js +++ b/src/components/BrowserMenu/MenuItem.react.js @@ -35,10 +35,7 @@ const MenuItem = ({ text, shortcut, disabled, active, greenActive, onClick, disa className={classes.join(' ')} onClick={handleClick} onMouseDown={disableMouseDown ? undefined : handleClick} // This is needed - onClick alone doesn't work in this context - onMouseEnter={() => { - console.log('[MenuItem] onMouseEnter', { text, hasOnItemHover: !!onItemHover }); - onItemHover?.(); - }} + onMouseEnter={onItemHover} style={{ position: 'relative', zIndex: 9999, From 4ca5c87d0f88182719f8998684e36dd7d90ae8dd Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:28:50 +0100 Subject: [PATCH 10/11] menu --- .../Data/Browser/BrowserToolbar.react.js | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js index efce7aad0e..d2887aeff3 100644 --- a/src/dashboard/Data/Browser/BrowserToolbar.react.js +++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js @@ -395,7 +395,7 @@ const BrowserToolbar = ({ - {scrollToTop && ( + {autoLoadFirstRow && ( )} - Scroll to top + Auto-load first row } onClick={() => { - toggleScrollToTop(); + toggleAutoLoadFirstRow(); }} /> - {autoLoadFirstRow && ( + {batchNavigate && ( )} - Auto-load first row + Batch-navigate panels } onClick={() => { - toggleAutoLoadFirstRow(); + toggleBatchNavigate(); }} /> - {syncPanelScroll && ( + {showPanelCheckbox && ( )} - Sync panel scrolling + Show panel selection } onClick={() => { - toggleSyncPanelScroll(); + toggleShowPanelCheckbox(); }} /> + - {batchNavigate && ( + {scrollToTop && ( )} - Batch-navigate panels + Scroll to top } onClick={() => { - toggleBatchNavigate(); + toggleScrollToTop(); }} /> - {showPanelCheckbox && ( + {syncPanelScroll && ( )} - Show panel selection + Sync panel scrolling } onClick={() => { - toggleShowPanelCheckbox(); + toggleSyncPanelScroll(); }} /> - Date: Mon, 26 Jan 2026 10:05:36 +0100 Subject: [PATCH 11/11] fix --- src/components/BrowserMenu/BrowserMenu.react.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/BrowserMenu/BrowserMenu.react.js b/src/components/BrowserMenu/BrowserMenu.react.js index 75d9b9ec31..5d97a9dd26 100644 --- a/src/components/BrowserMenu/BrowserMenu.react.js +++ b/src/components/BrowserMenu/BrowserMenu.react.js @@ -71,6 +71,7 @@ export default class BrowserMenu extends React.Component { ? (() => { // Find the parent menu container to get its width for proper positioning const parentMenuBody = this.wrapRef.current.closest(`.${styles.subMenuBody}`) || + this.wrapRef.current.closest(`.${styles.subMenuBodyLeft}`) || this.wrapRef.current.closest(`.${styles.body}`); const parentWidth = parentMenuBody ? parentMenuBody.clientWidth : this.wrapRef.current.clientWidth; return { @@ -138,6 +139,7 @@ export default class BrowserMenu extends React.Component { entryEvents.onMouseEnter = () => { // Find the parent menu container to get its right edge for proper positioning const parentMenuBody = this.wrapRef.current.closest(`.${styles.subMenuBody}`) || + this.wrapRef.current.closest(`.${styles.subMenuBodyLeft}`) || this.wrapRef.current.closest(`.${styles.body}`); const parentRect = parentMenuBody ? parentMenuBody.getBoundingClientRect() : this.wrapRef.current.getBoundingClientRect(); const estimatedSubmenuWidth = 150; // Estimate for edge detection