Skip to content

Fix 3dot menu delete regression#4286

Open
tintinthong wants to merge 6 commits intomainfrom
fix-delete-button-menu-regresssion
Open

Fix 3dot menu delete regression#4286
tintinthong wants to merge 6 commits intomainfrom
fix-delete-button-menu-regresssion

Conversation

@tintinthong
Copy link
Copy Markdown
Contributor

File Tree Delete Dropdown Regression

The Issue

Commit 6bea2ee2e3 refactored the file tree context menu to use a single shared BoxelDropdown across all file rows (to avoid memory leaks from per-file dropdown instances).

The new design separates the visible trigger (the 3-dot ContextButton) from the dropdown anchor (a hidden button):

[file-row]  [ContextButton .file-menu-trigger]  ← user clicks this
...
[button .file-tree-menu-anchor]                 ← ember-basic-dropdown anchors to THIS

The anchor is styled to be invisible:

.file-tree-menu-anchor {
  position: fixed;
  width: 0;
  height: 0;
  opacity: 0;
  pointer-events: none;
  /* ⚠️ no top / left set */
}

ember-basic-dropdown positions its content by calling getBoundingClientRect() on the trigger element. Since the anchor has no top/left, it sits at its natural DOM position. The dropdown renders in the DOM but appears offscreen, hidden behind other UI.

Why direct DOM manipulation doesn't work

An obvious fix is to set the anchor's inline styles via anchorEl.style.top = ... before calling open(). However, calling this.dropdownApi?.actions.open(e) sets tracked state on the BasicDropdown component, which triggers a Glimmer re-render. During that re-render, Glimmer reconciles the anchor button's DOM attributes against the template — and since the template declares no style attribute on the anchor, Glimmer strips the inline styles we just set. By the time ember-basic-dropdown calls reposition() after render, the anchor is back at its default position.

The Fix

Use a @tracked property for the anchor's style string so that Glimmer includes it in the re-render instead of removing it:

@tracked private anchorStyle = '';

In the template, bind it to the anchor button:

<button
  class='file-tree-menu-anchor'
  style={{this.anchorStyle}}
  {{bindings}}
></button>

In openFileMenu, set the tracked property before opening the dropdown:

@action
private openFileMenu(entryPath: LocalPath, e: MouseEvent) {
  // ...
  this.menuEntryPath = entryPath;

  // ✅ Set via tracked property — survives Glimmer re-render
  const triggerRect = (e.currentTarget as HTMLElement).getBoundingClientRect();
  this.anchorStyle = `top: ${triggerRect.top}px; left: ${triggerRect.left}px; width: ${triggerRect.width}px; height: ${triggerRect.height}px;`;

  this.dropdownApi?.actions.open(e);
}

Now the re-render triggered by open() applies anchorStyle to the anchor, and when ember-basic-dropdown calls getBoundingClientRect() on the anchor after render, it gets the correct coordinates of the 3-dot button.

Reset anchorStyle when the menu closes:

@action
private clearFileMenu() {
  this.menuEntryPath = undefined;
  this.anchorStyle = '';
}

Auto-close Timer

The menu also auto-closes after 800 ms if the user does not hover over it. Hovering pauses the timer; moving the mouse away restarts it.

private startCloseMenuTimer() {
  clearTimeout(this.closeMenuTimer);
  this.closeMenuTimer = setTimeout(() => {
    this.dropdownApi?.actions.close();
  }, 800);
}

private cancelCloseMenuTimer() {
  clearTimeout(this.closeMenuTimer);
}

The timer starts when the menu opens and is wired to the menu content via mouseenter/mouseleave:

<Menu
  {{on 'mouseenter' this.cancelCloseMenuTimer}}
  {{on 'mouseleave' this.startCloseMenuTimer}}
  ...
/>

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 94e70d52ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +131 to +132
{{on 'mouseenter' this.cancelCloseMenuTimer}}
{{on 'mouseleave' this.startCloseMenuTimer}}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep file menu open for non-mouse interactions

The new timeout-based close behavior is only coordinated through mouseenter/mouseleave, so keyboard- or touch-opened menus never cancel the 800ms timer and will auto-close even while the user is trying to navigate to Delete. This introduces an accessibility regression (keyboard/screen-reader users lose the menu before they can act) and can also cause intermittent failures on slower devices; the timer should also be paused by focus/pointer/touch interaction (or not enforced while the menu is active).

Useful? React with 👍 / 👎.

@github-actions
Copy link
Copy Markdown

Preview deployments

Copy link
Copy Markdown
Contributor

@lukemelia lukemelia left a comment

Choose a reason for hiding this comment

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

Any reason not to use floating-ui for this kind of thing?

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 31, 2026

Host Test Results

2 095 tests  ±0   2 080 ✅ ±0   2h 13m 23s ⏱️ - 1m 26s
    1 suites ±0      15 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit 10511d4. ± Comparison against base commit 7b7f1ed.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 31, 2026

Realm Server Test Results

  1 files  ±0    1 suites  ±0   12m 8s ⏱️ -19s
828 tests ±0  828 ✅ ±0  0 💤 ±0  0 ❌ ±0 
899 runs  ±0  899 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 10511d4. ± Comparison against base commit 7b7f1ed.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a regression in the file tree “…” context menu positioning by binding a tracked inline style to the hidden BoxelDropdown anchor so ember-basic-dropdown can compute the correct trigger rect.

Changes:

  • Bind a tracked anchorStyle to the hidden dropdown anchor and set it from the clicked trigger’s getBoundingClientRect().
  • Add an auto-close timer for the menu, with pointer/focus enter/leave handlers to pause/resume the timer.
  • Ensure timer cleanup on close and destruction.

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

@jurgenwerk
Copy link
Copy Markdown
Contributor

jurgenwerk commented Apr 1, 2026

I find the auto close behavior unexpected and inconsistent with other contextual menus that we have. If I open a contextual menu I would expect it to stay open

@tintinthong tintinthong force-pushed the fix-delete-button-menu-regresssion branch from 4789782 to bac8e56 Compare April 2, 2026 07:03
@tintinthong
Copy link
Copy Markdown
Contributor Author

Any reason not to use floating-ui for this kind of thing?

good point. refactored to use ember-velcro

@tintinthong
Copy link
Copy Markdown
Contributor Author

I find the auto close behavior unexpected and inconsistent with other contextual menus that we have. If I open a contextual menu I would expect it to stay open

noted! thanks for feedback. removed any auto-closing and accounted for click outside close

@tintinthong tintinthong requested review from a team, jurgenwerk and lukemelia April 2, 2026 08:05
Copy link
Copy Markdown
Contributor

@lukemelia lukemelia left a comment

Choose a reason for hiding this comment

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

So we have an onClickOutside modifier we can use?

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.

4 participants