mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-11-13 09:00:08 +00:00
Pixel editor: fix dropdown clipping when toolbar wraps near the sidebar
The pixel-art canvas-size dropdown (and the PNG-export dropdown that
shares the same class) used `position: absolute; right: 0` relative to
its toolbar wrap. The wrap lives inside `.pixelEditorBar`, which has
`flex-wrap: wrap`. When the bar wrapped and the trigger button landed
on the LEFT of its row, the menu's right-anchor pushed it 260px to the
LEFT — straight under the main sidebar.
Fix:
- Switched .pixelExportMenu to `position: fixed` (only when open).
- Added a `placeFlexMenu(menu, btn)` helper that anchors the menu's
right edge to the button's right edge by default, flips to the
button's left edge if anchoring right would push the menu past the
sidebar (using the aside's actual right edge as the safe-left
boundary, not just the viewport edge), and finally clamps to the
viewport on both sides.
- Added a fixed width (260px) + box-sizing border-box to keep the
menu from expanding to its natural content width once it's freed
from the previous absolute-positioning constraint.
- Window-resize handler auto-closes any open menu — the fixed coords
were computed against the previous layout, so resizing would
otherwise strand the menu away from its button.
Verified via CDP across 920px / 1000px / 1100px / 1440px viewports and
in both sidebar-open and sidebar-collapsed states. Also exercised the
specific failure scenario (button moved to the leftmost slot of the
bar, simulating the worst-case wrap layout) — menu now opens to the
right of the button, fully inside the canvas area.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
733d123edc
commit
809e0f8fae
2 files changed
+64
-8
No files matched your search
+52
-2
@@ -889,14 +889,20 @@
|
||||
|
||||
/* PNG export dropdown in the pixel editor toolbar */
|
||||
.pixelExportWrap { position: relative; }
|
||||
/* The menu is `position: fixed` so we can clamp it inside the viewport
|
||||
regardless of which row its button wrapped onto in the toolbar. When
|
||||
the button lands at the left of a wrapped row, an absolute-positioned
|
||||
menu anchored `right: 0` slides under the sidebar — fixed + JS
|
||||
placement avoids that. The actual top/left are set by placeFlexMenu(). */
|
||||
.pixelExportMenu {
|
||||
position: absolute; right: 0; top: calc(100% + 6px); z-index: 20;
|
||||
min-width: 260px; padding: 10px;
|
||||
position: fixed; z-index: 20;
|
||||
width: 260px; padding: 10px;
|
||||
background: var(--panel, #fff); color: var(--ink);
|
||||
border: 1px solid var(--line); border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgba(15,23,42,.18);
|
||||
flex-direction: column; gap: 8px;
|
||||
display: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.pixelExportMenu.open { display: flex; }
|
||||
.pixelExportRow { display: flex; align-items: center; gap: 8px; }
|
||||
@@ -9069,8 +9075,51 @@
|
||||
const willOpen = forceOpen !== undefined ? forceOpen : !isOpen;
|
||||
menu.classList.toggle('open', willOpen);
|
||||
btn.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
|
||||
if (willOpen) placeFlexMenu(menu, btn);
|
||||
}
|
||||
|
||||
// Position a `.pixelExportMenu` (fixed) below its trigger button,
|
||||
// right-aligned to the button by default and clamped inside the
|
||||
// viewport so it can't slide under the sidebar or off the right edge.
|
||||
// The safe-left boundary is the sidebar's right edge when visible
|
||||
// (not just the viewport edge) — otherwise a button that wrapped to
|
||||
// the left of its toolbar row would let the menu slide under the
|
||||
// sidebar.
|
||||
function placeFlexMenu(menu, btn) {
|
||||
if (!menu || !btn) return;
|
||||
const PAD = 8;
|
||||
const b = btn.getBoundingClientRect();
|
||||
const menuW = menu.offsetWidth || 260;
|
||||
const vw = window.innerWidth;
|
||||
const aside = document.querySelector('aside');
|
||||
const asideRect = aside ? aside.getBoundingClientRect() : null;
|
||||
// Sidebar is "visible at the left" when it's not collapsed AND its
|
||||
// right edge is inside the viewport (rules out the mobile 1-col layout
|
||||
// where the aside stacks above the canvas).
|
||||
const sidebarRight = (asideRect && asideRect.right > 0 && asideRect.left <= 0)
|
||||
? asideRect.right
|
||||
: 0;
|
||||
const minLeft = Math.max(PAD, sidebarRight + PAD);
|
||||
let left = b.right - menuW; // default: anchor right of button
|
||||
if (left < minLeft) left = b.left; // would clip left → anchor left of button
|
||||
if (left < minLeft) left = minLeft; // still clipped → clamp to sidebar edge
|
||||
if (left + menuW > vw - PAD) left = vw - menuW - PAD;
|
||||
menu.style.left = `${Math.round(left)}px`;
|
||||
menu.style.top = `${Math.round(b.bottom + 6)}px`;
|
||||
menu.style.right = 'auto';
|
||||
}
|
||||
|
||||
// Close any open .pixelExportMenu on window resize — its `position: fixed`
|
||||
// coords were computed against the previous layout, so on resize they'd
|
||||
// strand the menu away from its button.
|
||||
window.addEventListener('resize', () => {
|
||||
document.querySelectorAll('.pixelExportMenu.open').forEach((menu) => {
|
||||
menu.classList.remove('open');
|
||||
const btn = menu.parentElement?.querySelector('[aria-haspopup="true"]');
|
||||
if (btn) btn.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
});
|
||||
|
||||
// Draw a pixel-art layer onto an export canvas (ctx centred on the layer).
|
||||
function drawPixelArtToCanvas(ctx, layer, w, h) {
|
||||
try {
|
||||
@@ -9640,6 +9689,7 @@
|
||||
const wi = $('pixelResizeW'); if (wi) wi.value = _pixelEdit.w;
|
||||
const hi = $('pixelResizeH'); if (hi) hi.value = _pixelEdit.h;
|
||||
}
|
||||
if (willOpen) placeFlexMenu(menu, btn);
|
||||
}
|
||||
|
||||
function pixelFloodFill(x, y, idx) {
|
||||
|
||||
Reference in new issue
Block a user