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');
area.className = 'tl-clip-area';
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);
row.appendChild(area);
@ -269,8 +269,12 @@
// Drag to move (body only)
el.addEventListener('mousedown', function (e) {
if (e.target === lh || e.target === rh) return;
e.preventDefault();
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);
});

View file

@ -34,6 +34,9 @@ function Editor() {
const streamCacheRef = React.useRef({});
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(() => {
const data = window.ZAMPP_DATA;
@ -78,6 +81,8 @@ function Editor() {
setCurrentSeq(seq);
setPlayheadFrames(0);
setSelectedClipId(null);
historyRef.current = [clips];
historyIdxRef.current = 0;
setHistory([clips]);
setHistoryIdx(0);
setIsDirty(false);
@ -130,9 +135,11 @@ function Editor() {
function handleClipsChanged(clips) {
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 })));
if (newHistory.length > 50) newHistory.shift();
historyRef.current = newHistory;
historyIdxRef.current = newHistory.length - 1;
setHistory(newHistory);
setHistoryIdx(newHistory.length - 1);
markDirty();
@ -173,20 +180,22 @@ function Editor() {
}
function undo() {
if (historyIdx <= 0) return;
const idx = historyIdx - 1;
if (historyIdxRef.current <= 0) return;
const idx = historyIdxRef.current - 1;
historyIdxRef.current = 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);
renderTimelineClips(clips);
markDirty();
}
function redo() {
if (historyIdx >= history.length - 1) return;
const idx = historyIdx + 1;
if (historyIdxRef.current >= historyRef.current.length - 1) return;
const idx = historyIdxRef.current + 1;
historyIdxRef.current = 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);
renderTimelineClips(clips);
markDirty();
@ -356,7 +365,7 @@ function Editor() {
}} />
<span style={{ fontSize: 9, color: 'var(--text-tertiary)', fontFamily: 'monospace', width: 28 }}>{scale}px</span>
</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>