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,