mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
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:
@@ -26,6 +26,15 @@ describe("applyPatchByTarget", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("removes a boolean data attribute by selector", () => {
|
||||
const html = `<div class="headline clip" data-start="0" data-hidden></div>`;
|
||||
const op: PatchOperation = { type: "attribute", property: "hidden", value: null };
|
||||
|
||||
expect(applyPatchByTarget(html, { selector: ".headline" }, op)).toBe(
|
||||
`<div class="headline clip" data-start="0"></div>`,
|
||||
);
|
||||
});
|
||||
|
||||
it("updates inline z-index by selector when the clip has no DOM id", () => {
|
||||
const html = `<div class="headline clip" style="position: absolute; opacity: 1" data-start="0"></div>`;
|
||||
const op: PatchOperation = { type: "inline-style", property: "z-index", value: "3" };
|
||||
@@ -366,6 +375,7 @@ describe("motion attribute round-trip via sourcePatcher", () => {
|
||||
duration: 0.6,
|
||||
ease: "power2.out",
|
||||
from: { autoAlpha: 0, y: 32 },
|
||||
// fallow-ignore-next-line code-duplication
|
||||
to: { autoAlpha: 1, y: 0 },
|
||||
};
|
||||
|
||||
@@ -420,6 +430,7 @@ describe("motion attribute round-trip via sourcePatcher", () => {
|
||||
duration: 1,
|
||||
ease: "none",
|
||||
from: { opacity: 0 },
|
||||
// fallow-ignore-next-line code-duplication
|
||||
to: { opacity: 1 },
|
||||
};
|
||||
|
||||
|
||||
@@ -323,8 +323,9 @@ function patchAttributeByTarget(
|
||||
|
||||
if (value === null) {
|
||||
// Remove the attribute if present
|
||||
if (!attrPattern.test(tag)) return html;
|
||||
const removePattern = new RegExp(`\\s+${escapeRegex(fullAttr)}=(["'])[^"']*\\1`);
|
||||
const boolAttrPattern = new RegExp(`\\b${escapeRegex(fullAttr)}(?:=(["'])[^"']*\\1)?`);
|
||||
if (!boolAttrPattern.test(tag)) return html;
|
||||
const removePattern = new RegExp(`\\s+${escapeRegex(fullAttr)}(?:=(["'])[^"']*\\1)?`);
|
||||
const newTag = tag.replace(removePattern, "");
|
||||
return replaceTagAtMatch(html, match, newTag);
|
||||
}
|
||||
@@ -357,8 +358,9 @@ function patchAttribute(
|
||||
const attrPattern = new RegExp(`\\b${escapeRegex(fullAttr)}=(["'])([^"']*)\\1`);
|
||||
|
||||
if (value === null) {
|
||||
if (!attrPattern.test(tag)) return html;
|
||||
const removePattern = new RegExp(`\\s+${escapeRegex(fullAttr)}=(["'])[^"']*\\1`);
|
||||
const boolAttrPattern = new RegExp(`\\b${escapeRegex(fullAttr)}(?:=(["'])[^"']*\\1)?`);
|
||||
if (!boolAttrPattern.test(tag)) return html;
|
||||
const removePattern = new RegExp(`\\s+${escapeRegex(fullAttr)}(?:=(["'])[^"']*\\1)?`);
|
||||
const newTag = tag.replace(removePattern, "");
|
||||
return html.replace(tag, newTag);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user