diff --git a/packages/core/src/studio-api/helpers/manualEditsRenderScript.ts b/packages/core/src/studio-api/helpers/manualEditsRenderScript.ts index 976f49df0..e3cc14330 100644 --- a/packages/core/src/studio-api/helpers/manualEditsRenderScript.ts +++ b/packages/core/src/studio-api/helpers/manualEditsRenderScript.ts @@ -25,8 +25,11 @@ export function createStudioPositionSeekReapplyScript(): string { function studioPositionSeekReapplyRuntime(): void { const OFFSET_X_PROP = "--hf-studio-offset-x"; const OFFSET_Y_PROP = "--hf-studio-offset-y"; + const WIDTH_PROP = "--hf-studio-width"; + const HEIGHT_PROP = "--hf-studio-height"; const ROTATION_PROP = "--hf-studio-rotation"; const PATH_OFFSET_ATTR = "data-hf-studio-path-offset"; + const BOX_SIZE_ATTR = "data-hf-studio-box-size"; const ROTATION_ATTR = "data-hf-studio-rotation"; const ORIGINAL_TRANSLATE_ATTR = "data-hf-studio-original-translate"; const ORIGINAL_ROTATE_ATTR = "data-hf-studio-original-rotate"; @@ -36,6 +39,7 @@ function studioPositionSeekReapplyRuntime(): void { if ( !document.querySelector("[" + PATH_OFFSET_ATTR + '="true"]') && + !document.querySelector("[" + BOX_SIZE_ATTR + '="true"]') && !document.querySelector("[" + ROTATION_ATTR + '="true"]') && !document.querySelector("[" + MOTION_ATTR + "]") ) @@ -209,6 +213,15 @@ function studioPositionSeekReapplyRuntime(): void { ); } } + const boxSizeEls = document.querySelectorAll("[" + BOX_SIZE_ATTR + '="true"]'); + for (let i = 0; i < boxSizeEls.length; i++) { + const el = boxSizeEls[i] as HTMLElement; + if (!(el instanceof HTMLElement)) continue; + const w = el.style.getPropertyValue(WIDTH_PROP); + const h = el.style.getPropertyValue(HEIGHT_PROP); + if (w) el.style.setProperty("width", w); + if (h) el.style.setProperty("height", h); + } const rotEls = document.querySelectorAll("[" + ROTATION_ATTR + '="true"]'); for (let i = 0; i < rotEls.length; i++) { const el = rotEls[i] as HTMLElement; diff --git a/packages/producer/src/services/htmlCompiler.ts b/packages/producer/src/services/htmlCompiler.ts index 4b9362404..5894a3e58 100644 --- a/packages/producer/src/services/htmlCompiler.ts +++ b/packages/producer/src/services/htmlCompiler.ts @@ -904,6 +904,7 @@ export async function compileForRender( // positions survive frame-by-frame rendering without a JSON sidecar. const HF_POSITION_ATTRS = [ 'data-hf-studio-path-offset="true"', + 'data-hf-studio-box-size="true"', 'data-hf-studio-rotation="true"', 'data-hf-studio-motion="', ]; diff --git a/packages/studio/src/components/editor/domEditingElement.ts b/packages/studio/src/components/editor/domEditingElement.ts index 4a339c859..7d986234a 100644 --- a/packages/studio/src/components/editor/domEditingElement.ts +++ b/packages/studio/src/components/editor/domEditingElement.ts @@ -12,11 +12,9 @@ import type { import { buildStableSelector, escapeCssString, - getElementDepth, getSelectorIndex, getSourceFileForElement, isHtmlElement, - isTextBearingTag, normalizeTimelineCompositionSource, querySelectorAllSafely, } from "./domEditingDom"; @@ -68,23 +66,6 @@ function hasRenderedBox(el: HTMLElement): boolean { // ─── Visual scoring ────────────────────────────────────────────────────────── -function isEditableTextLeafForScoring(el: HTMLElement): boolean { - return isTextBearingTag(el.tagName.toLowerCase()) && el.children.length === 0; -} - -function getVisualElementScore(el: HTMLElement, pointerStackIndex: number): number { - const tagName = el.tagName.toLowerCase(); - const rect = el.getBoundingClientRect(); - const area = Math.max(1, rect.width * rect.height); - const smallerElementBonus = Math.max(0, 1_000_000 - Math.min(area, 1_000_000)) / 1_000; - const visualLeafBonus = - isEditableTextLeafForScoring(el) || ["img", "video", "canvas", "svg"].includes(tagName) - ? 2_000 - : 0; - - return getElementDepth(el) * 10_000 + visualLeafBonus + smallerElementBonus - pointerStackIndex; -} - // ─── Layer patch target ────────────────────────────────────────────────────── const DOM_LAYER_IGNORED_TAGS = new Set([ @@ -172,25 +153,31 @@ export function resolveVisualDomEditSelectionTarget( elementsFromPoint: Iterable, options: Pick, ): HTMLElement | null { - let best: { element: HTMLElement; score: number } | null = null; - let pointerStackIndex = 0; + const candidates: HTMLElement[] = []; for (const entry of elementsFromPoint) { - if (!isHtmlElement(entry)) { - pointerStackIndex += 1; - continue; - } - + if (!isHtmlElement(entry)) continue; if (hasRenderedBox(entry) && getDomLayerPatchTarget(entry, options.activeCompositionPath)) { - const score = getVisualElementScore(entry, pointerStackIndex); - if (!best || score > best.score) { - best = { element: entry, score }; - } + candidates.push(entry); } - pointerStackIndex += 1; } - return best?.element ?? null; + if (candidates.length === 0) return null; + + // candidates are in visual stacking order (topmost first, from elementsFromPoint). + // Start with the topmost and only replace with a descendant that is more + // specific within the same visual subtree. Never jump to an unrelated + // element that happens to be painted behind the current pick. + let best = candidates[0]; + + for (let i = 1; i < candidates.length; i++) { + const candidate = candidates[i]; + if (best.contains(candidate)) { + best = candidate; + } + } + + return best; } // ─── Raster detection ──────────────────────────────────────────────────────── diff --git a/packages/studio/src/hooks/useDomEditSession.ts b/packages/studio/src/hooks/useDomEditSession.ts index ba0260e06..d33508440 100644 --- a/packages/studio/src/hooks/useDomEditSession.ts +++ b/packages/studio/src/hooks/useDomEditSession.ts @@ -151,7 +151,6 @@ export function useDomEditSession({ setAgentModalOpen, setAgentPromptSelectionContext, setAgentModalAnchorPoint, - preloadAgentPromptSnippet, handleAskAgent, handleAgentModalSubmit, } = useAskAgentModal({ @@ -181,10 +180,6 @@ export function useDomEditSession({ applyDomSelection, resolveDomSelectionFromPreviewPoint, updateDomEditHoverSelection, - preloadAgentPromptSnippet, - setAgentPromptSelectionContext, - setAgentModalAnchorPoint, - setAgentModalOpen, onClickToSource, }); diff --git a/packages/studio/src/hooks/usePreviewInteraction.ts b/packages/studio/src/hooks/usePreviewInteraction.ts index 289acc1e9..0da7e6645 100644 --- a/packages/studio/src/hooks/usePreviewInteraction.ts +++ b/packages/studio/src/hooks/usePreviewInteraction.ts @@ -1,16 +1,8 @@ import { useCallback } from "react"; import { liveTime, usePlayerStore } from "../player"; -import { - getPreviewLocalPointer, - buildRasterClickSelectionContext, - pauseStudioPreviewPlayback, -} from "../utils/studioPreviewHelpers"; +import { pauseStudioPreviewPlayback } from "../utils/studioPreviewHelpers"; import { STUDIO_PREVIEW_SELECTION_ENABLED } from "../components/editor/manualEditingAvailability"; -import { - isLargeRasterDomEditSelection, - type DomEditSelection, -} from "../components/editor/domEditing"; -import type { AgentModalAnchorPoint } from "../utils/studioHelpers"; +import { type DomEditSelection } from "../components/editor/domEditing"; // ── Types ── @@ -32,12 +24,6 @@ export interface UsePreviewInteractionParams { ) => DomEditSelection | null; updateDomEditHoverSelection: (selection: DomEditSelection | null) => void; - // From useAskAgentModal - preloadAgentPromptSnippet: (selection: DomEditSelection) => Promise; - setAgentPromptSelectionContext: (context: string | undefined) => void; - setAgentModalAnchorPoint: (point: AgentModalAnchorPoint | null) => void; - setAgentModalOpen: (open: boolean) => void; - onClickToSource?: (selection: DomEditSelection) => void; } @@ -51,10 +37,6 @@ export function usePreviewInteraction({ applyDomSelection, resolveDomSelectionFromPreviewPoint, updateDomEditHoverSelection, - preloadAgentPromptSnippet, - setAgentPromptSelectionContext, - setAgentModalAnchorPoint, - setAgentModalOpen, onClickToSource, }: UsePreviewInteractionParams) { const handlePreviewCanvasMouseDown = useCallback( @@ -69,37 +51,17 @@ export function usePreviewInteraction({ } e.preventDefault(); e.stopPropagation(); - const localPointer = previewIframeRef.current - ? getPreviewLocalPointer(previewIframeRef.current, e.clientX, e.clientY) - : null; applyDomSelection(nextSelection, { additive: e.shiftKey }); if (!e.shiftKey && e.altKey && onClickToSource) { onClickToSource(nextSelection); } - if ( - !e.shiftKey && - localPointer && - isLargeRasterDomEditSelection(nextSelection, localPointer.viewport) - ) { - setAgentPromptSelectionContext( - buildRasterClickSelectionContext(nextSelection, localPointer), - ); - setAgentModalAnchorPoint({ x: e.clientX, y: e.clientY }); - void preloadAgentPromptSnippet(nextSelection); - setAgentModalOpen(true); - } }, [ applyDomSelection, captionEditMode, compositionLoading, onClickToSource, - preloadAgentPromptSnippet, resolveDomSelectionFromPreviewPoint, - previewIframeRef, - setAgentModalAnchorPoint, - setAgentModalOpen, - setAgentPromptSelectionContext, ], );