Skip to content

Commit bf9cbab

Browse files
committed
Fix TODO dropdown positioning
1 parent 15951d5 commit bf9cbab

3 files changed

Lines changed: 53 additions & 3 deletions

File tree

internal/app/static/app.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,14 +460,32 @@ function prioritySortRank(priority) {
460460
return 9;
461461
}
462462

463+
function resetTodoDropdown(dropdown) {
464+
dropdown.hidden = true;
465+
dropdown.style.removeProperty('left');
466+
dropdown.style.removeProperty('top');
467+
}
463468
function closeTodoMenus(exceptMenu) {
464469
document.querySelectorAll('[data-task-menu][aria-expanded="true"]').forEach((button) => {
465470
if (button === exceptMenu) return;
466471
button.setAttribute('aria-expanded', 'false');
467472
const dropdown = button.closest('.task-actions')?.querySelector('.task-menu-dropdown');
468-
if (dropdown) dropdown.hidden = true;
473+
if (dropdown) resetTodoDropdown(dropdown);
469474
});
470475
}
476+
function positionTodoDropdown(menu, dropdown) {
477+
const buttonRect = menu.getBoundingClientRect();
478+
dropdown.style.position = 'fixed';
479+
dropdown.style.left = '0px';
480+
dropdown.style.top = '0px';
481+
dropdown.hidden = false;
482+
const dropdownRect = dropdown.getBoundingClientRect();
483+
const left = Math.max(8, Math.min(window.innerWidth - dropdownRect.width - 8, buttonRect.right - dropdownRect.width));
484+
const opensDown = buttonRect.bottom + 6 + dropdownRect.height <= window.innerHeight - 8;
485+
const top = opensDown ? Math.min(window.innerHeight - dropdownRect.height - 8, buttonRect.bottom + 6) : Math.min(window.innerHeight - dropdownRect.height - 8, Math.max(8, buttonRect.top - dropdownRect.height - 6));
486+
dropdown.style.left = left + 'px';
487+
dropdown.style.top = top + 'px';
488+
}
471489
function initTodoActions() {
472490
document.addEventListener('click', (ev) => {
473491
const menu = ev.target.closest('[data-task-menu]');
@@ -481,8 +499,11 @@ function initTodoActions() {
481499
const expanded = menu.getAttribute('aria-expanded') === 'true';
482500
closeTodoMenus(menu);
483501
menu.setAttribute('aria-expanded', String(!expanded));
484-
dropdown.hidden = expanded;
502+
if (expanded) resetTodoDropdown(dropdown);
503+
else positionTodoDropdown(menu, dropdown);
485504
});
505+
window.addEventListener('resize', () => closeTodoMenus());
506+
window.addEventListener('scroll', () => closeTodoMenus(), true);
486507
document.addEventListener('keydown', (ev) => {
487508
if (ev.key === 'Escape') closeTodoMenus();
488509
});

internal/app/static/style.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/app/ui_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,35 @@ func TestTODOPageUsesCountersStructuredRowsAndCollapsedDone(t *testing.T) {
378378
t.Fatalf("TODO dropdown should only expose mark-done and copy-id actions, found %q in:\n%s", forbidden, body)
379379
}
380380
}
381+
382+
w = httptest.NewRecorder()
383+
r = httptest.NewRequest("GET", "/_static/app.js", nil)
384+
s.ServeHTTP(w, r)
385+
js := w.Body.String()
386+
for _, want := range []string{
387+
"function positionTodoDropdown(menu, dropdown)",
388+
"dropdown.style.position = 'fixed'",
389+
"window.innerHeight",
390+
"Math.min(window.innerHeight - dropdownRect.height - 8, Math.max(8, buttonRect.top - dropdownRect.height - 6))",
391+
"positionTodoDropdown(menu, dropdown)",
392+
} {
393+
if !strings.Contains(js, want) {
394+
t.Fatalf("TODO dropdown must be viewport-positioned to avoid clipping, missing %q in:\n%s", want, js)
395+
}
396+
}
397+
398+
w = httptest.NewRecorder()
399+
r = httptest.NewRequest("GET", "/_static/style.css", nil)
400+
s.ServeHTTP(w, r)
401+
css := w.Body.String()
402+
for _, want := range []string{
403+
".task-menu-dropdown{position:fixed;",
404+
"z-index:40",
405+
} {
406+
if !strings.Contains(css, want) {
407+
t.Fatalf("TODO dropdown must escape clipped task containers, missing CSS %q in:\n%s", want, css)
408+
}
409+
}
381410
}
382411

383412
func TestTagsPageHasWorkingFilterMarkupWithoutMetricCards(t *testing.T) {

0 commit comments

Comments
 (0)