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"> - + + + ).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"> - - + + + + + ).instances ); }); - test("wrap image in box", () => { + test("wrap image in element", () => { $instances.set( renderData( - <$.Body ws:id="body"> - <$.Image ws:id="image" /> - + + + ).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" /> - - + + + + + ).instances ); }); test("avoid wrapping text with link in link", () => { const { instances } = renderData( - <$.Body ws:id="body"> - <$.Text ws:id="text"> - <$.RichTextLink ws:id="richtextlink"> - - + + + + + ); $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"> - - + + + + + + ); + $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",