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 2d819e3bdf5e..3e4bd4f899b8 100644
--- a/apps/builder/app/builder/features/command-panel/command-panel.tsx
+++ b/apps/builder/app/builder/features/command-panel/command-panel.tsx
@@ -25,13 +25,16 @@ import {
collectionComponent,
parseComponentName,
elementComponent,
+ tags,
} from "@webstudio-is/sdk";
-import type { Breakpoint, Page } from "@webstudio-is/sdk";
+import type { Breakpoint, Instance, Page } from "@webstudio-is/sdk";
import type { TemplateMeta } from "@webstudio-is/template";
import {
$breakpoints,
$editingPageId,
+ $instances,
$pages,
+ $props,
$registeredComponentMetas,
$registeredTemplates,
$selectedBreakpoint,
@@ -44,7 +47,11 @@ import {
} from "~/shared/instance-utils";
import { humanizeString } from "~/shared/string-utils";
import { setCanvasWidth } from "~/builder/features/breakpoints";
-import { $selectedPage, selectPage } from "~/shared/awareness";
+import {
+ $selectedInstancePath,
+ $selectedPage,
+ selectPage,
+} from "~/shared/awareness";
import { mapGroupBy } from "~/shared/shim";
import { setActiveSidebarPanel } from "~/builder/shared/nano-states";
import { $commandMetas } from "~/shared/commands-emitter";
@@ -54,6 +61,7 @@ import {
getInstanceLabel,
InstanceIcon,
} from "~/builder/shared/instance-label";
+import { isTreeSatisfyingContentModel } from "~/shared/content-model";
const $commandPanel = atom<
| undefined
@@ -233,6 +241,101 @@ const ComponentOptionsGroup = ({ options }: { options: ComponentOption[] }) => {
);
};
+type TagOption = {
+ tokens: string[];
+ type: "tag";
+ tag: string;
+};
+
+const $tagOptions = computed(
+ [$selectedInstancePath, $instances, $props, $registeredComponentMetas],
+ (instancePath, instances, props, metas) => {
+ const tagOptions: TagOption[] = [];
+ if (instancePath === undefined) {
+ return tagOptions;
+ }
+ const [{ instance, instanceSelector }] = instancePath;
+ const childInstance: Instance = {
+ type: "instance",
+ id: "new_instance",
+ component: elementComponent,
+ children: [],
+ };
+ const newInstances = new Map(instances);
+ newInstances.set(childInstance.id, childInstance);
+ newInstances.set(instance.id, {
+ ...instance,
+ children: [...instance.children, { type: "id", value: childInstance.id }],
+ });
+ for (const tag of tags) {
+ childInstance.tag = tag;
+ const isSatisfying = isTreeSatisfyingContentModel({
+ instances: newInstances,
+ props,
+ metas,
+ instanceSelector,
+ });
+ if (isSatisfying) {
+ tagOptions.push({
+ tokens: ["tags", tag, `<${tag}>`],
+ type: "tag",
+ tag,
+ });
+ }
+ }
+ return tagOptions;
+ }
+);
+
+const TagOptionsGroup = ({ options }: { options: TagOption[] }) => {
+ return (
+ Tags}
+ actions={["add"]}
+ >
+ {options.map(({ tag }) => {
+ return (
+ {
+ closeCommandPanel();
+ const newInstance: Instance = {
+ type: "instance",
+ id: "new_instance",
+ component: elementComponent,
+ tag,
+ children: [],
+ };
+ insertWebstudioFragmentAt({
+ children: [{ type: "id", value: newInstance.id }],
+ instances: [newInstance],
+ props: [],
+ dataSources: [],
+ styleSourceSelections: [],
+ styleSources: [],
+ styles: [],
+ breakpoints: [],
+ assets: [],
+ resources: [],
+ });
+ }}
+ >
+
+
+
+
+ {`<${tag}>`}
+
+
+ );
+ })}
+
+ );
+};
+
type BreakpointOption = {
tokens: string[];
type: "breakpoint";
@@ -423,12 +526,25 @@ const ShortcutOptionsGroup = ({ options }: { options: ShortcutOption[] }) => {
};
const $options = computed(
- [$componentOptions, $breakpointOptions, $pageOptions, $shortcutOptions],
- (componentOptions, breakpointOptions, pageOptions, commandOptions) => [
+ [
+ $componentOptions,
+ $breakpointOptions,
+ $pageOptions,
+ $shortcutOptions,
+ $tagOptions,
+ ],
+ (
+ componentOptions,
+ breakpointOptions,
+ pageOptions,
+ commandOptions,
+ tagOptions
+ ) => [
...componentOptions,
...breakpointOptions,
...pageOptions,
...commandOptions,
+ ...tagOptions,
]
);
@@ -461,6 +577,14 @@ const CommandDialogContent = () => {
/>
);
}
+ if (group === "tag") {
+ return (
+
+ );
+ }
if (group === "breakpoint") {
return (