feat(studio): timeline revamp with active-clip highlighting and hide controls (#2017)

Timeline UI
- Highlight clips visible at the playhead in the primary color; others share one neutral color
- Minimalist rounded clips, single-color track rows, no gutter icons or superscript labels
- Per-track eye toggle and a per-element hide button in the design panel
- Ruler zoom fixes: sub-second tick intervals and correct label formatting at high zoom
- Sticky gutter so track controls stay visible while scrolling

WYSIWYG visibility (data-hidden)
- Runtime honors data-hidden (display:none), so hiding affects the render, not just the preview
- HTML stays the source of truth; hide state persists and round-trips on reload

Split several studio files to stay under the 600-line cap; pure relocations, no behavior change.
This commit is contained in:
Miguel Ángel
2026-07-07 04:26:56 -04:00
committed by GitHub
parent 5d59835446
commit 037266e72b
56 changed files with 2407 additions and 623 deletions
@@ -28,6 +28,7 @@ export function isElementComputedVisible(el: HTMLElement): boolean {
const VISUAL_LEAF_TAGS = new Set(["img", "video", "canvas", "svg", "audio"]);
// fallow-ignore-next-line complexity
function hasVisualPresence(el: HTMLElement): boolean {
const win = el.ownerDocument.defaultView;
if (!win) return false;
@@ -236,9 +237,13 @@ export function isLargeRasterDomEditSelection(
// ─── Element finders ──────────────────────────────────────────────────────────
type FindElementSelection = Pick<DomEditSelection, "id" | "hfId" | "selector" | "selectorIndex"> & {
sourceFile?: string;
};
export function findElementForSelection(
doc: Document,
selection: Pick<DomEditSelection, "id" | "hfId" | "selector" | "selectorIndex" | "sourceFile">,
selection: FindElementSelection,
activeCompositionPath: string | null = null,
): HTMLElement | null {
if (selection.hfId) {
@@ -259,6 +264,7 @@ export function findElementForSelection(
if (!selection.selector) return null;
// fallow-ignore-next-line code-duplication
if (selection.selector.startsWith(".") && selection.selectorIndex != null) {
const matches = querySelectorAllSafely(doc, selection.selector).filter(
(candidate): candidate is HTMLElement =>
@@ -270,6 +276,7 @@ export function findElementForSelection(
return matches[selection.selectorIndex] ?? null;
}
// fallow-ignore-next-line code-duplication
const matches = querySelectorAllSafely(doc, selection.selector).filter(
(candidate): candidate is HTMLElement =>
isHtmlElement(candidate) &&