diff --git a/packages/studio/src/components/editor/domEditing.ts b/packages/studio/src/components/editor/domEditing.ts index 6cc2b18f7..d8a16344e 100644 --- a/packages/studio/src/components/editor/domEditing.ts +++ b/packages/studio/src/components/editor/domEditing.ts @@ -26,6 +26,7 @@ export { // Layers, text fields, capabilities, selection, patch ops export { buildDefaultDomEditTextField, + buildDomEditPatchTarget, buildDomEditStylePatchOperation, buildDomEditTextPatchOperation, collectDomEditLayerItems, diff --git a/packages/studio/src/components/editor/domEditingLayers.test.ts b/packages/studio/src/components/editor/domEditingLayers.test.ts index 8a39e5441..900b2aea6 100644 --- a/packages/studio/src/components/editor/domEditingLayers.test.ts +++ b/packages/studio/src/components/editor/domEditingLayers.test.ts @@ -1,9 +1,32 @@ // @vitest-environment jsdom import { describe, expect, it } from "vitest"; -import { resolveDomEditSelection } from "./domEditingLayers"; +import { resolveDomEditSelection, buildDomEditPatchTarget } from "./domEditingLayers"; const opts = { activeCompositionPath: "index.html", isMasterView: true, skipSourceProbe: true }; +describe("buildDomEditPatchTarget", () => { + it("includes hfId when selection has hfId", () => { + const target = buildDomEditPatchTarget({ + id: undefined, + hfId: "hf-abc", + selector: ".foo", + selectorIndex: 0, + }); + expect(target.hfId).toBe("hf-abc"); + }); + + it("includes id and selector when hfId absent", () => { + const target = buildDomEditPatchTarget({ + id: "hero", + hfId: undefined, + selector: "#hero", + selectorIndex: undefined, + }); + expect(target.id).toBe("hero"); + expect(target.hfId).toBeUndefined(); + }); +}); + describe("resolveDomEditSelection — hfId from data-hf-id", () => { it("populates hfId from the element data-hf-id attribute", async () => { const el = document.createElement("div"); diff --git a/packages/studio/src/components/editor/domEditingLayers.ts b/packages/studio/src/components/editor/domEditingLayers.ts index e53e3360d..4f3195389 100644 --- a/packages/studio/src/components/editor/domEditingLayers.ts +++ b/packages/studio/src/components/editor/domEditingLayers.ts @@ -555,3 +555,14 @@ export function isTextEditableSelection(selection: DomEditSelection): boolean { } // buildElementAgentPrompt is in domEditingAgentPrompt.ts + +export function buildDomEditPatchTarget( + selection: Pick, +): { id?: string | null; hfId?: string; selector?: string; selectorIndex?: number } { + return { + id: selection.id, + hfId: selection.hfId, + selector: selection.selector, + selectorIndex: selection.selectorIndex, + }; +} diff --git a/packages/studio/src/hooks/useDomEditCommits.ts b/packages/studio/src/hooks/useDomEditCommits.ts index c65e87b88..2d53c1a03 100644 --- a/packages/studio/src/hooks/useDomEditCommits.ts +++ b/packages/studio/src/hooks/useDomEditCommits.ts @@ -5,7 +5,11 @@ import type { PatchOperation } from "../utils/sourcePatcher"; import { trackStudioEvent } from "../utils/studioTelemetry"; import { saveProjectFilesWithHistory } from "../utils/studioFileHistory"; import { primaryFontFamilyValue } from "../utils/studioFontHelpers"; -import { getDomEditTargetKey, type DomEditSelection } from "../components/editor/domEditing"; +import { + buildDomEditPatchTarget, + getDomEditTargetKey, + type DomEditSelection, +} from "../components/editor/domEditing"; import { applyStudioPathOffset, applyStudioBoxSize, @@ -182,11 +186,7 @@ export function useDomEditCommits({ if (options?.shouldSave && !options.shouldSave()) return; - const patchTarget: { id?: string | null; selector?: string; selectorIndex?: number } = { - id: selection.id, - selector: selection.selector, - selectorIndex: selection.selectorIndex, - }; + const patchTarget = buildDomEditPatchTarget(selection); // Mark the save timestamp before the file write so the SSE file-change // handler suppresses the reload even if the event arrives before the @@ -471,16 +471,8 @@ export function useDomEditCommits({ if (typeof originalContent !== "string") throw new Error(`Missing file contents for ${targetPath}`); - const patchTarget: { id?: string; selector?: string; selectorIndex?: number } = selection.id - ? { - id: selection.id, - selector: selection.selector, - selectorIndex: selection.selectorIndex, - } - : selection.selector - ? { selector: selection.selector, selectorIndex: selection.selectorIndex } - : ({} as never); - if (!patchTarget.id && !patchTarget.selector) { + const patchTarget = buildDomEditPatchTarget(selection); + if (!patchTarget.id && !patchTarget.selector && !patchTarget.hfId) { throw new Error("Selected element has no patchable target"); } @@ -561,6 +553,7 @@ export function useDomEditCommits({ { element: entry.element, id: entry.id ?? null, + hfId: entry.element.getAttribute("data-hf-id") ?? undefined, selector: entry.selector, selectorIndex: entry.selectorIndex, sourceFile: entry.sourceFile,