diff --git a/apps/builder/app/builder/shared/commands.test.tsx b/apps/builder/app/builder/shared/commands.test.tsx
index fdb601764f4e..4ba7965a5467 100644
--- a/apps/builder/app/builder/shared/commands.test.tsx
+++ b/apps/builder/app/builder/shared/commands.test.tsx
@@ -1,7 +1,7 @@
import { describe, expect, test } from "vitest";
import * as baseMetas from "@webstudio-is/sdk-components-react/metas";
import { createDefaultPages } from "@webstudio-is/project-build";
-import { $, renderData } from "@webstudio-is/template";
+import { $, renderData, ws } from "@webstudio-is/template";
import {
$instances,
$pages,
@@ -10,6 +10,7 @@ import {
import { registerContainers } from "~/shared/sync";
import { $awareness, selectInstance } from "~/shared/awareness";
import { deleteSelectedInstance, unwrap, wrapIn } from "./commands";
+import { elementComponent } from "@webstudio-is/sdk";
registerContainers();
@@ -81,71 +82,86 @@ describe("wrap in", () => {
test("wrap instance in link", () => {
$instances.set(
renderData(
- <$.Body ws:id="body">
- <$.Box ws:id="box">$.Box>
- $.Body>
+
+
+
).instances
);
- selectInstance(["box", "body"]);
- wrapIn("Link");
+ selectInstance(["divId", "bodyId"]);
+ wrapIn(elementComponent, "a");
expect($instances.get()).toEqual(
renderData(
- <$.Body ws:id="body">
- <$.Link ws:id={expect.any(String)}>
- <$.Box ws:id="box">$.Box>
- $.Link>
- $.Body>
+
+
+
+
+
).instances
);
});
- test("wrap image in box", () => {
+ test("wrap image in element", () => {
$instances.set(
renderData(
- <$.Body ws:id="body">
- <$.Image ws:id="image" />
- $.Body>
+
+
+
).instances
);
- selectInstance(["image", "body"]);
- wrapIn("Box");
+ selectInstance(["imageId", "bodyId"]);
+ wrapIn(elementComponent);
expect($instances.get()).toEqual(
renderData(
- <$.Body ws:id="body">
- <$.Box ws:id={expect.any(String)}>
- <$.Image ws:id="image" />
- $.Box>
- $.Body>
+
+
+
+
+
).instances
);
});
test("avoid wrapping text with link in link", () => {
const { instances } = renderData(
- <$.Body ws:id="body">
- <$.Text ws:id="text">
- <$.RichTextLink ws:id="richtextlink">$.RichTextLink>
- $.Text>
- $.Body>
+
+
+
+
+
);
$instances.set(instances);
- selectInstance(["text", "body"]);
- wrapIn("Link");
+ selectInstance(["textId", "bodyId"]);
+ wrapIn(elementComponent, "a");
// nothing is changed
expect($instances.get()).toEqual(instances);
});
test("avoid wrapping textual content", () => {
const { instances } = renderData(
- <$.Body ws:id="body">
- <$.Text ws:id="text">
- <$.Bold ws:id="bold">$.Bold>
- $.Text>
- $.Body>
+
+
+
+
+
+ );
+ $instances.set(instances);
+ selectInstance(["boldId", "textId", "bodyId"]);
+ wrapIn(elementComponent);
+ // nothing is changed
+ expect($instances.get()).toEqual(instances);
+ });
+
+ test("avoid wrapping list item", () => {
+ const { instances } = renderData(
+
+
+
+
+
);
$instances.set(instances);
- selectInstance(["bold", "text", "body"]);
- wrapIn("Box");
+ selectInstance(["listItemId", "listId", "bodyId"]);
+ wrapIn(elementComponent);
// nothing is changed
expect($instances.get()).toEqual(instances);
});
diff --git a/apps/builder/app/builder/shared/commands.ts b/apps/builder/app/builder/shared/commands.ts
index 1d2d72883c43..09c63289eaa7 100644
--- a/apps/builder/app/builder/shared/commands.ts
+++ b/apps/builder/app/builder/shared/commands.ts
@@ -1,6 +1,7 @@
import { nanoid } from "nanoid";
import {
blockTemplateComponent,
+ elementComponent,
isComponentDetachable,
} from "@webstudio-is/sdk";
import type { Instance } from "@webstudio-is/sdk";
@@ -51,6 +52,7 @@ import {
} from "~/shared/content-model";
import { generateFragmentFromHtml } from "~/shared/html";
import { generateFragmentFromTailwind } from "~/shared/tailwind/tailwind";
+import { getInstanceLabel } from "./instance-label";
export const $styleSourceInputElement = atom();
@@ -141,7 +143,7 @@ export const deleteSelectedInstance = () => {
});
};
-export const wrapIn = (component: string) => {
+export const wrapIn = (component: string, tag?: string) => {
const instancePath = $selectedInstancePath.get();
// global root or body are selected
if (instancePath === undefined || instancePath.length === 1) {
@@ -170,6 +172,9 @@ export const wrapIn = (component: string) => {
component,
children: [{ type: "id", value: selectedInstance.id }],
};
+ if (tag || elementComponent) {
+ newInstance.tag = tag ?? "div";
+ }
const parentInstance = data.instances.get(parentItem.instance.id);
data.instances.set(newInstanceId, newInstance);
if (parentInstance) {
@@ -179,14 +184,15 @@ export const wrapIn = (component: string) => {
}
}
}
- const matches = isTreeSatisfyingContentModel({
+ const isSatisfying = isTreeSatisfyingContentModel({
instances: data.instances,
props: data.props,
metas,
instanceSelector: newInstanceSelector,
});
- if (matches === false) {
- toast.error(`Cannot wrap in "${component}"`);
+ if (isSatisfying === false) {
+ const label = getInstanceLabel({ component, tag }, {});
+ toast.error(`Cannot wrap in ${label}`);
throw Error("Abort transaction");
}
});
@@ -510,12 +516,12 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
},
},
{
- name: "wrapInBox",
- handler: () => wrapIn("Box"),
+ name: "wrapInElement",
+ handler: () => wrapIn(elementComponent),
},
{
name: "wrapInLink",
- handler: () => wrapIn("Link"),
+ handler: () => wrapIn(elementComponent, "a"),
},
{
name: "unwrap",