diff --git a/apps/builder/app/builder/features/command-panel/command-panel.tsx b/apps/builder/app/builder/features/command-panel/command-panel.tsx
index c30eb38cadab..394d7efb4401 100644
--- a/apps/builder/app/builder/features/command-panel/command-panel.tsx
+++ b/apps/builder/app/builder/features/command-panel/command-panel.tsx
@@ -24,6 +24,7 @@ import {
componentCategories,
collectionComponent,
parseComponentName,
+ elementComponent,
} from "@webstudio-is/sdk";
import type { Breakpoint, Page } from "@webstudio-is/sdk";
import type { TemplateMeta } from "@webstudio-is/template";
@@ -162,6 +163,9 @@ const $componentOptions = computed(
) {
continue;
}
+ if (isFeatureEnabled("element") === false && name === elementComponent) {
+ continue;
+ }
const componentMeta = metas.get(name);
const label =
diff --git a/apps/builder/app/builder/features/components/components.tsx b/apps/builder/app/builder/features/components/components.tsx
index 1842dbef5ac3..066d8e13c954 100644
--- a/apps/builder/app/builder/features/components/components.tsx
+++ b/apps/builder/app/builder/features/components/components.tsx
@@ -9,6 +9,7 @@ import {
componentCategories,
collectionComponent,
parseComponentName,
+ elementComponent,
} from "@webstudio-is/sdk";
import {
theme,
@@ -97,6 +98,9 @@ const $metas = computed(
) {
continue;
}
+ if (isFeatureEnabled("element") === false && name === elementComponent) {
+ continue;
+ }
metas.push({
name,
diff --git a/apps/builder/app/builder/features/settings-panel/controls/tag-control.tsx b/apps/builder/app/builder/features/settings-panel/controls/tag-control.tsx
index 9ea05316c02c..91f3d900967c 100644
--- a/apps/builder/app/builder/features/settings-panel/controls/tag-control.tsx
+++ b/apps/builder/app/builder/features/settings-panel/controls/tag-control.tsx
@@ -42,7 +42,7 @@ export const TagControl = ({ meta, prop }: ControlProps<"tag">) => {
}}
getDescription={(item) => (
- {elementsByTag[item].description}
+ {elementsByTag[item]?.description}
)}
/>
diff --git a/apps/builder/app/canvas/features/webstudio-component/webstudio-component.tsx b/apps/builder/app/canvas/features/webstudio-component/webstudio-component.tsx
index c9efa87e3789..a1870961708e 100644
--- a/apps/builder/app/canvas/features/webstudio-component/webstudio-component.tsx
+++ b/apps/builder/app/canvas/features/webstudio-component/webstudio-component.tsx
@@ -27,6 +27,7 @@ import {
blockComponent,
blockTemplateComponent,
getIndexesWithinAncestors,
+ elementComponent,
} from "@webstudio-is/sdk";
import { indexProperty, tagProperty } from "@webstudio-is/sdk/runtime";
import {
@@ -471,10 +472,14 @@ export const WebstudioComponentCanvas = forwardRef<
return <>>;
}
- let Component =
+ let Component: string | AnyComponent =
components.get(instance.component) ??
(MissingComponentStub as AnyComponent);
+ if (instance.component === elementComponent) {
+ Component = instance.tag ?? "div";
+ }
+
if (instance.component === collectionComponent) {
const data = instanceProps.data;
if (data && Array.isArray(data) === false) {
@@ -662,7 +667,13 @@ export const WebstudioComponentPreview = forwardRef<
return <>>;
}
- let Component = components.get(instance.component);
+ let Component: undefined | string | AnyComponent = components.get(
+ instance.component
+ );
+
+ if (instance.component === elementComponent) {
+ Component = instance.tag ?? "div";
+ }
if (instance.component === blockComponent) {
Component = Block;
diff --git a/apps/builder/app/shared/instance-utils.ts b/apps/builder/app/shared/instance-utils.ts
index 9054bb446472..42cdd1b0b89d 100644
--- a/apps/builder/app/shared/instance-utils.ts
+++ b/apps/builder/app/shared/instance-utils.ts
@@ -30,6 +30,7 @@ import {
Prop,
parseComponentName,
Props,
+ elementComponent,
} from "@webstudio-is/sdk";
import {
$props,
@@ -171,14 +172,16 @@ const getLabelFromComponentName = (component: Instance["component"]) => {
};
export const getInstanceLabel = (
- instance: { component: string; label?: string },
+ instance: { component: string; label?: string; tag?: string },
meta: { label?: string }
) => {
- return (
- instance.label ||
- meta.label ||
- getLabelFromComponentName(instance.component)
- );
+ if (instance.label) {
+ return instance.label;
+ }
+ if (instance.component === elementComponent && instance.tag) {
+ return `<${instance.tag}>`;
+ }
+ return meta.label || getLabelFromComponentName(instance.component);
};
export const findAllEditableInstanceSelector = ({
diff --git a/packages/feature-flags/src/flags.ts b/packages/feature-flags/src/flags.ts
index 9c7d30a29b07..60319793f86c 100644
--- a/packages/feature-flags/src/flags.ts
+++ b/packages/feature-flags/src/flags.ts
@@ -5,3 +5,4 @@ export const aiRadixComponents = false;
export const animation = false;
export const videoAnimation = false;
export const resourceProp = false;
+export const element = false;
diff --git a/packages/html-data/bin/elements.ts b/packages/html-data/bin/elements.ts
index 1b6d498ac8f6..1784adca4c78 100644
--- a/packages/html-data/bin/elements.ts
+++ b/packages/html-data/bin/elements.ts
@@ -68,3 +68,20 @@ export const elementsByTag: Record = ${JSON.stringify(elementsB
const contentModelFile = "./src/__generated__/elements.ts";
await mkdir(dirname(contentModelFile), { recursive: true });
await writeFile(contentModelFile, contentModel);
+
+const tags: string[] = [];
+for (const [tag, element] of Object.entries(elementsByTag)) {
+ if (element.categories.includes("metadata")) {
+ continue;
+ }
+ // @todo remove when element insert can adapt to parent
+ if (element.categories.includes("none")) {
+ continue;
+ }
+ tags.push(tag);
+}
+const tagsContent = `export const tags: string[] = ${JSON.stringify(tags, null, 2)};
+`;
+const tagsFile = "../sdk/src/__generated__/tags.ts";
+await mkdir(dirname(tagsFile), { recursive: true });
+await writeFile(tagsFile, tagsContent);
diff --git a/packages/sdk/src/__generated__/tags.ts b/packages/sdk/src/__generated__/tags.ts
new file mode 100644
index 000000000000..9b7e3e4e7863
--- /dev/null
+++ b/packages/sdk/src/__generated__/tags.ts
@@ -0,0 +1,83 @@
+export const tags: string[] = [
+ "a",
+ "abbr",
+ "address",
+ "area",
+ "article",
+ "aside",
+ "audio",
+ "b",
+ "bdi",
+ "bdo",
+ "blockquote",
+ "br",
+ "button",
+ "canvas",
+ "cite",
+ "code",
+ "data",
+ "datalist",
+ "del",
+ "details",
+ "dfn",
+ "dialog",
+ "div",
+ "dl",
+ "em",
+ "embed",
+ "fieldset",
+ "figure",
+ "footer",
+ "form",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "header",
+ "hgroup",
+ "hr",
+ "i",
+ "iframe",
+ "img",
+ "input",
+ "ins",
+ "kbd",
+ "label",
+ "main",
+ "map",
+ "mark",
+ "menu",
+ "meter",
+ "nav",
+ "object",
+ "ol",
+ "output",
+ "p",
+ "picture",
+ "pre",
+ "progress",
+ "q",
+ "ruby",
+ "s",
+ "samp",
+ "search",
+ "section",
+ "select",
+ "slot",
+ "small",
+ "span",
+ "strong",
+ "sub",
+ "sup",
+ "table",
+ "textarea",
+ "th",
+ "time",
+ "u",
+ "ul",
+ "var",
+ "video",
+ "wbr",
+];
diff --git a/packages/sdk/src/core-metas.ts b/packages/sdk/src/core-metas.ts
index 2acba9318f2d..80aab76ec24b 100644
--- a/packages/sdk/src/core-metas.ts
+++ b/packages/sdk/src/core-metas.ts
@@ -4,14 +4,17 @@ import {
PaintBrushIcon,
SettingsIcon,
AddTemplateInstanceIcon,
- HtmlElementIcon,
+ BoxIcon,
} from "@webstudio-is/icons/svg";
import { html } from "./__generated__/normalize.css";
+import * as normalize from "./__generated__/normalize.css";
import type {
WsComponentMeta,
WsComponentPropsMeta,
} from "./schema/component-meta";
import type { Instance } from "./schema/instances";
+import { tagProperty } from "./runtime";
+import { tags } from "./__generated__/tags";
export const rootComponent = "ws:root";
@@ -31,11 +34,20 @@ export const elementComponent = "ws:element";
const elementMeta: WsComponentMeta = {
label: "Element",
- icon: HtmlElementIcon,
+ icon: BoxIcon,
+ // convert [object Module] to [object Object] to enable structured cloning
+ presetStyle: { ...normalize },
};
const elementPropsMeta: WsComponentPropsMeta = {
- props: {},
+ props: {
+ [tagProperty]: {
+ type: "string",
+ control: "tag",
+ required: true,
+ options: tags,
+ },
+ },
};
export const portalComponent = "Slot";
diff --git a/packages/sdk/src/core-templates.tsx b/packages/sdk/src/core-templates.tsx
index 3501ab5ed529..a437a00b17b9 100644
--- a/packages/sdk/src/core-templates.tsx
+++ b/packages/sdk/src/core-templates.tsx
@@ -10,8 +10,17 @@ import {
blockComponent,
collectionComponent,
descendantComponent,
+ elementComponent,
} from "./core-metas";
+const elementMeta: TemplateMeta = {
+ category: "general",
+ order: 0,
+ description:
+ "An HTML element is a core building block for web pages, structuring and displaying content like text, images, and links.",
+ template: ,
+};
+
const collectionItem = new Parameter("collectionItem");
const collectionMeta: TemplateMeta = {
@@ -97,6 +106,7 @@ const blockMeta: TemplateMeta = {
};
export const coreTemplates = {
+ [elementComponent]: elementMeta,
[collectionComponent]: collectionMeta,
[descendantComponent]: descendantMeta,
[blockComponent]: blockMeta,