Compare commits

..
2 changed files with 3 additions and 124 deletions

View file

@ -216,7 +216,6 @@ export const Panel = (() => {
let root = null;
let titleEl = null;
let bodyEl = null;
let drawer = null;
/** Owner key set by `openNamed` (e.g. 'inbox'). `refresh(name, )`
* is a no-op when the current owner doesn't match, so live
* updates can re-render an open view without grabbing focus
@ -230,7 +229,6 @@ export const Panel = (() => {
root = $('side-panel');
titleEl = $('side-panel-title');
bodyEl = $('side-panel-body');
drawer = root && root.querySelector('.side-panel-drawer');
}
return root != null;
}
@ -258,79 +256,6 @@ export const Panel = (() => {
root.classList.remove('open');
root.setAttribute('aria-hidden', 'true');
}
// #451: drag-to-resize the drawer's width. Listens on a thin
// hit-strip glued to the drawer's left edge; mousedown captures
// pointermove + pointerup on the document so the drag continues
// even if the cursor strays outside the 6px handle band. Width
// persists to localStorage so it survives page reload. The CSS
// clamps the value (min-width: 320px, max-width: 96vw) — drop
// unparseable / out-of-range stored values silently.
const WIDTH_KEY = 'hyperhive:side-panel-width';
const WIDTH_MIN = 320;
function clampWidth(w) {
const max = Math.floor(window.innerWidth * 0.96);
return Math.max(WIDTH_MIN, Math.min(max, w));
}
function applyStoredWidth() {
if (!drawer) return;
const raw = (() => {
try { return localStorage.getItem(WIDTH_KEY); }
catch { return null; }
})();
if (!raw) return;
const parsed = parseInt(raw, 10);
if (!Number.isFinite(parsed) || parsed <= 0) return;
drawer.style.setProperty('--side-panel-w', clampWidth(parsed) + 'px');
}
function bindResize() {
if (!drawer) return;
const handle = document.createElement('div');
handle.className = 'side-panel-resize';
handle.setAttribute('role', 'separator');
handle.setAttribute('aria-orientation', 'vertical');
handle.setAttribute('aria-label', 'drag to resize side panel');
handle.title = 'drag to resize';
drawer.prepend(handle);
let dragging = false;
handle.addEventListener('pointerdown', (e) => {
e.preventDefault();
dragging = true;
document.body.classList.add('side-panel-resizing');
// Capture so we keep getting pointermove even when the cursor
// outpaces the handle band (drag-fast-then-pause loses the
// handle's :hover state otherwise).
try { handle.setPointerCapture(e.pointerId); } catch { /* legacy */ }
});
document.addEventListener('pointermove', (e) => {
if (!dragging) return;
// Drawer is anchored to the right edge — width = viewport - pointer X.
const w = clampWidth(window.innerWidth - e.clientX);
drawer.style.setProperty('--side-panel-w', w + 'px');
});
function stopDrag() {
if (!dragging) return;
dragging = false;
document.body.classList.remove('side-panel-resizing');
// Persist the final width. Read the actual rendered width
// rather than re-deriving so the stored value matches what
// the operator saw at mouseup.
const w = drawer.getBoundingClientRect().width;
try { localStorage.setItem(WIDTH_KEY, String(Math.round(w))); }
catch { /* localStorage unavailable — width is session-only */ }
}
document.addEventListener('pointerup', stopDrag);
document.addEventListener('pointercancel', stopDrag);
// Re-clamp on viewport resize so a persisted width that exceeds
// 96vw doesn't push the drawer off-screen after a window shrink.
window.addEventListener('resize', () => {
if (dragging) return;
const cur = drawer.getBoundingClientRect().width;
const clamped = clampWidth(cur);
if (clamped !== Math.round(cur)) {
drawer.style.setProperty('--side-panel-w', clamped + 'px');
}
});
}
function bind() {
if (!ensure()) return;
$('side-panel-close').addEventListener('click', close);
@ -338,8 +263,6 @@ export const Panel = (() => {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && root.classList.contains('open')) close();
});
applyStoredWidth();
bindResize();
}
return { open, openNamed, refresh, close, bind };
})();

View file

@ -1111,10 +1111,8 @@ summary:hover { color: var(--purple); }
background: var(--bg-elev);
border: 1px solid var(--border);
padding: 0.5em 0.8em;
/* #450: no max-height cap let the inbox grow to fill the
side-panel-body which already scrolls (`overflow: auto`). The
pre-#450 24em cap clamped the list well short of the available
panel height even on tall viewports. */
max-height: 24em;
overflow-y: auto;
}
.inbox li {
padding: 0.25em 0;
@ -1299,19 +1297,7 @@ footer .banner-thin {
top: 0;
right: 0;
bottom: 0;
/* #451: width is a CSS variable so the drag handle (added by
Panel.bind) can update it live, and so localStorage-persisted
widths apply on first paint. Default min(760px, 94vw) preserves
the pre-#451 behaviour for operators who never drag. JS sets
`--side-panel-w` via inline style on the drawer; persistence
lives in localStorage (key `hyperhive:side-panel-width`),
replayed onto the var by `Panel.applyStoredWidth` at bind. */
width: var(--side-panel-w, min(760px, 94vw));
/* Clamp so a stored width can never push the drawer off-screen
or shrink it past readability. min content width matches the
panel-head's natural width with the close button visible. */
min-width: 320px;
max-width: 96vw;
width: min(760px, 94vw);
display: flex;
flex-direction: column;
background: var(--bg-elev);
@ -1323,36 +1309,6 @@ footer .banner-thin {
.side-panel.open { pointer-events: auto; }
.side-panel.open .side-panel-backdrop { opacity: 1; }
.side-panel.open .side-panel-drawer { transform: translateX(0); }
/* #451: drag-to-resize handle on the drawer's left edge. The handle
itself is invisible until hover/drag so it doesn't compete with the
2px mauve `border-left` for the visual boundary. Pointer-cursor
tells the operator the edge is grabbable; the brighter glow during
drag (`body.side-panel-resizing`) is the affordance the eye
tracks. */
.side-panel-resize {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 6px;
margin-left: -3px;
cursor: ew-resize;
z-index: 1;
background: transparent;
transition: background 120ms ease;
}
.side-panel-resize:hover,
body.side-panel-resizing .side-panel-resize {
background: rgba(203, 166, 247, 0.55);
}
/* While dragging: kill text selection + force the resize cursor
page-wide so the cursor doesn't flip back to default the moment it
leaves the 6px handle band. */
body.side-panel-resizing {
cursor: ew-resize !important;
user-select: none;
}
body.side-panel-resizing * { cursor: ew-resize !important; }
.side-panel-head {
flex: 0 0 auto;
display: flex;