Mind-map node context menu has different styling than the rest of the whiteboard #155

Open
opened 2026-05-06 09:34:36 +00:00 by AhmedHanafy725 · 3 comments
Member

The right-click context menu for mind-map nodes uses its own DOM/CSS implementation that does not match the standard whiteboard context menu used by sticky notes, shapes, frames, and the empty-canvas right-click.

Expected

The mind-map node context menu should reuse the same context-menu structure and styling as the rest of the canvas — same background, border, item hover state, separator style, icon spacing, font, and theme reactivity.

Actual

The mind-map menu appears with mismatched styling — different paddings, different background, different item layout. Visually inconsistent with the rest of the app.

Notes

The standard menu lives in crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js. The mind-map node menu is built ad-hoc inside mindmap.js (showNodeContextMenu). The fix is to either route mind-map node menu items through the shared module, or at minimum reuse the same CSS classes (.wb-context-menu, .wb-context-menu-item, etc. — confirm the actual class names by reading the standard menu code).

The right-click context menu for mind-map nodes uses its own DOM/CSS implementation that does not match the standard whiteboard context menu used by sticky notes, shapes, frames, and the empty-canvas right-click. ## Expected The mind-map node context menu should reuse the same context-menu structure and styling as the rest of the canvas — same background, border, item hover state, separator style, icon spacing, font, and theme reactivity. ## Actual The mind-map menu appears with mismatched styling — different paddings, different background, different item layout. Visually inconsistent with the rest of the app. ## Notes The standard menu lives in `crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js`. The mind-map node menu is built ad-hoc inside `mindmap.js` (`showNodeContextMenu`). The fix is to either route mind-map node menu items through the shared module, or at minimum reuse the same CSS classes (`.wb-context-menu`, `.wb-context-menu-item`, etc. — confirm the actual class names by reading the standard menu code).
Author
Member

Implementation Spec

Root cause

The standard whiteboard menu is rendered by WhiteboardContextMenu in crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js (renderMenu ~L141–171), using CSS classes wb-context-menu, ctx-item, ctx-divider, ctx-danger defined in whiteboard.css ~L111–118. These classes use theme variables (--wb-ctx-bg, --wb-border, --wb-text, --wb-hover, --wb-text-muted).

The mind-map node menu (showNodeContextMenu in mindmap.js ~L624–657) builds its own DOM with hardcoded inline styles (#2b3035 background, #495057 borders, #e0e0e0 text, hover via mouseenter/mouseleave) and emoji-glyph icons instead of Bootstrap icons. No wb-context-menu / ctx-item classes are used, so the menu is hardcoded dark, ignores theme, and uses different paddings, min-width, and box-shadow than the standard menu.

Today WhiteboardContextMenu only exposes init and hide — there's no programmatic API to render an arbitrary menu at a coordinate.

Approach: extract a shared show(items, x, y) helper

Option A (call existing API) is impossible — no public API exists. Option B (just adopt the classes) duplicates the markup pattern in two places. Option C — extract a small public show(items, x, y) helper from showAtPosition and have both the canvas path and the mindmap path use it — eliminates duplication and is barely more work since showAtPosition already separates "build items" from "render + position".

Files to Modify

  1. crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js — extract render/position/show into a public show(items, x, y); export it from the IIFE; refactor showAtPosition to delegate to it.
  2. crates/hero_whiteboard_ui/static/web/js/whiteboard/mindmap.js — replace the hand-rolled DOM in showNodeContextMenu with a call to WhiteboardContextMenu.show(items, evt.clientX, evt.clientY). Drop closeNodeContextMenu and _nodeMenuOutsideHandler — the shared module already wires document-level click + Escape dismissal.

No CSS changes.

Implementation Plan

Step 1 — extract shared helper

Files: contextmenu.js

  • Add public show(items, x, y) that calls renderMenu(items) and runs the existing viewport-clamp positioning (currently inlined at the end of showAtPosition): compute menuW/menuH from the rendered element, clamp x/y to viewport, set menuEl.style.left/top/display = 'block'.
  • Refactor showAtPosition so the final "render+position+display" step (~L127–138) becomes a single show(items, x, y) call. The earlier logic that resolves targetNode, selects on right-click, and builds the canvas/object items array stays exactly as-is.
  • targetNode is set inside showAtPosition for the canvas path's action callbacks. The new show must NOT clear it — callers manage targetNode themselves. Mindmap callers don't touch targetNode (their actions close over group/nodeData/nodeRect).
  • Export show: return { init, hide, show };.
  • Defensively, if menuEl is null inside show, lazily call init() first.

Step 2 — switch mindmap menu to shared helper

Files: mindmap.js

  • Replace showNodeContextMenu body with the standard items shape and a call to the new helper:
    function showNodeContextMenu(group, nodeData, nodeRect, evt) {
        var items = [
            { label: (nodeData.comment ? 'Edit' : 'Add') + ' Comment', icon: 'bi-chat',
              action: function() { showCommentPopup(group, nodeData, nodeRect); } },
            { label: 'Add Child', icon: 'bi-plus-circle',
              action: function() { addChildToNode(group, nodeData); } },
            { divider: true },
            { label: 'Delete Node', icon: 'bi-trash', danger: true,
              action: function() { deleteNode(group, nodeData); } },
        ];
        WhiteboardContextMenu.show(items, evt.clientX, evt.clientY);
    }
    
  • Remove the helper closeNodeContextMenu and _nodeMenuOutsideHandler (their only callers are inside showNodeContextMenu itself). Also remove the leading closeNodeContextMenu() call.
  • Issue #154 wiring (onNodeContextMenu with preventDefault / stopPropagation / cancelBubble) stays exactly as-is — that block is what stops the document-level menu from also firing on top of the per-node menu.

Dependencies: none.

Acceptance Criteria

  • Right-click on the node rect, label, collapse indicator, add-child button, or comment icon shows a menu visually identical to the standard whiteboard menu: same background, border, padding, min-width (~200px), font-size, item padding, icon column.
  • Items: "Add/Edit Comment", "Add Child", separator, "Delete Node" (red ctx-danger styling). Label flips based on nodeData.comment.
  • Hover state matches standard menu (var(--wb-hover) background); Delete uses red hover from .ctx-danger:hover.
  • Separator renders as the standard 1px line (.ctx-divider).
  • Toggling theme (light/dark) updates the menu colors automatically.
  • Outside-click and Escape dismiss the menu. Clicking an item runs the action and dismisses.
  • Menu is clamped to the viewport (right/bottom edges).
  • Canvas/object right-click menu (sticky / shape / frame / empty area) is unchanged.

What NOT to break

  • Issue #154 wiring (commit e3c4cb8): per-node contextmenu listeners on rect/label/indicator/addBtn/commentIcon with preventDefault + stopPropagation + cancelBubble. Without this the document-level handler still fires the canvas menu.
  • Three existing menu items: Add/Edit Comment → showCommentPopup, Add Child → addChildToNode, Delete Node → deleteNode. Closures over group/nodeData/nodeRect must survive.
  • showAtPosition's order of operations for the canvas/object menu (resolve targetNode, select on right-click, build items) — only the final render-and-position step changes.
  • Existing WhiteboardContextMenu.hide callers — hide stays exported.
## Implementation Spec ### Root cause The standard whiteboard menu is rendered by `WhiteboardContextMenu` in `crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js` (`renderMenu` ~L141–171), using CSS classes `wb-context-menu`, `ctx-item`, `ctx-divider`, `ctx-danger` defined in `whiteboard.css` ~L111–118. These classes use theme variables (`--wb-ctx-bg`, `--wb-border`, `--wb-text`, `--wb-hover`, `--wb-text-muted`). The mind-map node menu (`showNodeContextMenu` in `mindmap.js` ~L624–657) builds its own DOM with hardcoded inline styles (`#2b3035` background, `#495057` borders, `#e0e0e0` text, hover via `mouseenter`/`mouseleave`) and emoji-glyph icons instead of Bootstrap icons. No `wb-context-menu` / `ctx-item` classes are used, so the menu is hardcoded dark, ignores theme, and uses different paddings, min-width, and box-shadow than the standard menu. Today `WhiteboardContextMenu` only exposes `init` and `hide` — there's no programmatic API to render an arbitrary menu at a coordinate. ### Approach: extract a shared `show(items, x, y)` helper Option A (call existing API) is impossible — no public API exists. Option B (just adopt the classes) duplicates the markup pattern in two places. Option C — extract a small public `show(items, x, y)` helper from `showAtPosition` and have both the canvas path and the mindmap path use it — eliminates duplication and is barely more work since `showAtPosition` already separates "build items" from "render + position". ### Files to Modify 1. `crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js` — extract render/position/show into a public `show(items, x, y)`; export it from the IIFE; refactor `showAtPosition` to delegate to it. 2. `crates/hero_whiteboard_ui/static/web/js/whiteboard/mindmap.js` — replace the hand-rolled DOM in `showNodeContextMenu` with a call to `WhiteboardContextMenu.show(items, evt.clientX, evt.clientY)`. Drop `closeNodeContextMenu` and `_nodeMenuOutsideHandler` — the shared module already wires document-level click + Escape dismissal. No CSS changes. ### Implementation Plan #### Step 1 — extract shared helper Files: `contextmenu.js` - Add public `show(items, x, y)` that calls `renderMenu(items)` and runs the existing viewport-clamp positioning (currently inlined at the end of `showAtPosition`): compute `menuW`/`menuH` from the rendered element, clamp x/y to viewport, set `menuEl.style.left`/`top`/`display = 'block'`. - Refactor `showAtPosition` so the final "render+position+display" step (~L127–138) becomes a single `show(items, x, y)` call. The earlier logic that resolves `targetNode`, selects on right-click, and builds the canvas/object items array stays exactly as-is. - `targetNode` is set inside `showAtPosition` for the canvas path's action callbacks. The new `show` must NOT clear it — callers manage `targetNode` themselves. Mindmap callers don't touch `targetNode` (their actions close over `group`/`nodeData`/`nodeRect`). - Export `show`: `return { init, hide, show };`. - Defensively, if `menuEl` is null inside `show`, lazily call `init()` first. #### Step 2 — switch mindmap menu to shared helper Files: `mindmap.js` - Replace `showNodeContextMenu` body with the standard items shape and a call to the new helper: ```js function showNodeContextMenu(group, nodeData, nodeRect, evt) { var items = [ { label: (nodeData.comment ? 'Edit' : 'Add') + ' Comment', icon: 'bi-chat', action: function() { showCommentPopup(group, nodeData, nodeRect); } }, { label: 'Add Child', icon: 'bi-plus-circle', action: function() { addChildToNode(group, nodeData); } }, { divider: true }, { label: 'Delete Node', icon: 'bi-trash', danger: true, action: function() { deleteNode(group, nodeData); } }, ]; WhiteboardContextMenu.show(items, evt.clientX, evt.clientY); } ``` - Remove the helper `closeNodeContextMenu` and `_nodeMenuOutsideHandler` (their only callers are inside `showNodeContextMenu` itself). Also remove the leading `closeNodeContextMenu()` call. - Issue #154 wiring (`onNodeContextMenu` with `preventDefault` / `stopPropagation` / `cancelBubble`) stays exactly as-is — that block is what stops the document-level menu from also firing on top of the per-node menu. Dependencies: none. ### Acceptance Criteria - [ ] Right-click on the node rect, label, collapse indicator, add-child button, or comment icon shows a menu visually identical to the standard whiteboard menu: same background, border, padding, min-width (~200px), font-size, item padding, icon column. - [ ] Items: "Add/Edit Comment", "Add Child", separator, "Delete Node" (red `ctx-danger` styling). Label flips based on `nodeData.comment`. - [ ] Hover state matches standard menu (`var(--wb-hover)` background); Delete uses red hover from `.ctx-danger:hover`. - [ ] Separator renders as the standard 1px line (`.ctx-divider`). - [ ] Toggling theme (light/dark) updates the menu colors automatically. - [ ] Outside-click and Escape dismiss the menu. Clicking an item runs the action and dismisses. - [ ] Menu is clamped to the viewport (right/bottom edges). - [ ] Canvas/object right-click menu (sticky / shape / frame / empty area) is unchanged. ### What NOT to break - Issue #154 wiring (commit `e3c4cb8`): per-node `contextmenu` listeners on rect/label/indicator/addBtn/commentIcon with `preventDefault` + `stopPropagation` + `cancelBubble`. Without this the document-level handler still fires the canvas menu. - Three existing menu items: Add/Edit Comment → `showCommentPopup`, Add Child → `addChildToNode`, Delete Node → `deleteNode`. Closures over `group`/`nodeData`/`nodeRect` must survive. - `showAtPosition`'s order of operations for the canvas/object menu (resolve `targetNode`, select on right-click, build items) — only the final render-and-position step changes. - Existing `WhiteboardContextMenu.hide` callers — `hide` stays exported.
Author
Member

Validation

Check Result
Files changed 2 files (contextmenu.js +6, mindmap.js +8/-43)
cargo check --workspace pass
cargo test --workspace --lib pass
## Validation | Check | Result | |---|---| | Files changed | 2 files (contextmenu.js +6, mindmap.js +8/-43) | | `cargo check --workspace` | pass | | `cargo test --workspace --lib` | pass |
Author
Member

Implementation summary

Changes

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js — extracted a public show(items, x, y) helper that calls renderMenu(items) and runs the existing viewport-clamp / display logic. showAtPosition now delegates the final "render + position + display" step to show. Exported as part of the IIFE return: { init, hide, show }. targetNode handling for canvas-menu actions is unchanged.

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/mindmap.js — replaced the bespoke DOM in showNodeContextMenu with a single WhiteboardContextMenu.show(items, evt.clientX, evt.clientY) call. Items use the standard renderer's keys (label, icon, action, divider, danger):

    • Add/Edit Comment (bi-chat)
    • Add Child (bi-plus-circle)
    • divider
    • Delete Node (bi-trash, danger)
      Removed closeNodeContextMenu and _nodeMenuOutsideHandler — outside-click and Escape dismissal are already wired by WhiteboardContextMenu.init().

Issue #154's wiring (onNodeContextMenu attached to every per-node sub-shape) is intact.

Validation

  • cargo check --workspace: pass
  • cargo test --workspace --lib: pass
  • Diff scope: 2 files, +14/-43 net

Notes

UI-only change. Verify visually: right-click any mind-map node — the menu should look identical to the right-click menu used for sticky notes / shapes / frames / empty canvas. Theme switch should re-color the menu the same way it re-colors the standard menu.

## Implementation summary ### Changes - `crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js` — extracted a public `show(items, x, y)` helper that calls `renderMenu(items)` and runs the existing viewport-clamp / display logic. `showAtPosition` now delegates the final "render + position + display" step to `show`. Exported as part of the IIFE return: `{ init, hide, show }`. `targetNode` handling for canvas-menu actions is unchanged. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/mindmap.js` — replaced the bespoke DOM in `showNodeContextMenu` with a single `WhiteboardContextMenu.show(items, evt.clientX, evt.clientY)` call. Items use the standard renderer's keys (`label`, `icon`, `action`, `divider`, `danger`): - Add/Edit Comment (`bi-chat`) - Add Child (`bi-plus-circle`) - divider - Delete Node (`bi-trash`, danger) Removed `closeNodeContextMenu` and `_nodeMenuOutsideHandler` — outside-click and Escape dismissal are already wired by `WhiteboardContextMenu.init()`. Issue #154's wiring (`onNodeContextMenu` attached to every per-node sub-shape) is intact. ### Validation - `cargo check --workspace`: pass - `cargo test --workspace --lib`: pass - Diff scope: 2 files, +14/-43 net ### Notes UI-only change. Verify visually: right-click any mind-map node — the menu should look identical to the right-click menu used for sticky notes / shapes / frames / empty canvas. Theme switch should re-color the menu the same way it re-colors the standard menu.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_whiteboard#155
No description provided.