PageSourceSearch

https://www.iwant2eat.com/js/ctx-menu-clamp.o9t2943fbv.js

js iwant2eat.com collected 2026-09-24 14:45:41 UTC 3,666 bytes, 76 lines download raw bytes

1// Keeps the recipe context menu (MyRecipes) inside the viewport.
2//
3// Two jobs:
4//
5// 1. clampCtxMenu() — called from OnAfterRenderAsync after every render
6//    while the menu is open. The open coords are the raw cursor position
7//    on desktop and only an estimate-clamp on touch (card-longpress.js),
8//    and the menu's size changes while open (touch accordion expanding,
9//    error row appearing, items dropping out after a pick) — so measure
10//    the real box and nudge it back inside the right/bottom edges.
11//    Writing style.left/top directly is safe against Blazor re-renders:
12//    the diff compares successive virtual trees, and the style attribute
13//    it renders only changes when ctxX/Y change — i.e. on a fresh open,
14//    which is exactly when the stale nudge should be discarded.
15//
16// 2. Flyout flip — the hover submenus open at left:100%/top:0 of their
17//    row via pure CSS, so there's no render event to hook. A delegated
18//    mouseover/focusin listener measures the just-shown flyout and adds
19//    .ctx-fly-left / .ctx-fly-up when it would cross the right/bottom
20//    edge; the classes survive Blazor's child patches because the
21//    virtual tree's class attribute never changes while the menu is
22//    open, and a re-open recreates the DOM without them.
23
24const PAD_PX = 8;
25
26let _lastWrap = null;
27
28export function init() {
29    document.addEventListener('mouseover', onHoverFlip, true);
30    document.addEventListener('focusin', onHoverFlip, true);
31}
32
33export function clampCtxMenu() {
34    const m = document.querySelector('.ctx-menu');
35    if (!m) return;
36    const r = m.getBoundingClientRect();
37    const nl = Math.max(PAD_PX, Math.min(r.left, window.innerWidth - r.width - PAD_PX));
38    const nt = Math.max(PAD_PX, Math.min(r.top, window.innerHeight - r.height - PAD_PX));
39    if (nl !== r.left) m.style.left = nl + 'px';
40    if (nt !== r.top) m.style.top = nt + 'px';
41}
42
43// Find the card under a viewport point, seeing THROUGH the open
44// context-menu backdrop (which otherwise captures the hit). Lets a
45// right-click while a menu is open re-target the menu to the card under the
46// cursor instead of just closing. `attr` is 'data-recipe-id' or
47// 'data-collection-id'. Returns the attribute value, or null.
48export function cardAtPoint(x, y, attr) {
49    const backdrops = document.querySelectorAll('.ctx-backdrop');
50    const prev = [];
51    backdrops.forEach(b => { prev.push(b.style.pointerEvents); b.style.pointerEvents = 'none'; });
52    let el = null;
53    try { el = document.elementFromPoint(x, y); }
54    finally { backdrops.forEach((b, i) => { b.style.pointerEvents = prev[i]; }); }
55    const card = el && el.closest ? el.closest('[' + attr + ']') : null;
56    return card ? card.getAttribute(attr) : null;
57}
58
59function onHoverFlip(e) {
60    const wrap = e.target.closest ? e.target.closest('.ctx-menu .ctx-item-wrap') : null;
61    // Once per wrapper entry — mouseover refires on every child crossed,
62    // and a remove-measure-readd cycle mid-hover would be wasted work.
63    if (wrap === _lastWrap) return;
64    _lastWrap = wrap;
65    if (!wrap) return;
66    const sub = wrap.querySelector(':scope > .ctx-submenu');
67    if (!sub) return;
68
69    sub.classList.remove('ctx-fly-left', 'ctx-fly-up');
70    // Both classList writes and the measurement happen in this same
71    // task, before the next paint — no flicker from the reset.
72    const r = sub.getBoundingClientRect();
73    if (!r.width && !r.height) return; // hidden (touch accordion closed, etc.)
74    if (r.right > window.innerWidth - PAD_PX) sub.classList.add('ctx-fly-left');
75    if (r.bottom > window.innerHeight - PAD_PX) sub.classList.add('ctx-fly-up');
76}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.