File size: 4,232 Bytes
39371ea | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | const STORAGE_KEY = 'pi-column-ratio';
const DEFAULT_RATIO = 1 / 1.83;
export function createColumnResizer(layout, handle) {
const desktop = matchMedia('(min-width: 851px)');
const style = getComputedStyle(layout);
const minimumChat = parseFloat(style.getPropertyValue('--conversation-min'));
const minimumWorkspace = parseFloat(style.getPropertyValue('--workspace-min'));
let ratio = DEFAULT_RATIO, drag, frame = 0;
try {
const saved = Number(localStorage.getItem(STORAGE_KEY));
if (Number.isFinite(saved) && saved > 0 && saved < 1) ratio = saved;
} catch { /* The divider also works when browser storage is unavailable. */ }
function apply() {
if (!desktop.matches) return;
const width = layout.getBoundingClientRect().width;
if (!width) return;
const chat = Math.max(minimumChat, Math.min(width - minimumWorkspace, width * ratio));
layout.style.setProperty('--conversation-width', `${chat}px`);
const percent = Math.round(chat / width * 100);
handle.setAttribute('aria-valuemin', String(Math.round(minimumChat / width * 100)));
handle.setAttribute('aria-valuemax', String(Math.round((width - minimumWorkspace) / width * 100)));
handle.setAttribute('aria-valuenow', String(percent));
handle.setAttribute('aria-valuetext', `Chat ${percent}%, workspace ${100 - percent}%`);
}
function save() { try { localStorage.setItem(STORAGE_KEY, String(ratio)); } catch {} }
function setWidth(pixels) {
const width = layout.getBoundingClientRect().width;
ratio = Math.max(minimumChat, Math.min(width - minimumWorkspace, pixels)) / width;
apply();
}
function flushDrag() {
cancelAnimationFrame(frame); frame = 0;
if (drag) setWidth(drag.x - layout.getBoundingClientRect().left - drag.offset);
}
function finish(commit) {
if (!drag) return;
if (commit) flushDrag();
else { cancelAnimationFrame(frame); frame = 0; ratio = drag.initialRatio; }
const pointer = drag.pointer; drag = undefined;
if (handle.hasPointerCapture(pointer)) handle.releasePointerCapture(pointer);
document.documentElement.classList.remove('is-resizing-columns');
apply(); if (commit) save();
}
function reset() { finish(false); ratio = DEFAULT_RATIO; apply(); save(); }
handle.addEventListener('pointerdown', event => {
if (!desktop.matches || event.button !== 0 || !event.isPrimary || drag) return;
event.preventDefault(); handle.focus({ preventScroll: true });
const boundary = layout.querySelector('.conversation').getBoundingClientRect().right;
drag = { pointer: event.pointerId, initialRatio: ratio, x: event.clientX, offset: event.clientX - boundary };
handle.setPointerCapture(event.pointerId);
document.documentElement.classList.add('is-resizing-columns');
});
handle.addEventListener('pointermove', event => {
if (!drag || event.pointerId !== drag.pointer) return;
drag.x = event.clientX;
if (!frame) frame = requestAnimationFrame(flushDrag);
});
handle.addEventListener('pointerup', event => {
if (!drag || event.pointerId !== drag.pointer) return;
drag.x = event.clientX; finish(true);
});
handle.addEventListener('pointercancel', () => finish(false));
handle.addEventListener('lostpointercapture', () => finish(false));
handle.addEventListener('dblclick', reset);
handle.addEventListener('keydown', event => {
if (!desktop.matches) return;
if (event.key === 'Escape' && drag) { event.preventDefault(); finish(false); return; }
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter'].includes(event.key)) return;
event.preventDefault();
if (event.key === 'Enter') { reset(); return; }
finish(false);
const width = layout.querySelector('.conversation').getBoundingClientRect().width;
const step = event.shiftKey ? 48 : 16;
setWidth(event.key === 'Home' ? 0 : event.key === 'End' ? Infinity : width + (event.key === 'ArrowLeft' ? -step : step));
save();
});
window.addEventListener('blur', () => finish(false));
desktop.addEventListener('change', () => { finish(false); apply(); });
// Keep the preferred ratio on window resize, clamping only its displayed size.
new ResizeObserver(apply).observe(layout);
apply();
}
|