fix(editor): drag interactions, undo history, overflow clipping

Four critical fixes:
- Remove overflow:hidden on tlRef so Timeline.init's scroll survives re-renders
- Don't call _renderClips() inside mousedown (was destroying event target mid-drag)
- Use refs for undo history to eliminate stale closure in onClipsChanged callback
- Change .tl-clip-area overflow:hidden to overflow:visible so pointer events reach clip edges
This commit is contained in:
Zac Gaetano 2026-05-24 21:21:23 -04:00
parent 4673efac6a
commit 3dad82d992
2 changed files with 23 additions and 10 deletions

View file

@ -94,7 +94,7 @@
var area = document.createElement('div'); var area = document.createElement('div');
area.className = 'tl-clip-area'; area.className = 'tl-clip-area';
area.dataset.trackId = t.id; area.dataset.trackId = t.id;
area.style.cssText = 'flex:1;position:relative;overflow:hidden;'; area.style.cssText = 'flex:1;position:relative;overflow:visible;';
area.addEventListener('click', _onAreaClick); area.addEventListener('click', _onAreaClick);
row.appendChild(area); row.appendChild(area);
@ -269,8 +269,12 @@
// Drag to move (body only) // Drag to move (body only)
el.addEventListener('mousedown', function (e) { el.addEventListener('mousedown', function (e) {
if (e.target === lh || e.target === rh) return; if (e.target === lh || e.target === rh) return;
e.preventDefault();
s.selectedId = clip._id; s.selectedId = clip._id;
_renderClips(); // Update selection highlight without destroying the element we're dragging
s.tracksEl.querySelectorAll('.tl-clip').forEach(function (c) {
c.style.borderColor = c.dataset.clipId === clip._id ? 'var(--accent)' : 'var(--border-strong)';
});
_onMoveStart(e, clip); _onMoveStart(e, clip);
}); });

View file

@ -34,6 +34,9 @@ function Editor() {
const streamCacheRef = React.useRef({}); const streamCacheRef = React.useRef({});
const tlInitRef = React.useRef(false); const tlInitRef = React.useRef(false);
// Refs so Timeline callbacks always read current values without stale closure issues
const historyRef = React.useRef([[]]);
const historyIdxRef = React.useRef(0);
React.useEffect(() => { React.useEffect(() => {
const data = window.ZAMPP_DATA; const data = window.ZAMPP_DATA;
@ -78,6 +81,8 @@ function Editor() {
setCurrentSeq(seq); setCurrentSeq(seq);
setPlayheadFrames(0); setPlayheadFrames(0);
setSelectedClipId(null); setSelectedClipId(null);
historyRef.current = [clips];
historyIdxRef.current = 0;
setHistory([clips]); setHistory([clips]);
setHistoryIdx(0); setHistoryIdx(0);
setIsDirty(false); setIsDirty(false);
@ -130,9 +135,11 @@ function Editor() {
function handleClipsChanged(clips) { function handleClipsChanged(clips) {
setCurrentSeq(prev => prev ? { ...prev, clips } : prev); setCurrentSeq(prev => prev ? { ...prev, clips } : prev);
const newHistory = history.slice(0, historyIdx + 1); const newHistory = historyRef.current.slice(0, historyIdxRef.current + 1);
newHistory.push(clips.map(c => ({ ...c }))); newHistory.push(clips.map(c => ({ ...c })));
if (newHistory.length > 50) newHistory.shift(); if (newHistory.length > 50) newHistory.shift();
historyRef.current = newHistory;
historyIdxRef.current = newHistory.length - 1;
setHistory(newHistory); setHistory(newHistory);
setHistoryIdx(newHistory.length - 1); setHistoryIdx(newHistory.length - 1);
markDirty(); markDirty();
@ -173,20 +180,22 @@ function Editor() {
} }
function undo() { function undo() {
if (historyIdx <= 0) return; if (historyIdxRef.current <= 0) return;
const idx = historyIdx - 1; const idx = historyIdxRef.current - 1;
historyIdxRef.current = idx;
setHistoryIdx(idx); setHistoryIdx(idx);
const clips = (history[idx] || []).map(c => ({ ...c, _id: _uid() })); const clips = (historyRef.current[idx] || []).map(c => ({ ...c, _id: _uid() }));
setCurrentSeq(prev => prev ? { ...prev, clips } : prev); setCurrentSeq(prev => prev ? { ...prev, clips } : prev);
renderTimelineClips(clips); renderTimelineClips(clips);
markDirty(); markDirty();
} }
function redo() { function redo() {
if (historyIdx >= history.length - 1) return; if (historyIdxRef.current >= historyRef.current.length - 1) return;
const idx = historyIdx + 1; const idx = historyIdxRef.current + 1;
historyIdxRef.current = idx;
setHistoryIdx(idx); setHistoryIdx(idx);
const clips = (history[idx] || []).map(c => ({ ...c, _id: _uid() })); const clips = (historyRef.current[idx] || []).map(c => ({ ...c, _id: _uid() }));
setCurrentSeq(prev => prev ? { ...prev, clips } : prev); setCurrentSeq(prev => prev ? { ...prev, clips } : prev);
renderTimelineClips(clips); renderTimelineClips(clips);
markDirty(); markDirty();
@ -356,7 +365,7 @@ function Editor() {
}} /> }} />
<span style={{ fontSize: 9, color: 'var(--text-tertiary)', fontFamily: 'monospace', width: 28 }}>{scale}px</span> <span style={{ fontSize: 9, color: 'var(--text-tertiary)', fontFamily: 'monospace', width: 28 }}>{scale}px</span>
</div> </div>
<div ref={tlRef} className="timeline-container" style={{ flex: 1, overflow: 'hidden' }} /> <div ref={tlRef} className="timeline-container" style={{ flex: 1, minHeight: 0 }} />
</div> </div>
</div> </div>