mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
fix(studio): preserve canvas-only group anchors
This commit is contained in:
@@ -48,20 +48,26 @@ export function announceTimelineSelection(
|
||||
);
|
||||
const members = group.map(timelineIdFor).filter((id): id is string => Boolean(id));
|
||||
const anchor = timelineIdFor(primary);
|
||||
const publishedMembers = new Set(members);
|
||||
if (anchor) publishedMembers.add(anchor);
|
||||
const timelineAnchor = anchor ?? members[0] ?? null;
|
||||
// A member with no timeline row of its own resolves to null and is dropped here,
|
||||
// so a group can announce fewer ids than it has — or none, which reads back as an
|
||||
// empty selection and takes the canvas selection with it.
|
||||
logSelect("announce", {
|
||||
group: group.length,
|
||||
published: members.length,
|
||||
published: publishedMembers.size,
|
||||
anchor,
|
||||
anchorPublished: anchor != null && members.includes(anchor),
|
||||
anchorPublished: anchor != null && publishedMembers.has(anchor),
|
||||
});
|
||||
// A canvas target can be editable without owning a timeline row. Preserve that
|
||||
// canvas-only selection when the timeline has nothing truthful to represent.
|
||||
if (!timelineAnchor) return;
|
||||
// A late async primary that already belongs to the live set must preserve the
|
||||
// group. A fresh single click does not belong to it, so publish the singleton
|
||||
// first; otherwise `preserveSet` clears the set and sync wipes the canvas.
|
||||
if (group.length > 1 || !anchor || !getTimelineSelectionSet().has(anchor)) {
|
||||
setTimelineSelectionSet(new Set(members));
|
||||
if (group.length > 1 || !getTimelineSelectionSet().has(timelineAnchor)) {
|
||||
setTimelineSelectionSet(publishedMembers);
|
||||
}
|
||||
setSelectedTimelineElementId(anchor, { preserveSet: true });
|
||||
setSelectedTimelineElementId(timelineAnchor, { preserveSet: true });
|
||||
}
|
||||
|
||||
@@ -144,6 +144,33 @@ describe("useDomSelection marquee", () => {
|
||||
});
|
||||
harness.cleanup();
|
||||
});
|
||||
|
||||
it("uses a surviving group member as the timeline anchor when the canvas primary has no row", () => {
|
||||
const canvasOnly = document.createElement("div");
|
||||
canvasOnly.id = "canvas-only";
|
||||
const card = document.createElement("div");
|
||||
card.id = "card";
|
||||
document.body.append(canvasOnly, card);
|
||||
const harness = renderHarness(
|
||||
{ activeCompPath: "index.html", projectId: "project-1", refreshKey: 0 },
|
||||
{ timelineElements: [timelineElement("card")] },
|
||||
);
|
||||
|
||||
act(() =>
|
||||
harness
|
||||
.current()
|
||||
.applyMarqueeSelection(
|
||||
[makeSelection("Canvas only", canvasOnly), makeSelection("Card", card)],
|
||||
false,
|
||||
),
|
||||
);
|
||||
|
||||
expect(harness.timeline.setTimelineSelectionSet).toHaveBeenCalledWith(new Set(["card"]));
|
||||
expect(harness.timeline.setSelectedTimelineElementId).toHaveBeenCalledWith("card", {
|
||||
preserveSet: true,
|
||||
});
|
||||
harness.cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -482,10 +482,12 @@ export function useDomSelection({
|
||||
[activeCompPath, announceTimelineSelection, buildDomSelectionFromTarget, previewIframeRef],
|
||||
);
|
||||
|
||||
refreshDomEditGroupSelectionsFromPreviewRef.current = refreshDomEditGroupSelectionsFromPreview;
|
||||
|
||||
// ── Effects ──
|
||||
|
||||
useEffect(() => {
|
||||
refreshDomEditGroupSelectionsFromPreviewRef.current = refreshDomEditGroupSelectionsFromPreview;
|
||||
}, [refreshDomEditGroupSelectionsFromPreview]);
|
||||
|
||||
// Clear hover unconditionally on composition/project/preview change
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user