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
@@ -3,6 +3,7 @@ import type { TimelineElement } from "../store/playerStore";
import { defaultTimelineTheme, getClipHandleOpacity, type TimelineTheme } from "./timelineTheme";
import type { TimelineEditCapabilities } from "./timelineEditing";
import { isAudioTimelineElement } from "../../utils/timelineInspector";
import { timelineClipFocusId } from "./timelineNavigationIdentity";
interface TimelineClipProps {
el: TimelineElement;
@@ -18,6 +19,7 @@ interface TimelineClipProps {
capabilities: TimelineEditCapabilities;
theme?: TimelineTheme;
isComposition: boolean;
tabIndex?: 0 | -1;
onHoverStart: () => void;
onHoverEnd: () => void;
onPointerDown?: (e: React.PointerEvent) => void;
@@ -43,6 +45,7 @@ export const TimelineClip = memo(function TimelineClip({
capabilities,
theme = defaultTimelineTheme,
isComposition,
tabIndex = -1,
onHoverStart,
onHoverEnd,
onPointerDown,
@@ -82,19 +85,28 @@ export const TimelineClip = memo(function TimelineClip({
zIndex: isDragging ? 20 : isSelected ? 10 : isHovered ? 5 : 1,
// Regular cursor over clips (CapCut-style, user preference) — no grab hand.
cursor: "default",
appearance: "none",
color: "inherit",
font: "inherit",
padding: 0,
textAlign: "left",
transform: isDragging ? "translateY(-1px)" : undefined,
};
return (
<div
<button
type="button"
data-clip={isGestureActor ? undefined : "true"}
data-el-id={isGestureActor ? undefined : (el.key ?? el.id)}
data-timeline-focus-id={isGestureActor ? undefined : timelineClipFocusId(el.key ?? el.id)}
data-clip-start={el.start}
data-clip-end={el.start + el.duration}
data-clip-hidden={el.hidden ? "true" : undefined}
data-active={isActive ? "" : undefined}
aria-hidden={isGestureActor ? "true" : undefined}
tabIndex={isGestureActor ? undefined : -1}
tabIndex={isGestureActor ? undefined : tabIndex}
aria-label={`${displayLabel}, ${startLabel} to ${endLabel} seconds`}
aria-pressed={isGestureActor ? undefined : isSelected}
className={clipClassName}
style={style}
title={
@@ -176,6 +188,6 @@ export const TimelineClip = memo(function TimelineClip({
</span>
)}
{children}
</div>
</button>
);
});