mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +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:
@@ -250,7 +250,7 @@ export function querySelectorAllSafely(doc: Document, selector: string): Element
|
||||
}
|
||||
}
|
||||
|
||||
export function humanizeIdentifier(value: string): string {
|
||||
function humanizeIdentifier(value: string): string {
|
||||
return (
|
||||
value
|
||||
.replace(/\.html$/i, "")
|
||||
@@ -270,10 +270,16 @@ export function buildStableSelector(el: HTMLElement): string | undefined {
|
||||
const compositionId = el.getAttribute("data-composition-id");
|
||||
if (compositionId) return `[data-composition-id="${escapeCssString(compositionId)}"]`;
|
||||
|
||||
// Group wrappers carry no id/class; their data-hf-group value is the unique,
|
||||
// stable handle the source mutations write — use it so the wrapper is
|
||||
// selectable, patchable (move/scale), and addressable for ungroup.
|
||||
const group = el.getAttribute("data-hf-group");
|
||||
if (group) return `[data-hf-group="${escapeCssString(group)}"]`;
|
||||
|
||||
return getPreferredClassSelector(el);
|
||||
}
|
||||
|
||||
export function getPreferredClassSelector(el: HTMLElement): string | undefined {
|
||||
function getPreferredClassSelector(el: HTMLElement): string | undefined {
|
||||
const classes = Array.from(el.classList)
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
@@ -283,6 +289,34 @@ export function getPreferredClassSelector(el: HTMLElement): string | undefined {
|
||||
return preferred ? `.${escapeCssIdentifier(preferred)}` : undefined;
|
||||
}
|
||||
|
||||
// fallow-ignore-next-line complexity
|
||||
export function buildElementLabel(el: HTMLElement): string {
|
||||
const compositionId = el.getAttribute("data-composition-id");
|
||||
if (compositionId && compositionId !== "main") {
|
||||
return humanizeIdentifier(compositionId);
|
||||
}
|
||||
|
||||
const compositionSrc =
|
||||
el.getAttribute("data-composition-src") ?? el.getAttribute("data-composition-file");
|
||||
if (compositionSrc) {
|
||||
return humanizeIdentifier(compositionSrc);
|
||||
}
|
||||
|
||||
const group = el.getAttribute("data-hf-group");
|
||||
if (group) return group;
|
||||
|
||||
if (el.id) return humanizeIdentifier(el.id);
|
||||
|
||||
const preferredClass = getPreferredClassSelector(el);
|
||||
if (preferredClass) {
|
||||
return humanizeIdentifier(preferredClass.replace(/^\./, ""));
|
||||
}
|
||||
|
||||
const text = (el.textContent ?? "").trim().replace(/\s+/g, " ");
|
||||
if (text) return text.length > 40 ? `${text.slice(0, 39)}…` : text;
|
||||
return el.tagName.toLowerCase();
|
||||
}
|
||||
|
||||
export function getSelectorIndex(
|
||||
doc: Document,
|
||||
el: HTMLElement,
|
||||
|
||||
Reference in New Issue
Block a user