Skip to content

fix: add more insert edge cases #5078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions apps/builder/app/shared/content-model.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ test("flow accepts flow", () => {
isTreeSatisfyingContentModel({
...renderData(
<$.Body ws:id="bodyId">
<$.Box ws:id="divId"></$.Box>
<$.Box ws:id="articleId" tag="article"></$.Box>
</$.Body>
),
metas: defaultMetas,
Expand Down Expand Up @@ -114,7 +114,7 @@ test("transparent category accepts flow", () => {
...renderData(
<$.Body ws:id="bodyId">
<$.Link ws:id="linkId">
<$.Box ws:id="divId"></$.Box>
<$.Box ws:id="articleId" tag="article"></$.Box>
</$.Link>
</$.Body>
),
Expand Down Expand Up @@ -162,7 +162,7 @@ test("transparent category should pass through phrasing category and forbid flow
<$.Body ws:id="bodyId">
<$.Box ws:id="spanId" tag="span">
<$.Link ws:id="linkId">
<$.Box ws:id="divId"></$.Box>
<$.Box ws:id="articleId" tag="article"></$.Box>
</$.Link>
</$.Box>
</$.Body>
Expand Down Expand Up @@ -213,7 +213,7 @@ test("transparent category should pass through category when check deep in the t
<$.Body ws:id="bodyId">
<$.Box ws:id="spanId" tag="span">
<$.Link ws:id="linkId">
<$.Box ws:id="divId"></$.Box>
<$.Box ws:id="articleId" tag="article"></$.Box>
</$.Link>
</$.Box>
</$.Body>
Expand Down Expand Up @@ -308,14 +308,14 @@ test("prevent nesting interactive instances when check deep in the tree", () =>
...renderData(
<$.Body ws:id="bodyId">
<$.Button ws:id="buttonId">
<$.Box ws:id="divId">
<$.Box ws:id="spanId" tag="span">
<$.Link ws:id="linkId"></$.Link>
</$.Box>
</$.Button>
</$.Body>
),
metas: defaultMetas,
instanceSelector: ["linkId", "divId", "buttonId", "bodyId"],
instanceSelector: ["linkId", "spanId", "buttonId", "bodyId"],
})
).toBeFalsy();
});
Expand Down Expand Up @@ -385,3 +385,35 @@ test("edge case: allow wrapping input with label", () => {
})
).toBeFalsy();
});

test("edge case: allow inserting div where phrasing is required", () => {
expect(
isTreeSatisfyingContentModel({
...renderData(
<$.Body ws:id="bodyId">
<$.Button>
<$.Box></$.Box>
</$.Button>
</$.Body>
),
metas: defaultMetas,
instanceSelector: ["bodyId"],
})
).toBeTruthy();
});

test("edge case: support a > img", () => {
expect(
isTreeSatisfyingContentModel({
...renderData(
<$.Body ws:id="bodyId">
<$.Link>
<$.Image />
</$.Link>
</$.Body>
),
metas: defaultMetas,
instanceSelector: ["bodyId"],
})
).toBeTruthy();
});
23 changes: 17 additions & 6 deletions apps/builder/app/shared/content-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ const isIntersected = (arrayA: string[], arrayB: string[]) => {
return arrayA.some((item) => arrayB.includes(item));
};

/**
* checks if tag has interactive category
* though img is an exception and historically its interactivity ignored
* so img can be put into links and buttons
*/
const isTagInteractive = (tag: string) => {
return tag !== "img" && categoriesByTag[tag].includes("interactive");
};

const isTagSatisfyingContentModel = ({
tag,
allowedCategories,
Expand All @@ -53,16 +62,19 @@ const isTagSatisfyingContentModel = ({
if (allowedCategories.includes(tag)) {
return true;
}
// very big hack to support putting div into buttons or headings
// users put "Embed HTML" all over the place to embed icons
// radix templates do it as well
if (allowedCategories.includes("phrasing") && tag === "div") {
return true;
}
// instance does not match parent constraints
if (isIntersected(allowedCategories, categoriesByTag[tag]) === false) {
return false;
}
// prevent nesting interactive elements
// like button > button or a > input
if (
allowedCategories.includes("non-interactive") &&
categoriesByTag[tag].includes("interactive")
) {
if (allowedCategories.includes("non-interactive") && isTagInteractive(tag)) {
// interactive exception, label > input is not recommended but a popular case
// to automatically focus input when click on label text without using id
if (allowedCategories.includes("label-content") && tag === "input") {
Expand Down Expand Up @@ -96,8 +108,7 @@ const getTagChildrenCategories = (
// like button > button or a > input
if (
tag &&
(categoriesByTag[tag].includes("interactive") ||
allowedCategories?.includes("non-interactive"))
(isTagInteractive(tag) || allowedCategories?.includes("non-interactive"))
) {
childrenCategories = [...childrenCategories, "non-interactive"];
}
Expand Down