Files
cex/frontend/resize.ts
2026-09-07 15:52:46 -04:00

47 lines
3.6 KiB
TypeScript

import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
// Widths stay in effect while switching projects, tabs, and collapsed panels.
export function installSidebarResize(editor: monaco.editor.IStandaloneCodeEditor) {
const main = document.querySelector('main')!;
const left = main.querySelector('aside')!;
const right = document.getElementById('explanation-pane')!;
const handles: { side: 'left' | 'right'; panel: HTMLElement; handle: HTMLDivElement }[] = [];
function limits(side: 'left' | 'right') {
const overlay = window.innerWidth <= 1100 || right.classList.contains('graph-expanded');
const other = side === 'left' ? (right.hidden || overlay ? 0 : right.getBoundingClientRect().width) : (overlay ? 0 : left.getBoundingClientRect().width);
return { min: side === 'left' ? 180 : 300, max: Math.max(side === 'left' ? 180 : 300, Math.min(side === 'left' ? 650 : 1100, main.clientWidth - other - (overlay && side === 'right' ? 40 : 280))) };
}
function widthProperty(side: 'left' | 'right') { return side === 'right' && right.classList.contains('graph-expanded') ? '--expanded-sidebar-width' : `--${side}-sidebar-width`; }
function setWidth(side: 'left' | 'right', width: number) {
const {min,max} = limits(side);
document.documentElement.style.setProperty(widthProperty(side), `${Math.round(Math.max(min,Math.min(max,width)))}px`);
editor.layout(); update();
}
function update() {
for (const {side,panel,handle} of handles) {
const {min,max}=limits(side);
handle.setAttribute('aria-valuemin',String(min));handle.setAttribute('aria-valuemax',String(Math.round(max)));
handle.setAttribute('aria-valuenow',String(Math.round(panel.getBoundingClientRect().width)));
}
}
for (const [side,panel] of [['left',left],['right',right]] as const) {
const handle = document.createElement('div');handle.className=`sidebar-resizer ${side}-resizer`;handle.tabIndex=0;
handle.setAttribute('role','separator');handle.setAttribute('aria-orientation','vertical');
handle.setAttribute('aria-label',`Resize ${side} sidebar`);handle.title='Drag to resize · arrow keys adjust · double-click to reset';
panel.append(handle);handles.push({side,panel,handle});
let drag: {x:number;width:number} | null=null;
handle.onpointerdown=event=>{if(event.button)return;event.preventDefault();drag={x:event.clientX,width:panel.getBoundingClientRect().width};handle.setPointerCapture(event.pointerId);document.body.classList.add('resizing-sidebars');};
handle.onpointermove=event=>{if(drag)setWidth(side,drag.width+(event.clientX-drag.x)*(side==='left'?1:-1));};
const stop=()=>{drag=null;document.body.classList.remove('resizing-sidebars');};
handle.onpointerup=handle.onpointercancel=handle.onlostpointercapture=stop;
handle.ondblclick=()=>{document.documentElement.style.removeProperty(widthProperty(side));editor.layout();update();};
handle.onkeydown=event=>{
if(event.key==='ArrowLeft'||event.key==='ArrowRight') {event.preventDefault();const direction=(event.key==='ArrowRight'?1:-1)*(side==='left'?1:-1);setWidth(side,panel.getBoundingClientRect().width+direction*(event.shiftKey?50:10));}
else if(event.key==='Home'||event.key==='End') {event.preventDefault();const range=limits(side);setWidth(side,event.key==='Home'?range.min:range.max);}
};
}
const observer=new ResizeObserver(update);observer.observe(left);observer.observe(right);
window.addEventListener('resize',()=>{for(const {side,panel} of handles) if(!panel.hidden && document.documentElement.style.getPropertyValue(widthProperty(side))) setWidth(side,panel.getBoundingClientRect().width);});
update();
}