Fix calendar sidebar resize lag at fast drag speeds
Replace per-mousemove setSidebarWidth() calls (triggering full React re-renders including FullCalendar) with direct DOM style mutation during drag. React state is committed only once on mouseup, eliminating all mid-drag re-renders and localStorage writes that caused the lag. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
59c89c904c
commit
ff81ef7c14
@ -61,16 +61,21 @@ export default function CalendarPage() {
|
|||||||
return SIDEBAR_DEFAULT;
|
return SIDEBAR_DEFAULT;
|
||||||
});
|
});
|
||||||
const isResizingRef = useRef(false);
|
const isResizingRef = useRef(false);
|
||||||
|
const sidebarRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const handleSidebarMouseDown = useCallback((e: React.MouseEvent) => {
|
const handleSidebarMouseDown = useCallback((e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
isResizingRef.current = true;
|
isResizingRef.current = true;
|
||||||
const startX = e.clientX;
|
const startX = e.clientX;
|
||||||
const startWidth = sidebarWidth;
|
const startWidth = sidebarWidth;
|
||||||
|
let latestWidth = startWidth;
|
||||||
|
|
||||||
const onMouseMove = (ev: MouseEvent) => {
|
const onMouseMove = (ev: MouseEvent) => {
|
||||||
const newWidth = Math.min(SIDEBAR_MAX, Math.max(SIDEBAR_MIN, startWidth + (ev.clientX - startX)));
|
latestWidth = Math.min(SIDEBAR_MAX, Math.max(SIDEBAR_MIN, startWidth + (ev.clientX - startX)));
|
||||||
setSidebarWidth(newWidth);
|
// Direct DOM mutation — bypasses React entirely during drag, zero re-renders
|
||||||
|
if (sidebarRef.current) {
|
||||||
|
sidebarRef.current.style.width = latestWidth + 'px';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseUp = () => {
|
const onMouseUp = () => {
|
||||||
@ -79,6 +84,8 @@ export default function CalendarPage() {
|
|||||||
document.removeEventListener('mouseup', onMouseUp);
|
document.removeEventListener('mouseup', onMouseUp);
|
||||||
document.body.style.cursor = '';
|
document.body.style.cursor = '';
|
||||||
document.body.style.userSelect = '';
|
document.body.style.userSelect = '';
|
||||||
|
// Single React state commit on release — triggers localStorage persist + final reconciliation
|
||||||
|
setSidebarWidth(latestWidth);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.body.style.cursor = 'col-resize';
|
document.body.style.cursor = 'col-resize';
|
||||||
@ -468,7 +475,7 @@ export default function CalendarPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full overflow-hidden animate-fade-in">
|
<div className="flex h-full overflow-hidden animate-fade-in">
|
||||||
<CalendarSidebar onUseTemplate={handleUseTemplate} onSharedVisibilityChange={setVisibleSharedIds} width={sidebarWidth} />
|
<CalendarSidebar ref={sidebarRef} onUseTemplate={handleUseTemplate} onSharedVisibilityChange={setVisibleSharedIds} width={sidebarWidth} />
|
||||||
<div
|
<div
|
||||||
onMouseDown={handleSidebarMouseDown}
|
onMouseDown={handleSidebarMouseDown}
|
||||||
className="w-1 shrink-0 cursor-col-resize hover:bg-accent/30 active:bg-accent/50 transition-colors duration-150"
|
className="w-1 shrink-0 cursor-col-resize hover:bg-accent/30 active:bg-accent/50 transition-colors duration-150"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback, forwardRef } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { Plus, Pencil, Trash2, FileText } from 'lucide-react';
|
import { Plus, Pencil, Trash2, FileText } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@ -17,7 +17,7 @@ interface CalendarSidebarProps {
|
|||||||
width: number;
|
width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CalendarSidebar({ onUseTemplate, onSharedVisibilityChange, width }: CalendarSidebarProps) {
|
const CalendarSidebar = forwardRef<HTMLDivElement, CalendarSidebarProps>(function CalendarSidebar({ onUseTemplate, onSharedVisibilityChange, width }, ref) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { data: calendars = [], sharedData: sharedCalendars = [] } = useCalendars();
|
const { data: calendars = [], sharedData: sharedCalendars = [] } = useCalendars();
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
@ -94,7 +94,7 @@ export default function CalendarSidebar({ onUseTemplate, onSharedVisibilityChang
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="shrink-0 border-r bg-card flex flex-col" style={{ width }}>
|
<div ref={ref} className="shrink-0 border-r bg-card flex flex-col" style={{ width }}>
|
||||||
<div className="h-16 px-4 border-b flex items-center justify-between shrink-0">
|
<div className="h-16 px-4 border-b flex items-center justify-between shrink-0">
|
||||||
<span className="text-sm font-semibold font-heading text-foreground">Calendars</span>
|
<span className="text-sm font-semibold font-heading text-foreground">Calendars</span>
|
||||||
<Button
|
<Button
|
||||||
@ -220,4 +220,6 @@ export default function CalendarSidebar({ onUseTemplate, onSharedVisibilityChang
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
export default CalendarSidebar;
|
||||||
Loading…
x
Reference in New Issue
Block a user