mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 23:03:09 +00:00
feat(studio): element groups — studio UI (#1759)
* feat(studio): element groups — source mutations Wrap/unwrap source mutations (group geometry, the wrap-elements / unwrap-elements routes) that the studio group feature is built on. Studio UI lands in the next PR. * fix(studio): hoverable group interior + non-sticky drill-in Two group selection bugs with animated members: 1) Empty space inside a group's overlay didn't hover/select the group. Members animated outside the wrapper's static box (110px box vs 340px member union), so elementsFromPoint hit only the full-bleed background there. Add a member-union hit-test fallback: a point inside a group's live member bounds resolves to that group (innermost wins). 2) After drilling into a group and selecting a child, nothing else was selectable — out-of-scope resolved to null. Make drill-in non-sticky: interacting outside the drilled group re-resolves normally and exits the drill-in, so a later click on the group selects it as a unit again.
This commit is contained in:
@@ -81,6 +81,37 @@ function removePointerEventsOverride(style: HTMLStyleElement | null): void {
|
||||
}
|
||||
}
|
||||
|
||||
// Animated group members can move outside their wrapper's static layout box, so
|
||||
// the empty space inside a group's *visual* bounds (the member-union the overlay
|
||||
// draws) doesn't hit-test to the group via elementsFromPoint. Recover it: if the
|
||||
// point falls within a group's live member-union rect, return that wrapper.
|
||||
// Innermost (smallest-area) group wins for nested groups.
|
||||
function findGroupAtPoint(doc: Document, x: number, y: number): HTMLElement | null {
|
||||
let best: HTMLElement | null = null;
|
||||
let bestArea = Infinity;
|
||||
for (const group of Array.from(doc.querySelectorAll<HTMLElement>("[data-hf-group]"))) {
|
||||
let left = Infinity;
|
||||
let top = Infinity;
|
||||
let right = -Infinity;
|
||||
let bottom = -Infinity;
|
||||
for (const member of Array.from(group.children)) {
|
||||
const r = member.getBoundingClientRect();
|
||||
if (r.width === 0 && r.height === 0) continue;
|
||||
left = Math.min(left, r.left);
|
||||
top = Math.min(top, r.top);
|
||||
right = Math.max(right, r.right);
|
||||
bottom = Math.max(bottom, r.bottom);
|
||||
}
|
||||
if (right < left || x < left || x > right || y < top || y > bottom) continue;
|
||||
const area = (right - left) * (bottom - top);
|
||||
if (area < bestArea) {
|
||||
bestArea = area;
|
||||
best = group;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
// fallow-ignore-next-line complexity
|
||||
export function getPreviewTargetFromPointer(
|
||||
iframe: HTMLIFrameElement,
|
||||
@@ -113,6 +144,12 @@ export function getPreviewTargetFromPointer(
|
||||
if (visualTarget) return visualTarget;
|
||||
}
|
||||
|
||||
// No element hit (e.g. empty space inside an animated group's overlay) — fall
|
||||
// back to the group whose member-union contains the point, so the whole group
|
||||
// area is hoverable/selectable, not just where a member currently sits.
|
||||
const groupHit = findGroupAtPoint(doc, localPointer.x, localPointer.y);
|
||||
if (groupHit && getDomLayerPatchTarget(groupHit, activeCompositionPath)) return groupHit;
|
||||
|
||||
const fallback = getEventTargetElement(doc.elementFromPoint(localPointer.x, localPointer.y));
|
||||
if (!fallback || !getDomLayerPatchTarget(fallback, activeCompositionPath)) return null;
|
||||
if (!isElementComputedVisible(fallback)) return null;
|
||||
|
||||
Reference in New Issue
Block a user