@btst/stack 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/packages/ui/src/components/minimal-tiptap/utils.cjs +15 -11
  2. package/dist/packages/ui/src/components/minimal-tiptap/utils.mjs +15 -11
  3. package/dist/packages/ui/src/components/ui-builder/index.cjs +9 -7
  4. package/dist/packages/ui/src/components/ui-builder/index.mjs +9 -7
  5. package/dist/packages/ui/src/components/ui-builder/internal/canvas/auto-frame.cjs +6 -3
  6. package/dist/packages/ui/src/components/ui-builder/internal/canvas/auto-frame.mjs +6 -3
  7. package/dist/packages/ui/src/components/ui-builder/internal/components/add-component-popover.cjs +228 -48
  8. package/dist/packages/ui/src/components/ui-builder/internal/components/add-component-popover.mjs +228 -48
  9. package/dist/packages/ui/src/components/ui-builder/internal/components/element-selector.cjs +1 -1
  10. package/dist/packages/ui/src/components/ui-builder/internal/components/element-selector.mjs +1 -1
  11. package/dist/packages/ui/src/components/ui-builder/internal/components/error-fallback.cjs +4 -2
  12. package/dist/packages/ui/src/components/ui-builder/internal/components/error-fallback.mjs +4 -2
  13. package/dist/packages/ui/src/components/ui-builder/internal/components/multi-select.cjs +6 -3
  14. package/dist/packages/ui/src/components/ui-builder/internal/components/multi-select.mjs +6 -3
  15. package/dist/packages/ui/src/components/ui-builder/internal/dnd/draggable-new-component.cjs +67 -0
  16. package/dist/packages/ui/src/components/ui-builder/internal/dnd/draggable-new-component.mjs +62 -0
  17. package/dist/packages/ui/src/components/ui-builder/internal/dnd/drop-zone.cjs +181 -37
  18. package/dist/packages/ui/src/components/ui-builder/internal/dnd/drop-zone.mjs +181 -38
  19. package/dist/packages/ui/src/components/ui-builder/internal/editor-panel.cjs +1 -1
  20. package/dist/packages/ui/src/components/ui-builder/internal/editor-panel.mjs +1 -1
  21. package/dist/packages/ui/src/components/ui-builder/internal/form-fields/classname-control/classname-group-control.cjs +1 -1
  22. package/dist/packages/ui/src/components/ui-builder/internal/form-fields/classname-control/classname-group-control.mjs +1 -1
  23. package/dist/packages/ui/src/components/ui-builder/internal/form-fields/classname-control/classname-item-control.cjs +9 -2
  24. package/dist/packages/ui/src/components/ui-builder/internal/form-fields/classname-control/classname-item-control.mjs +9 -2
  25. package/dist/packages/ui/src/components/ui-builder/internal/form-fields/iconname-field.cjs +3 -2
  26. package/dist/packages/ui/src/components/ui-builder/internal/form-fields/iconname-field.mjs +3 -2
  27. package/dist/packages/ui/src/components/ui-builder/internal/layers-panel.cjs +1 -1
  28. package/dist/packages/ui/src/components/ui-builder/internal/layers-panel.mjs +1 -1
  29. package/dist/packages/ui/src/components/ui-builder/internal/props-panel.cjs +17 -5
  30. package/dist/packages/ui/src/components/ui-builder/internal/props-panel.mjs +17 -5
  31. package/dist/packages/ui/src/components/ui-builder/internal/utils/render-utils.cjs +70 -16
  32. package/dist/packages/ui/src/components/ui-builder/internal/utils/render-utils.mjs +73 -20
  33. package/dist/packages/ui/src/lib/ui-builder/context/dnd-context-colission-utils.cjs +14 -9
  34. package/dist/packages/ui/src/lib/ui-builder/context/dnd-context-colission-utils.mjs +14 -9
  35. package/dist/packages/ui/src/lib/ui-builder/context/dnd-context.cjs +38 -10
  36. package/dist/packages/ui/src/lib/ui-builder/context/dnd-context.mjs +35 -11
  37. package/dist/packages/ui/src/lib/ui-builder/context/dnd-contexts.cjs +1 -0
  38. package/dist/packages/ui/src/lib/ui-builder/context/dnd-contexts.mjs +1 -0
  39. package/dist/packages/ui/src/lib/ui-builder/context/drag-overlay.cjs +7 -4
  40. package/dist/packages/ui/src/lib/ui-builder/context/drag-overlay.mjs +7 -4
  41. package/dist/packages/ui/src/lib/ui-builder/hooks/use-auto-scroll.cjs +4 -4
  42. package/dist/packages/ui/src/lib/ui-builder/hooks/use-auto-scroll.mjs +4 -4
  43. package/dist/packages/ui/src/lib/ui-builder/hooks/use-dnd-event-handlers.cjs +53 -16
  44. package/dist/packages/ui/src/lib/ui-builder/hooks/use-dnd-event-handlers.mjs +53 -16
  45. package/dist/packages/ui/src/lib/ui-builder/hooks/use-drop-validation.cjs +23 -7
  46. package/dist/packages/ui/src/lib/ui-builder/hooks/use-drop-validation.mjs +23 -7
  47. package/dist/packages/ui/src/lib/ui-builder/registry/form-field-overrides.cjs +110 -11
  48. package/dist/packages/ui/src/lib/ui-builder/registry/form-field-overrides.mjs +111 -13
  49. package/dist/packages/ui/src/lib/ui-builder/store/editor-store.cjs +3 -2
  50. package/dist/packages/ui/src/lib/ui-builder/store/editor-store.mjs +3 -2
  51. package/dist/packages/ui/src/lib/ui-builder/store/layer-store.cjs +53 -7
  52. package/dist/packages/ui/src/lib/ui-builder/store/layer-store.mjs +54 -8
  53. package/dist/packages/ui/src/lib/ui-builder/store/layer-utils.cjs +4 -3
  54. package/dist/packages/ui/src/lib/ui-builder/store/layer-utils.mjs +4 -3
  55. package/dist/packages/ui/src/lib/ui-builder/utils/variable-resolver.cjs +12 -0
  56. package/dist/packages/ui/src/lib/ui-builder/utils/variable-resolver.mjs +12 -1
  57. package/dist/plugins/ui-builder/client/components/index.d.cts +1 -1
  58. package/dist/plugins/ui-builder/client/components/index.d.mts +1 -1
  59. package/dist/plugins/ui-builder/client/components/index.d.ts +1 -1
  60. package/dist/plugins/ui-builder/client/hooks/index.d.cts +2 -2
  61. package/dist/plugins/ui-builder/client/hooks/index.d.mts +2 -2
  62. package/dist/plugins/ui-builder/client/hooks/index.d.ts +2 -2
  63. package/dist/plugins/ui-builder/client/index.d.cts +17 -7
  64. package/dist/plugins/ui-builder/client/index.d.mts +17 -7
  65. package/dist/plugins/ui-builder/client/index.d.ts +17 -7
  66. package/dist/plugins/ui-builder/index.d.cts +2 -2
  67. package/dist/plugins/ui-builder/index.d.mts +2 -2
  68. package/dist/plugins/ui-builder/index.d.ts +2 -2
  69. package/dist/shared/{stack.BSM2cgoq.d.cts → stack.BYysGdHl.d.cts} +1 -1
  70. package/dist/shared/{stack.CqfZWfjJ.d.cts → stack.BdJFrdyt.d.cts} +8 -2
  71. package/dist/shared/{stack.e1FN86dE.d.mts → stack.ChVuHi5e.d.mts} +8 -2
  72. package/dist/shared/{stack.CLtOoAqF.d.mts → stack.DYCFcnkL.d.mts} +1 -1
  73. package/dist/shared/{stack.MMntCVZZ.d.ts → stack.EhM4pmtN.d.ts} +8 -2
  74. package/dist/shared/{stack.BD1m-4yB.d.ts → stack.kFbDspnF.d.ts} +1 -1
  75. package/package.json +1 -1
@@ -93,23 +93,27 @@ const validateFileOrBase64 = (input, options, originalFile, validFiles, errors)
93
93
  };
94
94
  const checkTypeAndSize = (input, { allowedMimeTypes, maxFileSize }) => {
95
95
  const mimeType = input instanceof File ? input.type : base64MimeType(input);
96
- const size = input instanceof File ? input.size : getBase64Size(input);
96
+ let size;
97
+ if (input instanceof File) {
98
+ size = input.size;
99
+ } else {
100
+ const base64Data = input.split(",")[1];
101
+ if (!base64Data || base64Data.trim() === "") {
102
+ return { isValidType: true, isValidSize: false };
103
+ }
104
+ try {
105
+ size = atob(base64Data).length;
106
+ } catch {
107
+ return { isValidType: true, isValidSize: false };
108
+ }
109
+ }
97
110
  const isValidType = allowedMimeTypes.length === 0 || allowedMimeTypes.includes(mimeType) || allowedMimeTypes.includes(`${mimeType.split("/")[0]}/*`);
98
111
  const isValidSize = !maxFileSize || size <= maxFileSize;
99
112
  return { isValidType, isValidSize };
100
113
  };
101
- const getBase64Size = (input) => {
102
- if (input.includes(",")) {
103
- const base64Part = input.split(",")[1];
104
- if (base64Part) {
105
- return atob(base64Part).length;
106
- }
107
- }
108
- return atob(input).length;
109
- };
110
114
  const base64MimeType = (encoded) => {
111
115
  const result = encoded.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,.*/);
112
- return result && result.length > 1 ? result[1] : "unknown";
116
+ return result && result.length > 1 ? result[1] ?? "unknown" : "unknown";
113
117
  };
114
118
  const isBase64 = (str) => {
115
119
  if (str.startsWith("data:")) {
@@ -91,23 +91,27 @@ const validateFileOrBase64 = (input, options, originalFile, validFiles, errors)
91
91
  };
92
92
  const checkTypeAndSize = (input, { allowedMimeTypes, maxFileSize }) => {
93
93
  const mimeType = input instanceof File ? input.type : base64MimeType(input);
94
- const size = input instanceof File ? input.size : getBase64Size(input);
94
+ let size;
95
+ if (input instanceof File) {
96
+ size = input.size;
97
+ } else {
98
+ const base64Data = input.split(",")[1];
99
+ if (!base64Data || base64Data.trim() === "") {
100
+ return { isValidType: true, isValidSize: false };
101
+ }
102
+ try {
103
+ size = atob(base64Data).length;
104
+ } catch {
105
+ return { isValidType: true, isValidSize: false };
106
+ }
107
+ }
95
108
  const isValidType = allowedMimeTypes.length === 0 || allowedMimeTypes.includes(mimeType) || allowedMimeTypes.includes(`${mimeType.split("/")[0]}/*`);
96
109
  const isValidSize = !maxFileSize || size <= maxFileSize;
97
110
  return { isValidType, isValidSize };
98
111
  };
99
- const getBase64Size = (input) => {
100
- if (input.includes(",")) {
101
- const base64Part = input.split(",")[1];
102
- if (base64Part) {
103
- return atob(base64Part).length;
104
- }
105
- }
106
- return atob(input).length;
107
- };
108
112
  const base64MimeType = (encoded) => {
109
113
  const result = encoded.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,.*/);
110
- return result && result.length > 1 ? result[1] : "unknown";
114
+ return result && result.length > 1 ? result[1] ?? "unknown" : "unknown";
111
115
  };
112
116
  const isBase64 = (str) => {
113
117
  if (str.startsWith("data:")) {
@@ -38,7 +38,8 @@ const UIBuilder = ({
38
38
  allowPagesDeletion = true,
39
39
  navLeftChildren,
40
40
  navRightChildren,
41
- showExport = true
41
+ showExport = true,
42
+ blocks
42
43
  }) => {
43
44
  const layerStore$1 = useStore.useStore(layerStore.useLayerStore, (state) => state);
44
45
  const editorStore$1 = useStore.useStore(editorStore.useEditorStore, (state) => state);
@@ -57,7 +58,7 @@ const UIBuilder = ({
57
58
  }, [userPanelConfig, memoizedDefaultTabsContent, navLeftChildren, navRightChildren, showExport]);
58
59
  React.useEffect(() => {
59
60
  if (editorStore$1 && componentRegistry && !editorStoreInitialized) {
60
- editorStore$1.initialize(componentRegistry, persistLayerStore, allowPagesCreation, allowPagesDeletion, allowVariableEditing);
61
+ editorStore$1.initialize(componentRegistry, persistLayerStore, allowPagesCreation, allowPagesDeletion, allowVariableEditing, blocks);
61
62
  setEditorStoreInitialized(true);
62
63
  }
63
64
  }, [
@@ -67,7 +68,8 @@ const UIBuilder = ({
67
68
  persistLayerStore,
68
69
  allowPagesCreation,
69
70
  allowPagesDeletion,
70
- allowVariableEditing
71
+ allowVariableEditing,
72
+ blocks
71
73
  ]);
72
74
  React.useEffect(() => {
73
75
  if (layerStore$1 && editorStore$1) {
@@ -144,11 +146,11 @@ function MainLayout({ panelConfig }) {
144
146
  });
145
147
  React.useEffect(() => {
146
148
  const editorPanel = mainPanels.find((panel) => panel.title === "UI Editor");
147
- const currentPanel = mainPanels.find((panel) => panel.title === selectedPanel.title);
149
+ const currentPanel = mainPanels.find((panel) => panel.title === selectedPanel?.title);
148
150
  if (!currentPanel) {
149
151
  setSelectedPanel(editorPanel || mainPanels[0]);
150
152
  }
151
- }, [mainPanels, selectedPanel.title]);
153
+ }, [mainPanels, selectedPanel?.title]);
152
154
  const handlePanelClickById = React.useCallback((e) => {
153
155
  const panelIndex = parseInt(e.currentTarget.dataset.panelIndex || "0");
154
156
  setSelectedPanel(mainPanels[panelIndex]);
@@ -180,11 +182,11 @@ function MainLayout({ panelConfig }) {
180
182
  }
181
183
  ) }),
182
184
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex size-full flex-col md:hidden overflow-hidden ", children: [
183
- selectedPanel.content,
185
+ selectedPanel?.content,
184
186
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-4 left-4 right-4 z-50", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center rounded-full bg-primary p-2 shadow-lg", children: mainPanels.map((panel, index) => /* @__PURE__ */ jsxRuntime.jsx(
185
187
  button.Button,
186
188
  {
187
- variant: selectedPanel.title !== panel.title ? "default" : "secondary",
189
+ variant: selectedPanel?.title !== panel.title ? "default" : "secondary",
188
190
  size: "sm",
189
191
  className: "flex-1",
190
192
  "data-panel-index": index,
@@ -30,7 +30,8 @@ const UIBuilder = ({
30
30
  allowPagesDeletion = true,
31
31
  navLeftChildren,
32
32
  navRightChildren,
33
- showExport = true
33
+ showExport = true,
34
+ blocks
34
35
  }) => {
35
36
  const layerStore = useStore(useLayerStore, (state) => state);
36
37
  const editorStore = useStore(useEditorStore, (state) => state);
@@ -49,7 +50,7 @@ const UIBuilder = ({
49
50
  }, [userPanelConfig, memoizedDefaultTabsContent, navLeftChildren, navRightChildren, showExport]);
50
51
  useEffect(() => {
51
52
  if (editorStore && componentRegistry && !editorStoreInitialized) {
52
- editorStore.initialize(componentRegistry, persistLayerStore, allowPagesCreation, allowPagesDeletion, allowVariableEditing);
53
+ editorStore.initialize(componentRegistry, persistLayerStore, allowPagesCreation, allowPagesDeletion, allowVariableEditing, blocks);
53
54
  setEditorStoreInitialized(true);
54
55
  }
55
56
  }, [
@@ -59,7 +60,8 @@ const UIBuilder = ({
59
60
  persistLayerStore,
60
61
  allowPagesCreation,
61
62
  allowPagesDeletion,
62
- allowVariableEditing
63
+ allowVariableEditing,
64
+ blocks
63
65
  ]);
64
66
  useEffect(() => {
65
67
  if (layerStore && editorStore) {
@@ -136,11 +138,11 @@ function MainLayout({ panelConfig }) {
136
138
  });
137
139
  useEffect(() => {
138
140
  const editorPanel = mainPanels.find((panel) => panel.title === "UI Editor");
139
- const currentPanel = mainPanels.find((panel) => panel.title === selectedPanel.title);
141
+ const currentPanel = mainPanels.find((panel) => panel.title === selectedPanel?.title);
140
142
  if (!currentPanel) {
141
143
  setSelectedPanel(editorPanel || mainPanels[0]);
142
144
  }
143
- }, [mainPanels, selectedPanel.title]);
145
+ }, [mainPanels, selectedPanel?.title]);
144
146
  const handlePanelClickById = useCallback((e) => {
145
147
  const panelIndex = parseInt(e.currentTarget.dataset.panelIndex || "0");
146
148
  setSelectedPanel(mainPanels[panelIndex]);
@@ -172,11 +174,11 @@ function MainLayout({ panelConfig }) {
172
174
  }
173
175
  ) }),
174
176
  /* @__PURE__ */ jsxs("div", { className: "flex size-full flex-col md:hidden overflow-hidden ", children: [
175
- selectedPanel.content,
177
+ selectedPanel?.content,
176
178
  /* @__PURE__ */ jsx("div", { className: "absolute bottom-4 left-4 right-4 z-50", children: /* @__PURE__ */ jsx("div", { className: "flex justify-center rounded-full bg-primary p-2 shadow-lg", children: mainPanels.map((panel, index) => /* @__PURE__ */ jsx(
177
179
  Button,
178
180
  {
179
- variant: selectedPanel.title !== panel.title ? "default" : "secondary",
181
+ variant: selectedPanel?.title !== panel.title ? "default" : "secondary",
180
182
  size: "sm",
181
183
  className: "flex-1",
182
184
  "data-panel-index": index,
@@ -97,7 +97,10 @@ const CopyHostStyles = ({
97
97
  console.log(
98
98
  `Tried to add an element that was already mirrored. Updating instead...`
99
99
  );
100
- elements[index$1].mirror.innerText = el.innerText;
100
+ const element = elements[index$1];
101
+ if (element?.mirror) {
102
+ element.mirror.innerText = el.innerText;
103
+ }
101
104
  return;
102
105
  }
103
106
  const mirror = await mirrorEl(el);
@@ -165,13 +168,13 @@ const CopyHostStyles = ({
165
168
  const parentComputedStyle = win.parent.getComputedStyle(parentHtml);
166
169
  for (let i = 0; i < parentComputedStyle.length; i++) {
167
170
  const property = parentComputedStyle[i];
168
- if (property.startsWith("--")) {
171
+ if (property?.startsWith("--")) {
169
172
  const value = parentComputedStyle.getPropertyValue(property);
170
173
  doc.documentElement.style.setProperty(property, value);
171
174
  doc.body.style.setProperty(property, value);
172
175
  }
173
176
  }
174
- parentBody.classList.forEach((className) => {
177
+ parentBody?.classList.forEach((className) => {
175
178
  if (className.includes("font-") || className === "antialiased" || className.includes("__variable")) {
176
179
  doc.body.classList.add(className);
177
180
  }
@@ -93,7 +93,10 @@ const CopyHostStyles = ({
93
93
  console.log(
94
94
  `Tried to add an element that was already mirrored. Updating instead...`
95
95
  );
96
- elements[index].mirror.innerText = el.innerText;
96
+ const element = elements[index];
97
+ if (element?.mirror) {
98
+ element.mirror.innerText = el.innerText;
99
+ }
97
100
  return;
98
101
  }
99
102
  const mirror = await mirrorEl(el);
@@ -161,13 +164,13 @@ const CopyHostStyles = ({
161
164
  const parentComputedStyle = win.parent.getComputedStyle(parentHtml);
162
165
  for (let i = 0; i < parentComputedStyle.length; i++) {
163
166
  const property = parentComputedStyle[i];
164
- if (property.startsWith("--")) {
167
+ if (property?.startsWith("--")) {
165
168
  const value = parentComputedStyle.getPropertyValue(property);
166
169
  doc.documentElement.style.setProperty(property, value);
167
170
  doc.body.style.setProperty(property, value);
168
171
  }
169
172
  }
170
- parentBody.classList.forEach((className) => {
173
+ parentBody?.classList.forEach((className) => {
171
174
  if (className.includes("font-") || className === "antialiased" || className.includes("__variable")) {
172
175
  doc.body.classList.add(className);
173
176
  }
@@ -10,6 +10,7 @@ const editorStore = require('../../../../lib/ui-builder/store/editor-store.cjs')
10
10
  const utils = require('../../../../lib/utils.cjs');
11
11
  const layerRenderer = require('../../layer-renderer.cjs');
12
12
  const layerUtils = require('../../../../lib/ui-builder/store/layer-utils.cjs');
13
+ const draggableNewComponent = require('../dnd/draggable-new-component.cjs');
13
14
 
14
15
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
15
16
 
@@ -17,22 +18,44 @@ const React__default = /*#__PURE__*/_interopDefaultCompat(React);
17
18
 
18
19
  const fallback = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full bg-muted rounded border animate-pulse" });
19
20
  const previewLayerCache = /* @__PURE__ */ new Map();
21
+ function isValidChildOfParent(componentRegistry, componentType, parentType) {
22
+ const def = componentRegistry[componentType];
23
+ if (!def?.childOf) {
24
+ return true;
25
+ }
26
+ if (!parentType) {
27
+ return false;
28
+ }
29
+ return def.childOf.includes(parentType);
30
+ }
31
+ function isChildOnlyComponent(componentRegistry, componentType) {
32
+ const def = componentRegistry[componentType];
33
+ return Boolean(def?.childOf);
34
+ }
20
35
  function AddComponentsPopover({
21
36
  className,
22
37
  children,
23
38
  addPosition,
24
39
  parentLayerId,
25
40
  onOpenChange,
41
+ enableDragToCanvas = false,
26
42
  onChange
27
43
  }) {
28
44
  const [open, setOpen] = React__default.useState(false);
45
+ const [activeView, setActiveView] = React__default.useState("components");
29
46
  const componentRegistry = editorStore.useEditorStore((state) => state.registry);
47
+ const blocks = editorStore.useEditorStore((state) => state.blocks);
48
+ const findLayerById = layerStore.useLayerStore((state) => state.findLayerById);
49
+ const parentLayerType = React.useMemo(() => {
50
+ const parentLayer = findLayerById(parentLayerId);
51
+ return parentLayer?.type;
52
+ }, [findLayerById, parentLayerId]);
30
53
  const groupedOptions = React.useMemo(() => {
31
- const componentOptions = Object.keys(componentRegistry).map((name) => ({
54
+ const componentOptions = Object.keys(componentRegistry).filter((name) => isValidChildOfParent(componentRegistry, name, parentLayerType)).map((name) => ({
32
55
  value: name,
33
56
  label: name,
34
57
  type: "component",
35
- from: componentRegistry[name].from
58
+ from: componentRegistry[name]?.from
36
59
  }));
37
60
  return componentOptions.reduce(
38
61
  (acc, option) => {
@@ -41,16 +64,34 @@ function AddComponentsPopover({
41
64
  if (!acc[group]) {
42
65
  acc[group] = [];
43
66
  }
44
- acc[group].push(option);
67
+ acc[group]?.push(option);
45
68
  return acc;
46
69
  },
47
70
  {}
48
71
  );
49
- }, [componentRegistry]);
72
+ }, [componentRegistry, parentLayerType]);
50
73
  const categories = React.useMemo(() => {
51
74
  return Object.keys(groupedOptions);
52
75
  }, [groupedOptions]);
76
+ const groupedBlocks = React.useMemo(() => {
77
+ if (!blocks) return {};
78
+ return Object.values(blocks).reduce(
79
+ (acc, block) => {
80
+ if (!acc[block.category]) {
81
+ acc[block.category] = [];
82
+ }
83
+ acc[block.category]?.push(block);
84
+ return acc;
85
+ },
86
+ {}
87
+ );
88
+ }, [blocks]);
89
+ const blockCategories = React.useMemo(() => {
90
+ return Object.keys(groupedBlocks);
91
+ }, [groupedBlocks]);
92
+ const hasBlocks = blocks && Object.keys(blocks).length > 0;
53
93
  const addComponentLayer = layerStore.useLayerStore((state) => state.addComponentLayer);
94
+ const addLayerDirect = layerStore.useLayerStore((state) => state.addLayerDirect);
54
95
  const handleSelect = React__default.useCallback(
55
96
  (currentValue) => {
56
97
  if (onChange) {
@@ -75,6 +116,15 @@ function AddComponentsPopover({
75
116
  onChange
76
117
  ]
77
118
  );
119
+ const handleBlockSelect = React__default.useCallback(
120
+ (block) => {
121
+ const clonedTemplate = cloneLayerWithNewIds(block.template);
122
+ addLayerDirect(clonedTemplate, parentLayerId, addPosition);
123
+ setOpen(false);
124
+ onOpenChange?.(false);
125
+ },
126
+ [addLayerDirect, parentLayerId, addPosition, onOpenChange]
127
+ );
78
128
  const handleOpenChange = React.useCallback(
79
129
  (open2) => {
80
130
  setOpen(open2);
@@ -82,63 +132,180 @@ function AddComponentsPopover({
82
132
  },
83
133
  [onOpenChange]
84
134
  );
135
+ const handleDragStart = React.useCallback(() => {
136
+ setOpen(false);
137
+ onOpenChange?.(false);
138
+ }, [onOpenChange]);
85
139
  const defaultTab = categories[0] || "";
140
+ const defaultBlockCategory = blockCategories[0] || "";
86
141
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: utils.cn("relative flex justify-center", className), children: /* @__PURE__ */ jsxRuntime.jsxs(popover.Popover, { open, onOpenChange: handleOpenChange, children: [
87
142
  /* @__PURE__ */ jsxRuntime.jsx(popover.PopoverTrigger, { asChild: true, children }),
88
- /* @__PURE__ */ jsxRuntime.jsx(popover.PopoverContent, { className: "w-[280px] p-0", align: "start", children: categories.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(tabs.Tabs, { defaultValue: defaultTab, className: "w-full", children: [
89
- /* @__PURE__ */ jsxRuntime.jsx(tabs.TabsList, { className: utils.cn(categories.length > 1 ? "h-14 w-full rounded-none border-b flex flex-row overflow-x-scroll justify-start" : "hidden"), children: categories.map((category) => /* @__PURE__ */ jsxRuntime.jsxs(tabs.TabsTrigger, { value: category, className: "flex flex-col justify-start items-start overflow-hidden px-2 py-1 min-w-24 min-h-11 flex-shrink-0", children: [
90
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm", children: formatCategoryName(category) }),
91
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full min-h-[12px] text-[8px] leading-[9px] text-left text-muted-foreground text-wrap", children: category })
92
- ] }, category)) }),
93
- categories.map((category) => /* @__PURE__ */ jsxRuntime.jsx(tabs.TabsContent, { value: category, className: "m-0", children: /* @__PURE__ */ jsxRuntime.jsxs(command.Command, { className: "border-0", children: [
94
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center px-3 w-full [&>div:first-child]:w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
95
- command.CommandInput,
143
+ /* @__PURE__ */ jsxRuntime.jsxs(popover.PopoverContent, { className: "w-[320px] p-0", align: "start", children: [
144
+ hasBlocks && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex border-b", children: [
145
+ /* @__PURE__ */ jsxRuntime.jsx(
146
+ "button",
147
+ {
148
+ className: utils.cn(
149
+ "flex-1 px-4 py-2 text-sm font-medium transition-colors",
150
+ activeView === "components" ? "bg-background border-b-2 border-primary" : "bg-muted/50 text-muted-foreground hover:text-foreground"
151
+ ),
152
+ onClick: () => setActiveView("components"),
153
+ children: "Components"
154
+ }
155
+ ),
156
+ /* @__PURE__ */ jsxRuntime.jsx(
157
+ "button",
96
158
  {
97
- placeholder: "Find components",
98
- className: "border-0 focus:ring-0 w-full"
159
+ className: utils.cn(
160
+ "flex-1 px-4 py-2 text-sm font-medium transition-colors",
161
+ activeView === "blocks" ? "bg-background border-b-2 border-primary" : "bg-muted/50 text-muted-foreground hover:text-foreground"
162
+ ),
163
+ onClick: () => setActiveView("blocks"),
164
+ children: "Blocks"
99
165
  }
100
- ) }),
101
- /* @__PURE__ */ jsxRuntime.jsxs(command.CommandList, { className: "max-h-[250px]", children: [
102
- /* @__PURE__ */ jsxRuntime.jsx(command.CommandEmpty, { children: "No components found" }),
103
- /* @__PURE__ */ jsxRuntime.jsx(command.CommandGroup, { children: groupedOptions[category].map((component) => /* @__PURE__ */ jsxRuntime.jsx(
104
- GroupedComponentItem,
166
+ )
167
+ ] }),
168
+ activeView === "components" && categories.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(tabs.Tabs, { defaultValue: defaultTab, className: "w-full", children: [
169
+ /* @__PURE__ */ jsxRuntime.jsx(tabs.TabsList, { className: utils.cn(categories.length > 1 ? "h-14 w-full rounded-none border-b flex flex-row overflow-x-scroll justify-start" : "hidden"), children: categories.map((category) => /* @__PURE__ */ jsxRuntime.jsxs(tabs.TabsTrigger, { value: category, className: "flex flex-col justify-start items-start overflow-hidden px-2 py-1 min-w-24 min-h-11 shrink-0", children: [
170
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm", children: formatCategoryName(category) }),
171
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full min-h-[12px] text-[8px] leading-[9px] text-left text-muted-foreground text-wrap", children: category })
172
+ ] }, category)) }),
173
+ categories.map((category) => /* @__PURE__ */ jsxRuntime.jsx(tabs.TabsContent, { value: category, className: "m-0", children: /* @__PURE__ */ jsxRuntime.jsxs(command.Command, { className: "border-0", children: [
174
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center px-3 w-full [&>div:first-child]:w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
175
+ command.CommandInput,
105
176
  {
106
- component,
107
- onClick: handleSelect
108
- },
109
- component.value
110
- )) })
111
- ] })
112
- ] }) }, category))
113
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(command.Command, { children: [
114
- /* @__PURE__ */ jsxRuntime.jsx(command.CommandInput, { placeholder: "Add component" }),
115
- /* @__PURE__ */ jsxRuntime.jsx(command.CommandList, { children: /* @__PURE__ */ jsxRuntime.jsx(command.CommandEmpty, { children: "No components found" }) })
116
- ] }) })
177
+ placeholder: "Find components",
178
+ className: "border-0 focus:ring-0 w-full"
179
+ }
180
+ ) }),
181
+ /* @__PURE__ */ jsxRuntime.jsxs(command.CommandList, { className: "max-h-[250px]", children: [
182
+ /* @__PURE__ */ jsxRuntime.jsx(command.CommandEmpty, { children: "No components found" }),
183
+ /* @__PURE__ */ jsxRuntime.jsx(command.CommandGroup, { children: groupedOptions[category]?.map((component) => /* @__PURE__ */ jsxRuntime.jsx(
184
+ GroupedComponentItem,
185
+ {
186
+ component,
187
+ onClick: handleSelect,
188
+ onDragStart: handleDragStart,
189
+ enableDrag: enableDragToCanvas
190
+ },
191
+ component.value
192
+ )) })
193
+ ] })
194
+ ] }) }, category))
195
+ ] }) : activeView === "components" ? /* @__PURE__ */ jsxRuntime.jsxs(command.Command, { children: [
196
+ /* @__PURE__ */ jsxRuntime.jsx(command.CommandInput, { placeholder: "Add component" }),
197
+ /* @__PURE__ */ jsxRuntime.jsx(command.CommandList, { children: /* @__PURE__ */ jsxRuntime.jsx(command.CommandEmpty, { children: "No components found" }) })
198
+ ] }) : null,
199
+ activeView === "blocks" && hasBlocks && /* @__PURE__ */ jsxRuntime.jsxs(tabs.Tabs, { defaultValue: defaultBlockCategory, className: "w-full", children: [
200
+ /* @__PURE__ */ jsxRuntime.jsx(tabs.TabsList, { className: "h-14 w-full rounded-none border-b flex flex-row overflow-x-scroll justify-start", children: blockCategories.map((category) => /* @__PURE__ */ jsxRuntime.jsxs(tabs.TabsTrigger, { value: category, className: "flex flex-col justify-start items-start overflow-hidden px-2 py-1 min-w-24 min-h-11 shrink-0", children: [
201
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm", children: formatCategoryName(category) }),
202
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full min-h-[12px] text-[8px] leading-[9px] text-left text-muted-foreground", children: [
203
+ groupedBlocks[category]?.length,
204
+ " blocks"
205
+ ] })
206
+ ] }, category)) }),
207
+ blockCategories.map((category) => /* @__PURE__ */ jsxRuntime.jsx(tabs.TabsContent, { value: category, className: "m-0", children: /* @__PURE__ */ jsxRuntime.jsxs(command.Command, { className: "border-0", children: [
208
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center px-3 w-full [&>div:first-child]:w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
209
+ command.CommandInput,
210
+ {
211
+ placeholder: "Find blocks",
212
+ className: "border-0 focus:ring-0 w-full"
213
+ }
214
+ ) }),
215
+ /* @__PURE__ */ jsxRuntime.jsxs(command.CommandList, { className: "max-h-[250px]", children: [
216
+ /* @__PURE__ */ jsxRuntime.jsx(command.CommandEmpty, { children: "No blocks found" }),
217
+ /* @__PURE__ */ jsxRuntime.jsx(command.CommandGroup, { children: groupedBlocks[category]?.map((block) => /* @__PURE__ */ jsxRuntime.jsx(
218
+ BlockItem,
219
+ {
220
+ block,
221
+ onClick: handleBlockSelect
222
+ },
223
+ block.name
224
+ )) })
225
+ ] })
226
+ ] }) }, category))
227
+ ] })
228
+ ] })
117
229
  ] }) });
118
230
  }
231
+ function cloneLayerWithNewIds(layer) {
232
+ const newId = generateUniqueId();
233
+ let newChildren;
234
+ if (Array.isArray(layer.children)) {
235
+ newChildren = layer.children.map((child) => cloneLayerWithNewIds(child));
236
+ } else if (typeof layer.children === "string") {
237
+ newChildren = layer.children;
238
+ } else if (layer.children && typeof layer.children === "object" && "__variableRef" in layer.children) {
239
+ newChildren = layer.children;
240
+ } else {
241
+ newChildren = layer.children;
242
+ }
243
+ return {
244
+ ...layer,
245
+ id: newId,
246
+ children: newChildren
247
+ };
248
+ }
249
+ function generateUniqueId() {
250
+ return Math.random().toString(36).substring(2, 9);
251
+ }
252
+ const BlockItem = React.memo(({
253
+ block,
254
+ onClick
255
+ }) => {
256
+ const handleSelect = React.useCallback(() => {
257
+ onClick(block);
258
+ }, [onClick, block]);
259
+ return /* @__PURE__ */ jsxRuntime.jsxs(
260
+ command.CommandItem,
261
+ {
262
+ onSelect: handleSelect,
263
+ className: "cursor-pointer flex flex-col items-start gap-1 py-3",
264
+ children: [
265
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: formatBlockName(block.name) }),
266
+ block.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground line-clamp-2", children: block.description })
267
+ ]
268
+ }
269
+ );
270
+ });
271
+ BlockItem.displayName = "BlockItem";
272
+ function formatBlockName(name) {
273
+ return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
274
+ }
119
275
  const GroupedComponentItem = React.memo(({
120
276
  component,
121
- onClick
277
+ onClick,
278
+ onDragStart,
279
+ enableDrag = false
122
280
  }) => {
123
281
  const handleSelect = React.useCallback(() => {
124
282
  onClick(component.value);
125
283
  }, [onClick, component.value]);
126
284
  const componentRegistry = editorStore.useEditorStore((state) => state.registry);
127
- return /* @__PURE__ */ jsxRuntime.jsxs(
285
+ const content = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
286
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 w-10 h-6 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
287
+ LazyComponentPreview,
288
+ {
289
+ componentType: component.value,
290
+ componentRegistry
291
+ }
292
+ ) }),
293
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: component.label }) })
294
+ ] });
295
+ return /* @__PURE__ */ jsxRuntime.jsx(
128
296
  command.CommandItem,
129
297
  {
130
298
  onSelect: handleSelect,
131
- className: "cursor-pointer flex items-center gap-3 py-3",
132
- children: [
133
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 w-10 h-8 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
134
- LazyComponentPreview,
135
- {
136
- componentType: component.value,
137
- componentRegistry
138
- }
139
- ) }),
140
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: component.label }) })
141
- ]
299
+ className: "cursor-pointer flex items-center gap-2 py-2",
300
+ children: enableDrag ? /* @__PURE__ */ jsxRuntime.jsx(
301
+ draggableNewComponent.DraggableNewComponent,
302
+ {
303
+ componentType: component.value,
304
+ onDragStart,
305
+ className: "flex-1",
306
+ children: content
307
+ }
308
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: content })
142
309
  },
143
310
  component.value
144
311
  );
@@ -156,7 +323,7 @@ const LazyComponentPreview = React.memo(({
156
323
  let timeoutId = null;
157
324
  const observer = new IntersectionObserver(
158
325
  ([entry]) => {
159
- if (entry.isIntersecting) {
326
+ if (entry?.isIntersecting) {
160
327
  timeoutId = setTimeout(() => setShouldLoad(true), 50);
161
328
  }
162
329
  },
@@ -179,12 +346,20 @@ const LazyComponentPreview = React.memo(({
179
346
  ) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full bg-muted rounded border" }) });
180
347
  });
181
348
  LazyComponentPreview.displayName = "LazyComponentPreview";
349
+ const ChildOnlyPlaceholder = React.memo(({ componentType }) => {
350
+ const initials = componentType.replace(/([A-Z])/g, " $1").trim().split(" ").map((word) => word[0]).slice(0, 2).join("");
351
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full bg-muted/50 rounded border border-dashed flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px] font-medium text-muted-foreground", children: initials }) });
352
+ });
353
+ ChildOnlyPlaceholder.displayName = "ChildOnlyPlaceholder";
182
354
  const ComponentPreview = React.memo(({
183
355
  componentType,
184
356
  componentRegistry
185
357
  }) => {
186
- const style = { width: "200%", height: "200%" };
358
+ const isChildOnly = isChildOnlyComponent(componentRegistry, componentType);
187
359
  const previewLayer = React.useMemo(() => {
360
+ if (isChildOnly) {
361
+ return null;
362
+ }
188
363
  const cacheKey = `${componentType}-${JSON.stringify(componentRegistry[componentType]?.schema)}`;
189
364
  if (previewLayerCache.has(cacheKey)) {
190
365
  return previewLayerCache.get(cacheKey);
@@ -200,15 +375,20 @@ const ComponentPreview = React.memo(({
200
375
  console.warn(`Failed to create preview for component ${componentType}:`, error);
201
376
  return null;
202
377
  }
203
- }, [componentType, componentRegistry]);
378
+ }, [componentType, componentRegistry, isChildOnly]);
379
+ if (isChildOnly) {
380
+ return /* @__PURE__ */ jsxRuntime.jsx(ChildOnlyPlaceholder, { componentType });
381
+ }
204
382
  if (!previewLayer) {
205
383
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full bg-muted rounded border" });
206
384
  }
385
+ const style = { width: "200%", height: "200%" };
207
386
  return /* @__PURE__ */ jsxRuntime.jsx(
208
387
  "div",
209
388
  {
210
- className: "w-full h-full bg-background rounded border overflow-hidden transform scale-50 origin-top-left",
389
+ className: "w-full h-full bg-background rounded border overflow-hidden transform scale-50 origin-top-left pointer-events-none",
211
390
  style,
391
+ inert: true,
212
392
  children: /* @__PURE__ */ jsxRuntime.jsx(
213
393
  layerRenderer,
214
394
  {
@@ -224,7 +404,7 @@ ComponentPreview.displayName = "ComponentPreview";
224
404
  function formatCategoryName(name) {
225
405
  const words = name.split("/");
226
406
  const lastWord = words[words.length - 1];
227
- return lastWord.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
407
+ return lastWord?.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase()) ?? "";
228
408
  }
229
409
 
230
410
  exports.AddComponentsPopover = AddComponentsPopover;