Skip to content

Commit 3b5559c

Browse files
committed
fix(studio): keep the live preview attribute in sync with what gets persisted
1 parent e85cf8b commit 3b5559c

3 files changed

Lines changed: 38 additions & 4 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { describe, expect, it } from "vitest";
2+
import { resolveOptimisticAttributeValue } from "./useDomEditAttributeCommits";
3+
4+
describe("resolveOptimisticAttributeValue", () => {
5+
it("removes an HTML boolean attribute when the value is the literal string false", () => {
6+
expect(resolveOptimisticAttributeValue("loop", "false")).toBeNull();
7+
expect(resolveOptimisticAttributeValue("muted", "false")).toBeNull();
8+
});
9+
10+
it("keeps the literal string false for a non-boolean attribute, matching what persist() writes", () => {
11+
expect(resolveOptimisticAttributeValue("data-example", "false")).toBe("false");
12+
});
13+
14+
it("always removes on null regardless of attribute", () => {
15+
expect(resolveOptimisticAttributeValue("loop", null)).toBeNull();
16+
expect(resolveOptimisticAttributeValue("data-example", null)).toBeNull();
17+
});
18+
19+
it("passes any other value through unchanged", () => {
20+
expect(resolveOptimisticAttributeValue("data-example", "true")).toBe("true");
21+
expect(resolveOptimisticAttributeValue("loop", "true")).toBe("true");
22+
});
23+
});

‎packages/studio/src/hooks/useDomEditAttributeCommits.ts‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useCallback, useRef } from "react";
2-
import type { PatchOperation } from "../utils/sourcePatcher";
2+
import { HTML_BOOLEAN_ATTRIBUTES, type PatchOperation } from "../utils/sourcePatcher";
33
import {
44
findElementForSelection,
55
getDomEditTargetKey,
@@ -55,6 +55,14 @@ function resolveFullAttrName(attr: string, prefixData: boolean | undefined): str
5555
return prefixData && !attr.startsWith("data-") ? `data-${attr}` : attr;
5656
}
5757

58+
// Matches sourcePatcher's own boolean handling: "false" means "remove" only
59+
// for HTML_BOOLEAN_ATTRIBUTES (loop, muted, ...), so the live preview node
60+
// ends up holding what persist() actually writes to disk.
61+
export function resolveOptimisticAttributeValue(attr: string, value: string | null): string | null {
62+
if (value === null) return null;
63+
return value === "false" && HTML_BOOLEAN_ATTRIBUTES.has(attr) ? null : value;
64+
}
65+
5866
function setOrRemovePreviewAttribute(
5967
el: HTMLElement,
6068
fullAttr: string,
@@ -393,8 +401,11 @@ export function useDomEditAttributeCommits({
393401
},
394402
apply: () => {
395403
if (!editedElement) return;
396-
const nextValue = value === null || value === "false" ? null : value;
397-
setOrRemovePreviewAttribute(editedElement, attr, nextValue);
404+
setOrRemovePreviewAttribute(
405+
editedElement,
406+
attr,
407+
resolveOptimisticAttributeValue(attr, value),
408+
);
398409
},
399410
persist: () =>
400411
persistDomEditOperations(domEditSelection, [op], {

‎packages/studio/src/utils/sourcePatcher.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ function findMatchingClosingTagIndex(html: string, tagName: string, contentStart
420420
return -1;
421421
}
422422

423-
const HTML_BOOLEAN_ATTRIBUTES = new Set([
423+
export const HTML_BOOLEAN_ATTRIBUTES = new Set([
424424
"loop",
425425
"muted",
426426
"autoplay",

0 commit comments

Comments
 (0)