docs(studio): document timeline keyboard navigation (#3031)

* feat(studio): expose timeline treegrid semantics

* feat(studio): coordinate logical timeline focus

* feat(studio): add timeline keyboard controls

* docs(studio): document timeline keyboard navigation
This commit is contained in:
Miguel Ángel
2026-08-04 17:09:07 -07:00
committed by GitHub
parent 0f1333c80d
commit 7bf425b7a9
41 changed files with 1911 additions and 653 deletions
@@ -8,6 +8,7 @@ import { VIDEO_EXT, IMAGE_EXT } from "../../utils/mediaTypes";
import { TIMELINE_ASSET_MIME } from "../../utils/timelineAssetDrop";
import { ContextMenu } from "./AssetContextMenu";
import { usePlayerStore } from "../../player/store/playerStore";
import { timelineClipFocusId } from "../../player/components/timelineNavigationIdentity";
import { useAssetPreviewStore } from "../../utils/assetPreviewStore";
import { findClipForAsset, isPointerClick } from "../../utils/assetClickBehavior";
import { basename, ext, truncateMiddle, formatDuration } from "./assetHelpers";
@@ -133,7 +134,7 @@ export function AssetCard({
const pointerDownRef = useRef<{ x: number; y: number } | null>(null);
const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
const requestClipReveal = usePlayerStore((s) => s.requestClipReveal);
const requestTimelineFocus = usePlayerStore((s) => s.requestTimelineFocus);
const elements = usePlayerStore((s) => s.elements);
const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
@@ -158,7 +159,7 @@ export function AssetCard({
const clipKey = clip.key ?? clip.id;
setSelectedElementId(clipKey);
// Scroll the timeline so the selected clip is actually visible.
requestClipReveal(clipKey);
requestTimelineFocus(timelineClipFocusId(clipKey));
return;
}
}
@@ -171,7 +172,7 @@ export function AssetCard({
asset,
projectId,
setSelectedElementId,
requestClipReveal,
requestTimelineFocus,
setPreviewAsset,
clearPreviewAsset,
],
@@ -6,6 +6,7 @@ import { usePlayerStore } from "../../player/store/playerStore";
import { useAssetPreviewStore } from "../../utils/assetPreviewStore";
import { findClipForAsset, isPointerClick } from "../../utils/assetClickBehavior";
import { resolveMediaPreviewUrl } from "../../player/components/thumbnailUtils";
import { timelineClipFocusId } from "../../player/components/timelineNavigationIdentity";
export function AudioRow({
projectId,
@@ -43,7 +44,7 @@ export function AudioRow({
// CapCut-style click behavior: drag-threshold gate.
const pointerDownRef = useRef<{ x: number; y: number } | null>(null);
const setSelectedElementId = usePlayerStore((s) => s.setSelectedElementId);
const requestClipReveal = usePlayerStore((s) => s.requestClipReveal);
const requestTimelineFocus = usePlayerStore((s) => s.requestTimelineFocus);
const elements = usePlayerStore((s) => s.elements);
const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
@@ -67,7 +68,7 @@ export function AudioRow({
const clipKey = clip.key ?? clip.id;
setSelectedElementId(clipKey);
// Scroll the timeline so the selected clip is actually visible.
requestClipReveal(clipKey);
requestTimelineFocus(timelineClipFocusId(clipKey));
return;
}
}
@@ -80,7 +81,7 @@ export function AudioRow({
asset,
projectId,
setSelectedElementId,
requestClipReveal,
requestTimelineFocus,
setPreviewAsset,
clearPreviewAsset,
],