feat: widgets and ai chat

This commit is contained in:
Rotimi Best
2026-04-27 11:40:38 +01:00
parent ae1801f85f
commit 5717dd8004
391 changed files with 28177 additions and 22467 deletions
@@ -0,0 +1,113 @@
type ResizablePanelDragState = {
hasDragged: boolean;
width: number;
};
type StartResizablePanelDragOptions = {
event: PointerEvent;
startWidth: number;
resolveWidth: (params: { startWidth: number; startX: number; currentX: number; deltaX: number }) => number;
onPreview: (width: number) => void;
onCommit: (state: ResizablePanelDragState) => void;
onDragStart?: () => void;
onDragEnd?: (state: ResizablePanelDragState) => void;
cursor?: string;
dragThreshold?: number;
};
export function startResizablePanelDrag({
event,
startWidth,
resolveWidth,
onPreview,
onCommit,
onDragStart,
onDragEnd,
cursor = 'col-resize',
dragThreshold = 3
}: StartResizablePanelDragOptions) {
const startX = event.clientX;
const previousCursor = document.body.style.cursor;
const previousUserSelect = document.body.style.userSelect;
let hasDragged = false;
let nextWidth = startWidth;
let animationFrame: number | null = null;
let hasStopped = false;
document.body.style.cursor = cursor;
document.body.style.userSelect = 'none';
onDragStart?.();
function flushPreview() {
animationFrame = null;
onPreview(nextWidth);
}
function schedulePreview() {
if (animationFrame !== null) {
return;
}
animationFrame = window.requestAnimationFrame(flushPreview);
}
function stopResize(shouldCommit: boolean) {
if (hasStopped) {
return;
}
hasStopped = true;
if (animationFrame !== null) {
window.cancelAnimationFrame(animationFrame);
animationFrame = null;
onPreview(nextWidth);
}
document.body.style.cursor = previousCursor;
document.body.style.userSelect = previousUserSelect;
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
window.removeEventListener('pointercancel', handlePointerCancel);
const state = { hasDragged, width: nextWidth };
if (shouldCommit) {
onCommit(state);
}
onDragEnd?.(state);
}
function handlePointerMove(moveEvent: PointerEvent) {
const deltaX = moveEvent.clientX - startX;
nextWidth = resolveWidth({
startWidth,
startX,
currentX: moveEvent.clientX,
deltaX
});
if (Math.abs(deltaX) > dragThreshold) {
hasDragged = true;
}
schedulePreview();
}
function handlePointerUp() {
stopResize(true);
}
function handlePointerCancel() {
stopResize(false);
}
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
window.addEventListener('pointercancel', handlePointerCancel);
return () => {
stopResize(false);
};
}