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:
@@ -20,13 +20,19 @@ export interface UsePreviewInteractionParams {
|
||||
resolveDomSelectionFromPreviewPoint: (
|
||||
clientX: number,
|
||||
clientY: number,
|
||||
options?: { preferClipAncestor?: boolean; skipSourceProbe?: boolean },
|
||||
options?: {
|
||||
preferClipAncestor?: boolean;
|
||||
skipSourceProbe?: boolean;
|
||||
activeGroupElement?: HTMLElement | null;
|
||||
},
|
||||
) => Promise<DomEditSelection | null>;
|
||||
resolveAllDomSelectionsFromPreviewPoint: (
|
||||
clientX: number,
|
||||
clientY: number,
|
||||
) => Promise<DomEditSelection[]>;
|
||||
updateDomEditHoverSelection: (selection: DomEditSelection | null) => void;
|
||||
/** Drill into a group (double-click on the canvas) so its children become selectable. */
|
||||
setActiveGroupElement: (el: HTMLElement | null) => void;
|
||||
|
||||
onClickToSource?: (selection: DomEditSelection) => void;
|
||||
}
|
||||
@@ -53,6 +59,7 @@ export function usePreviewInteraction({
|
||||
resolveDomSelectionFromPreviewPoint,
|
||||
resolveAllDomSelectionsFromPreviewPoint,
|
||||
updateDomEditHoverSelection,
|
||||
setActiveGroupElement,
|
||||
onClickToSource,
|
||||
}: UsePreviewInteractionParams) {
|
||||
const cycleRef = useRef<ClickCycleState | null>(null);
|
||||
@@ -62,6 +69,24 @@ export function usePreviewInteraction({
|
||||
async (e: React.MouseEvent<HTMLDivElement>, options?: { preferClipAncestor?: boolean }) => {
|
||||
if (!STUDIO_PREVIEW_SELECTION_ENABLED || captionEditMode || compositionLoading) return;
|
||||
|
||||
// Double-click a group → drill into it and select the child under the
|
||||
// pointer (resolve with the group as the explicit drill-in scope, since the
|
||||
// activeGroupElement state hasn't re-rendered yet within this handler).
|
||||
if (e.detail >= 2 && !e.shiftKey) {
|
||||
const hit = await resolveDomSelectionFromPreviewPoint(e.clientX, e.clientY);
|
||||
if (hit?.element.hasAttribute("data-hf-group")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
cycleRef.current = null;
|
||||
setActiveGroupElement(hit.element);
|
||||
const child = await resolveDomSelectionFromPreviewPoint(e.clientX, e.clientY, {
|
||||
activeGroupElement: hit.element,
|
||||
});
|
||||
applyDomSelection(child ?? hit);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const prev = cycleRef.current;
|
||||
const dx = prev ? e.clientX - prev.x : Infinity;
|
||||
@@ -125,6 +150,7 @@ export function usePreviewInteraction({
|
||||
onClickToSource,
|
||||
resolveAllDomSelectionsFromPreviewPoint,
|
||||
resolveDomSelectionFromPreviewPoint,
|
||||
setActiveGroupElement,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user