fix(studio): bake the group transform into members on ungroup (#1764)

Ungroup only baked the wrapper's layout (left/top), not its GSAP transform — so a
moved group's members snapped back to their creation-time positions. Distribute the
group's static transform onto each member before stripping it: translation is an
exact per-axis add; rotation/scale are composed about the group center so off-center
members don't drift. Animated group transforms are left to be stripped, not baked.
This commit is contained in:
Miguel Ángel
2026-06-27 11:33:45 -04:00
committed by GitHub
parent cf8f8aa568
commit bc5a51a6ee
2 changed files with 176 additions and 13 deletions
@@ -404,6 +404,15 @@ export interface WrapElementsResult {
export interface UnwrapElementsResult {
html: string;
unwrapped: boolean;
/** The unwrapped wrapper's id, so callers can strip GSAP that targeted it
* (the wrapper is gone; a leftover `gsap.set("#id")` would throw at runtime). */
unwrappedGroupId?: string;
/** Members (id'd children) with their absolute layout centres (post un-rebase),
* so the caller can BAKE the group's GSAP transform into each member before
* stripping it — otherwise the group's moves are lost on ungroup. */
members?: Array<{ id: string; cx: number; cy: number }>;
/** The wrapper's layout centre — the pivot for baking the group's rotation/scale. */
groupCenter?: { cx: number; cy: number };
}
export interface ElementRebase {
@@ -554,22 +563,36 @@ export function unwrapElementsFromHtml(
// Undo the rebase: child absolute position = child (rebased) + wrapper origin.
const wLeft = getInlineStylePx(group, "left");
const wTop = getInlineStylePx(group, "top");
const groupCenter = {
cx: wLeft + getInlineStylePx(group, "width") / 2,
cy: wTop + getInlineStylePx(group, "height") / 2,
};
// Move children back to the wrapper's slot, preserving order.
const members: Array<{ id: string; cx: number; cy: number }> = [];
for (const child of Array.from(group.children)) {
if (isHTMLElement(child)) {
setInlineLeftTop(
child,
getInlineStylePx(child, "left") + wLeft,
getInlineStylePx(child, "top") + wTop,
);
const newLeft = getInlineStylePx(child, "left") + wLeft;
const newTop = getInlineStylePx(child, "top") + wTop;
setInlineLeftTop(child, newLeft, newTop);
if (child.id) {
members.push({
id: child.id,
cx: newLeft + getInlineStylePx(child, "width") / 2,
cy: newTop + getInlineStylePx(child, "height") / 2,
});
}
}
parent.insertBefore(child, group);
}
const groupId = group.id || undefined;
group.remove();
return {
html: wrappedFragment ? document.body.innerHTML || "" : document.toString(),
unwrapped: true,
unwrappedGroupId: groupId,
members,
groupCenter,
};
}