mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-10-27 08:40:39 +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
@@ -5,12 +5,8 @@ Running list of deferred work. Detailed specs for the larger items live in
|
||||
|
||||
## Adam's working list
|
||||
|
||||
- **Pixel canvas size dropdown — fix sidebar clipping.** When the
|
||||
sidebar is open and the canvas size row wraps to a second line on
|
||||
the left, the pixel-art canvas-size dropdown is clipped by the
|
||||
sidebar. Easiest fix is probably "always right-align when wrapped"
|
||||
so the dropdown opens away from the sidebar; or move the dropdown
|
||||
to a placement that can't collide with the sidebar.
|
||||
_(Empty — see "Shipped (recent)" below for the items that just
|
||||
landed. Drop new items in here as they come up.)_
|
||||
|
||||
## QR code layer — remaining phases
|
||||
|
||||
@@ -88,6 +84,16 @@ Each has a full spec in `docs/`.
|
||||
|
||||
## Shipped (recent)
|
||||
|
||||
- **Pixel-art dropdowns — no more sidebar clipping.** The pixel-editor
|
||||
resize-grid and PNG-export menus were `position: absolute; right: 0`
|
||||
inside their toolbar wrap. When the toolbar wrapped and the trigger
|
||||
button landed at the left of its row, the menu slid leftward under
|
||||
the main sidebar. Switched both menus to `position: fixed` with a
|
||||
JS-placed coord that anchors to the button's right edge by default,
|
||||
flips to its left edge if anchoring right would clip past the
|
||||
sidebar (sidebar's actual right edge, not just the viewport),
|
||||
and clamps inside the viewport on both sides. Also auto-closes any
|
||||
open menu on window resize to avoid stranded fixed-position remnants.
|
||||
- **Top-right action strip — permanent, never wraps.** Pulled the
|
||||
canvas-size readouts and the undo / redo pair out of the topbar's
|
||||
flex flow and pinned them in a fixed strip directly under the Nostr
|
||||
|
||||
+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