Conversation
…serving submenu pop-out behavior
Menu Overflow Fix - Manual Testing Plan 🧪Issuenode-red#5377: UX: Ensure menus handle vertical overflow Summary of Changes ✨This fix enables menus to scroll when they exceed viewport height while still allowing submenus to pop out horizontally without being clipped. Technical Approach 🔧
Technical Deep Dive 🤿The Problem 🐛When a menu exceeds the viewport height, the natural solution is to add scrolling: .menu {
max-height: calc(100vh - 20px);
overflow-y: auto; /* Enable vertical scrolling */
overflow-x: visible; /* Allow submenus to pop out */
}However, this doesn't work. Per CSS specification, when one overflow axis is set to The Solution: Portal Pattern 🌀When a submenu needs special positioning, we "portal" it to When portaling is triggered:
How it works:
Key Implementation Details 🔑Submenu positioning ( function getSubmenuPosition(parentMenuEl, submenuEl, preferredSide) {
var parentMenuRect = parentMenuEl.getBoundingClientRect();
var submenuWidth = submenuEl.offsetWidth || 230;
var submenuHeight = submenuEl.offsetHeight || 200;
var viewportWidth = window.innerWidth;
var viewportHeight = window.innerHeight;
var padding = 10;
var x;
var y = parentMenuRect.top;
// Shift y if it would overflow bottom of viewport
if (y + submenuHeight > viewportHeight - padding) {
y = viewportHeight - submenuHeight - padding;
}
// Shift y if it would overflow top of viewport
if (y < padding) {
y = padding;
}
// Calculate x position based on preferred side with flip logic
if (preferredSide === "left") {
x = parentMenuRect.left - submenuWidth;
if (x < padding) {
x = parentMenuRect.right;
if (x + submenuWidth > viewportWidth - padding) {
x = viewportWidth - submenuWidth - padding;
}
}
} else {
x = parentMenuRect.right;
if (x + submenuWidth > viewportWidth - padding) {
x = parentMenuRect.left - submenuWidth;
if (x < padding) {
x = padding;
}
}
}
return { x: x, y: y };
}Portal detection ( function doesSubmenuNeedPortaling() {
var parentMenu = item.closest(".red-ui-menu-dropdown");
var overflowY = parentMenu.css("overflow-y");
// If parent menu is scrolling, portal the submenu
if (overflowY === "auto" || overflowY === "scroll") {
return true;
}
var itemRect = item[0].getBoundingClientRect();
var submenuWidth = submenu.outerWidth() || 230;
var viewportWidth = window.innerWidth;
var padding = 10;
// If submenu would overflow right edge, portal it
if (itemRect.right + submenuWidth > viewportWidth - padding) {
return true;
}
return false;
}Theme preservation: 🎨 Header menus use a dark theme while context menus use light. When portaling to Dark theme (header menus): 🌑
Light theme (context menus): ☀️
Files Modified 📁
Manual Testing Checklist ☑️1. Header Menus (Dark Theme) 🌑1.1 Hamburger Menu (☰)Submenus: Projects, Edit, View, Arrange, Flows, Subflows, Groups
1.2 Deploy Menu 🚀
1.3 User Menu (if logged in) 👤
2. Context Menus (Light Theme) ☀️How to trigger: Right-click on workspace 2.1 No SelectionSubmenus: Insert (Node, Junction, Link Nodes, Import, Import Example)
2.2 With Node(s) Selected 🎯Additional submenus: Node, Group, Arrange (if multiple)
3. Tabs Menus (Light Theme) 📑Note: These menus have no submenus 3.1 Flow Tab Dropdown (▼ button on tabs bar)
3.2 Flow Tab Context Menu
4. Submenu Styling (Portaled Menus) 🎨Test these when submenus are portaled (parent scrolling or near viewport edge) 4.1 Header Menu Submenus (Dark Theme) 🌑
4.2 Context Menu Submenus (Light Theme) ☀️
5. Edge Cases 🔪
6. Browser Compatibility 🌐
Known Limitations
|
Node-RED Contribution: Written Explanation 📝1. What did you choose to build, and why?Problem Selection 🎯I chose to address Issue node-red#5377: "UX: Ensure menus handle vertical overflow" from the Node-RED repository. The original issue: "Our current menus (main menu, context menu) do not cope well if there is not enough vertical space to show them. If the bottom is off-screen, the options need to be scrollable so they can be accessed." Why this problem:
My Interpretation 🧩The core challenge was: How do you enable vertical scrolling on a menu while allowing submenus to escape horizontally? The submenu problem I discovered: While testing the initial vertical scrolling implementation, I noticed a critical UX issue: when adding This wasn't explicitly mentioned in the original issue, but I recognized it would create a poor user experience. Users wouldn't be able to access submenu items if the parent menu needed to scroll. I decided to scope this into the fix to ensure a complete, polished solution. What I Focused On ⚡Core functionality:
Implementation locations:
What I Intentionally Left Out 🔪Scope decisions:
Implementation Tradeoffs ⚖️Architectural choices:
Accepted technical debt:
These are acceptable tradeoffs for the timebox, but would be good candidates for follow-up improvements if the implementation is adopted. 2. How and why did I use AI? 🦾I used Claude (Anthropic's AI assistant) extensively throughout this work. Here's how: The Brainstorming Phase: What Didn't Work 🪦I explored several CSS-only approaches (wrapper elements, overflow tricks, The Final Solution: Taking Modern Libraries as Inspiration 💡After these dead ends, I realized this is a solved problem in the positioning library space. Libraries like Floating UI (the modern successor to Popper.js) exist specifically to handle floating element positioning - tooltips, dropdowns, and popovers that need to escape overflow containers with intelligent collision detection and flip behavior. This sparked the key insight: rather than reinventing the wheel, study how battle-tested libraries solve this problem, then adapt those patterns to Node-RED's vanilla JS architecture. 🔍 AI helped me research Floating UI's approach and understand its core algorithms, but the strategic decision was mine: instead of adding the dependency, replicate its core positioning patterns in vanilla JS matching Node-RED's existing style. Why not just use Floating UI directly? Floating UI would have solved this elegantly, but Node-RED intentionally avoids frontend dependencies (the editor package.json has zero npm dependencies). Adding it would introduce new maintenance overhead and set a precedent inconsistent with the existing editor architecture. Instead, replicating Floating UI's well-documented positioning patterns in vanilla JS respected the project's philosophy while delivering the same functionality. What AI Helped With & What I Did Myself 🤖 🧠AI handled (80% of code generation):
I handled (100% of decision-making):
Tradeoffs in AI usage: 🎭 I chose to use AI heavily for code generation because:
I retained control over decisions because:
This balance let me deliver a working solution within the timebox while ensuring it fit Node-RED's architecture and standards. Summary 🎬This contribution was intentionally scoped to fit a 2–3 hour timebox while still delivering a complete, user-safe solution. I focused on solving the underlying UX problem rather than applying a partial fix, even when that meant identifying edge cases not explicitly called out in the original issue. I made deliberate tradeoffs to align with Node-RED's existing architecture and philosophy, favoring minimal dependencies, small surface area changes, and predictable behavior. AI was used as an accelerator for exploration and implementation, but all architectural decisions, scoping choices, and UX validation were made deliberately by me to ensure the solution fit Node-RED's standards and user needs. |
…al-overflow merged.
Proposed changes
See comment below
Checklist
npm run testto verify the unit tests pass