diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx index 6f9a706..d8fd563 100644 --- a/src/components/video-editor/timeline/Item.tsx +++ b/src/components/video-editor/timeline/Item.tsx @@ -79,10 +79,15 @@ export default function Item({ [span.start, span.end], ); + // Guarantee a minimum clickable width on the outer wrapper so that + // very short items (< 1px) remain visible and interactive. + const MIN_ITEM_PX = 16; + const safeItemStyle = { ...itemStyle, minWidth: MIN_ITEM_PX }; + return (
onSelect?.()} diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx index 2b03685..9480dc5 100644 --- a/src/components/video-editor/timeline/TimelineEditor.tsx +++ b/src/components/video-editor/timeline/TimelineEditor.tsx @@ -116,16 +116,21 @@ function calculateTimelineScale(durationSeconds: number): TimelineScaleConfig { const intervalMs = Math.round(selectedCandidate.intervalSeconds * 1000); const gridMs = Math.round(selectedCandidate.gridSeconds * 1000); - // Set minItemDurationMs to 1ms for maximum granularity - const minItemDurationMs = 1; + // Minimum item duration: at least 500ms or 0.3% of the video, capped at 3s. + // Prevents items from being shrunk to invisible sizes. + const minItemDurationMs = totalMs > 0 + ? Math.min(Math.max(500, Math.round(totalMs * 0.003)), 3000) + : 500; const defaultItemDurationMs = Math.min( Math.max(minItemDurationMs, intervalMs * 2), totalMs > 0 ? totalMs : intervalMs * 2, ); + // Minimum visible range: at least 2s or 0.5% of the video, capped at 15s. + // Decoupled from intervalMs so long videos can still zoom in deeply. const minVisibleRangeMs = totalMs > 0 - ? Math.min(Math.max(intervalMs * 3, minItemDurationMs * 6, 1000), totalMs) - : Math.max(intervalMs * 3, minItemDurationMs * 6, 1000); + ? Math.max(2000, Math.min(Math.round(totalMs * 0.005), 15000)) + : 2000; return { intervalMs, diff --git a/src/components/video-editor/timeline/TimelineWrapper.tsx b/src/components/video-editor/timeline/TimelineWrapper.tsx index 35685b3..37ebe94 100644 --- a/src/components/video-editor/timeline/TimelineWrapper.tsx +++ b/src/components/video-editor/timeline/TimelineWrapper.tsx @@ -135,7 +135,10 @@ export default function TimelineWrapper({ const activeItemId = event.active.id as string; let clampedSpan = clampSpanToBounds(updatedSpan); - if (clampedSpan.end - clampedSpan.start < Math.min(minItemDurationMs, totalMs || minItemDurationMs)) { + const effectiveMinDuration = totalMs > 0 + ? Math.min(minItemDurationMs, totalMs) + : minItemDurationMs; + if (clampedSpan.end - clampedSpan.start < effectiveMinDuration) { return; }