mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
A group renders as its own row with member rows beneath it, and disclosure
splits into two independent controls: caret shows/hides a group's member
rows (structural), `∿` shows/hides any row's automation-lane rows. Plain
tracks lose their caret (nothing to disclose structurally) and keep only
`∿`. `expandedClipIds` keeps its existing keyframe-lane-state job;
`expandedGroupIds`/`expandedLaneOwnerIds` are new, independent sets.
Groups get a real position in the row/geometry pipeline rather than a
visual-only overlay: `useTimelineTrackDerivations` re-emits a group's member
tracks contiguously under a synthetic fractional anchor key
(firstMember - 0.5, the same fractional-key convention sub-composition
expansion already uses), so `rowGeometry`/keyboard-nav/virtualization treat
a group row as a first-class row without widening their key type away from
number. `TimelineLogicalRow.level` widens `1 | 2` to `1 | 2 | 3` (group /
member-under-group / lane), lanes always `owner.level + 1`.
All of it — grouped row emission, the header, the new expansion state — is
gated behind `isCanaryEnabled("audio-groups")`; disabled, `groups` resolves
empty and every new code path no-ops. `TimelineElement.audioGroup` (+
`audioGroupLabel`, resolved once per document via `resolveAudioGroups` from
B1) is parsed unconditionally, mirroring how `hidden`/`fxChain` already
flow DOM → manifest → TimelineElement — inert without the canary.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
43 lines
1.4 KiB
TypeScript
43 lines
1.4 KiB
TypeScript
import type { PropertyGroupName } from "@hyperframes/core/gsap-parser";
|
|
import {
|
|
timelineKeyframeSelectionKey,
|
|
type TimelineKeyframeTarget,
|
|
} from "./timelineKeyframeIdentity";
|
|
|
|
function stableId(kind: string, ...parts: Array<string | number>): string {
|
|
// Attribute-safe by construction; callers embedding it in CSS selectors must use CSS.escape.
|
|
return JSON.stringify(["timeline", kind, ...parts]);
|
|
}
|
|
|
|
export function timelineTrackRowId(track: number): string {
|
|
return stableId("track", track);
|
|
}
|
|
|
|
export function timelineGroupRowId(groupId: string): string {
|
|
return stableId("group", groupId);
|
|
}
|
|
|
|
export function timelinePropertyRowId(elementId: string, group: PropertyGroupName): string {
|
|
return stableId("property", elementId, group);
|
|
}
|
|
|
|
export function timelineLogicalRowCellId(
|
|
lanesId: string,
|
|
rowId: string,
|
|
cell: "header" | "content",
|
|
): string {
|
|
return `${lanesId}-${stableId("cell", rowId, cell)}`;
|
|
}
|
|
|
|
export function timelineClipFocusId(elementId: string): string {
|
|
return stableId("clip", elementId);
|
|
}
|
|
|
|
export function timelineKeyframeFocusId(elementId: string, target: TimelineKeyframeTarget): string {
|
|
return stableId("keyframe", timelineKeyframeSelectionKey(elementId, target));
|
|
}
|
|
|
|
export function timelineEaseFocusId(elementId: string, target: TimelineKeyframeTarget): string {
|
|
return stableId("ease", timelineKeyframeSelectionKey(elementId, target));
|
|
}
|