@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
@@ -23,6 +23,8 @@ const DROP_INDICATOR_HOVER = "before:bg-blue-500 before:shadow-xl";
23
23
  const DROP_INDICATOR_DEFAULT = "before:bg-blue-400/50";
24
24
  const DROP_INDICATOR_INLINE_HOVER = "before:bg-blue-500 before:shadow-lg";
25
25
  const DROP_INDICATOR_INLINE_DEFAULT = "before:bg-blue-400/40";
26
+ const DROP_INDICATOR_DISABLED = "before:bg-gray-400/30";
27
+ const DROP_INDICATOR_DISABLED_HOVER = "before:bg-gray-500/40";
26
28
  function getLayoutType(element) {
27
29
  const styles = window.getComputedStyle(element);
28
30
  const display = styles.display;
@@ -59,6 +61,11 @@ const DropPlaceholder = ({
59
61
  isActive = false,
60
62
  style
61
63
  }) => {
64
+ const [layoutType, setLayoutType] = React__default.useState("block");
65
+ const [calculatedStyle, setCalculatedStyle] = React__default.useState(null);
66
+ const [element, setElement] = React__default.useState(null);
67
+ const dndContext = dndContexts.useDndContext();
68
+ const isDropValid = dndContext.canDropOnLayer(parentId);
62
69
  const {
63
70
  isOver,
64
71
  setNodeRef
@@ -68,31 +75,113 @@ const DropPlaceholder = ({
68
75
  type: "drop-zone",
69
76
  parentId,
70
77
  position
71
- }
78
+ },
79
+ // Disable the droppable when drop is not valid (e.g., childOf constraint violation)
80
+ disabled: !isDropValid
72
81
  });
73
- const [layoutType, setLayoutType] = React__default.useState("block");
74
- const [element, setElement] = React__default.useState(null);
75
- const dndContext = dndContexts.useDndContext();
76
82
  React__default.useLayoutEffect(() => {
77
83
  if (!isActive || !element) return;
78
- let parentElement = element.parentElement;
79
- while (parentElement && parentElement.classList.contains("relative") && parentElement.children.length === 2) {
80
- parentElement = parentElement.parentElement;
84
+ const parentContainer = element.closest(`[data-layer-id="${parentId}"]`);
85
+ if (!parentContainer) return;
86
+ let detectedLayout = getLayoutType(parentContainer);
87
+ if (dndContext?.activeLayerId) {
88
+ const draggedElement = document.querySelector(`[data-layer-id="${dndContext.activeLayerId}"]`);
89
+ if (draggedElement) {
90
+ const draggedTagName = draggedElement.tagName.toLowerCase();
91
+ const draggedDisplay = window.getComputedStyle(draggedElement).display;
92
+ const isInlineElement = draggedDisplay === "inline" || draggedDisplay === "inline-block" || ["span", "a", "strong", "em", "code", "small", "mark", "del", "ins", "sub", "sup"].includes(draggedTagName);
93
+ if (isInlineElement && detectedLayout === "block") {
94
+ detectedLayout = "inline";
95
+ }
96
+ }
97
+ }
98
+ setLayoutType(detectedLayout);
99
+ const nextSibling = element.nextElementSibling;
100
+ let siblingElement = null;
101
+ if (nextSibling) {
102
+ if (nextSibling.hasAttribute("data-layer-id")) {
103
+ siblingElement = nextSibling;
104
+ } else {
105
+ siblingElement = nextSibling.querySelector("[data-layer-id]");
106
+ }
81
107
  }
82
- if (parentElement) {
83
- let detectedLayout = getLayoutType(parentElement);
84
- if (dndContext?.activeLayerId) {
85
- const draggedElement = document.querySelector(`[data-layer-id="${dndContext.activeLayerId}"]`);
86
- if (draggedElement) {
87
- const draggedTagName = draggedElement.tagName.toLowerCase();
88
- const draggedDisplay = window.getComputedStyle(draggedElement).display;
89
- const isInlineElement = draggedDisplay === "inline" || draggedDisplay === "inline-block" || ["span", "a", "strong", "em", "code", "small", "mark", "del", "ins", "sub", "sup"].includes(draggedTagName);
90
- if (isInlineElement && detectedLayout === "block") {
91
- detectedLayout = "inline";
92
- }
108
+ const parentRect = parentContainer.getBoundingClientRect();
109
+ if (siblingElement) {
110
+ const siblingRect = siblingElement.getBoundingClientRect();
111
+ const positionStyle = {
112
+ position: "absolute",
113
+ pointerEvents: "auto",
114
+ zIndex: 999
115
+ };
116
+ if (detectedLayout === "flex-row") {
117
+ positionStyle.left = siblingRect.left - parentRect.left - 4;
118
+ positionStyle.top = siblingRect.top - parentRect.top;
119
+ positionStyle.width = 8;
120
+ positionStyle.height = siblingRect.height;
121
+ } else if (detectedLayout === "inline") {
122
+ positionStyle.left = siblingRect.left - parentRect.left - 3;
123
+ positionStyle.top = siblingRect.top - parentRect.top;
124
+ positionStyle.width = 6;
125
+ positionStyle.height = siblingRect.height;
126
+ } else if (detectedLayout === "grid") {
127
+ positionStyle.left = siblingRect.left - parentRect.left - 4;
128
+ positionStyle.top = siblingRect.top - parentRect.top - 4;
129
+ positionStyle.width = 8;
130
+ positionStyle.height = 8;
131
+ } else {
132
+ positionStyle.left = siblingRect.left - parentRect.left;
133
+ positionStyle.top = siblingRect.top - parentRect.top - 4;
134
+ positionStyle.width = siblingRect.width;
135
+ positionStyle.height = 8;
136
+ }
137
+ setCalculatedStyle(positionStyle);
138
+ } else {
139
+ const childLayers = parentContainer.querySelectorAll("[data-layer-id]");
140
+ const directChildLayers = Array.from(childLayers).filter((el) => {
141
+ const closestLayerParent = el.parentElement?.closest("[data-layer-id]");
142
+ return closestLayerParent === parentContainer || closestLayerParent === null;
143
+ });
144
+ const lastChild = directChildLayers[directChildLayers.length - 1];
145
+ if (lastChild) {
146
+ const lastChildRect = lastChild.getBoundingClientRect();
147
+ const positionStyle = {
148
+ position: "absolute",
149
+ pointerEvents: "auto",
150
+ zIndex: 999
151
+ };
152
+ if (detectedLayout === "flex-row") {
153
+ positionStyle.left = lastChildRect.right - parentRect.left - 4;
154
+ positionStyle.top = lastChildRect.top - parentRect.top;
155
+ positionStyle.width = 8;
156
+ positionStyle.height = lastChildRect.height;
157
+ } else if (detectedLayout === "inline") {
158
+ positionStyle.left = lastChildRect.right - parentRect.left - 3;
159
+ positionStyle.top = lastChildRect.top - parentRect.top;
160
+ positionStyle.width = 6;
161
+ positionStyle.height = lastChildRect.height;
162
+ } else if (detectedLayout === "grid") {
163
+ positionStyle.left = lastChildRect.right - parentRect.left - 4;
164
+ positionStyle.top = lastChildRect.top - parentRect.top - 4;
165
+ positionStyle.width = 8;
166
+ positionStyle.height = 8;
167
+ } else {
168
+ positionStyle.left = lastChildRect.left - parentRect.left;
169
+ positionStyle.top = lastChildRect.bottom - parentRect.top - 4;
170
+ positionStyle.width = lastChildRect.width;
171
+ positionStyle.height = 8;
93
172
  }
173
+ setCalculatedStyle(positionStyle);
174
+ } else {
175
+ setCalculatedStyle({
176
+ position: "absolute",
177
+ pointerEvents: "auto",
178
+ zIndex: 999,
179
+ left: 0,
180
+ top: 0,
181
+ right: 0,
182
+ height: 8
183
+ });
94
184
  }
95
- setLayoutType(detectedLayout);
96
185
  }
97
186
  }, [isActive, element, parentId, position, dndContext?.activeLayerId]);
98
187
  const combinedRef = React__default.useCallback((node) => {
@@ -100,57 +189,112 @@ const DropPlaceholder = ({
100
189
  setNodeRef(node);
101
190
  }, [setNodeRef]);
102
191
  if (!isActive) return null;
192
+ const useCalculatedPositioning = calculatedStyle !== null;
193
+ const getIndicatorStyles = (baseHover, baseDefault) => {
194
+ if (!isDropValid) {
195
+ return isOver ? DROP_INDICATOR_DISABLED_HOVER : DROP_INDICATOR_DISABLED;
196
+ }
197
+ return isOver ? baseHover : baseDefault;
198
+ };
103
199
  return /* @__PURE__ */ jsxRuntime.jsx(
104
200
  "div",
105
201
  {
106
202
  ref: combinedRef,
107
203
  className: utils.cn(
204
+ // Always apply absolute positioning base classes
108
205
  "absolute pointer-events-auto z-[999]",
109
206
  "before:content-[''] before:absolute",
110
207
  BEFORE_TRANSITION,
111
208
  "before:pointer-events-none",
112
- // Use custom style if provided, otherwise use default layout-based positioning
113
- !style && [
114
- // Flex-row layout: vertical lines on the left edge
209
+ // Disabled state visual cue
210
+ !isDropValid && "opacity-60 cursor-not-allowed",
211
+ // Fallback positioning classes when calculatedStyle is not available
212
+ !useCalculatedPositioning && !style && [
213
+ // Flex-row layout: vertical lines on left edge
115
214
  layoutType === "flex-row" && [
116
- "-left-4 top-0 bottom-0 w-8",
215
+ "-left-1 top-0 bottom-0 w-2",
117
216
  BEFORE_CENTER_TRANSFORM,
118
217
  "before:w-1 before:h-8",
119
- isOver ? `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:w-6 before:h-20` : `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:w-1 before:h-6`
218
+ getIndicatorStyles(
219
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:w-2 before:h-16`,
220
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:w-1 before:h-6`
221
+ )
120
222
  ],
121
- // Inline layout: subtle vertical indicators for text flow
223
+ // Inline layout: subtle vertical indicators
122
224
  layoutType === "inline" && [
123
- "-left-3 top-0 bottom-0 w-6",
225
+ "-left-1 top-0 bottom-0 w-2",
124
226
  BEFORE_CENTER_TRANSFORM,
125
227
  "before:w-0.5 before:h-4",
126
- isOver ? `${DROP_INDICATOR_INLINE_HOVER} ${BEFORE_ROUNDED_HOVER} before:w-4 before:h-10` : `${DROP_INDICATOR_INLINE_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:w-0.5 before:h-3`
228
+ getIndicatorStyles(
229
+ `${DROP_INDICATOR_INLINE_HOVER} ${BEFORE_ROUNDED_HOVER} before:w-1 before:h-8`,
230
+ `${DROP_INDICATOR_INLINE_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:w-0.5 before:h-3`
231
+ )
127
232
  ],
128
- // Vertical layout (flex-col, block): horizontal lines on the top edge
233
+ // Vertical layout (flex-col, block): horizontal lines on top edge
129
234
  (layoutType === "flex-col" || layoutType === "block") && [
130
- "left-0 right-0 -top-4 h-8",
235
+ "left-0 right-0 -top-1 h-2",
131
236
  BEFORE_CENTER_TRANSFORM,
132
237
  "before:h-1 before:w-8",
133
- isOver ? `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:h-6 before:w-20` : `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:h-1 before:w-6`
238
+ getIndicatorStyles(
239
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:h-2 before:w-16`,
240
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:h-1 before:w-6`
241
+ )
134
242
  ],
135
243
  // Grid layout: corner indicator
136
244
  layoutType === "grid" && [
137
- "-left-4 -top-4 w-8 h-8",
245
+ "-left-1 -top-1 w-2 h-2",
138
246
  BEFORE_CENTER_TRANSFORM,
139
247
  "before:h-2 before:w-2",
140
- isOver ? `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:h-6 before:w-6` : `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:h-2 before:w-2`
248
+ getIndicatorStyles(
249
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:h-4 before:w-4`,
250
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:h-2 before:w-2`
251
+ )
252
+ ]
253
+ ],
254
+ // When using calculated positioning, only apply visual indicator classes
255
+ useCalculatedPositioning && [
256
+ BEFORE_CENTER_TRANSFORM,
257
+ layoutType === "flex-row" && [
258
+ "before:w-1 before:h-8",
259
+ getIndicatorStyles(
260
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:w-2 before:h-16`,
261
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT}`
262
+ )
263
+ ],
264
+ layoutType === "inline" && [
265
+ "before:w-0.5 before:h-4",
266
+ getIndicatorStyles(
267
+ `${DROP_INDICATOR_INLINE_HOVER} ${BEFORE_ROUNDED_HOVER}`,
268
+ `${DROP_INDICATOR_INLINE_DEFAULT} ${BEFORE_ROUNDED_DEFAULT}`
269
+ )
270
+ ],
271
+ (layoutType === "flex-col" || layoutType === "block") && [
272
+ "before:h-1 before:w-8",
273
+ getIndicatorStyles(
274
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:h-2 before:w-16`,
275
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT}`
276
+ )
277
+ ],
278
+ layoutType === "grid" && [
279
+ "before:h-2 before:w-2",
280
+ getIndicatorStyles(
281
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER}`,
282
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT}`
283
+ )
141
284
  ]
142
285
  ],
143
- // Custom style overrides for absolute positioning
286
+ // Custom style overrides
144
287
  style && [
145
- "before:bg-blue-500/60 before:rounded-sm",
146
- isOver && "before:bg-blue-600 before:shadow-md"
288
+ isDropValid ? ["before:bg-blue-500/60 before:rounded-sm", isOver && "before:bg-blue-600 before:shadow-md"] : ["before:bg-gray-400/30 before:rounded-sm", isOver && "before:bg-gray-500/40"]
147
289
  ]
148
290
  ),
149
- style,
291
+ style: useCalculatedPositioning ? calculatedStyle : style,
150
292
  "data-testid": `drop-placeholder-${parentId}-${position}`,
151
- "data-drop-indicator": true
293
+ "data-drop-indicator": true,
294
+ "data-drop-valid": isDropValid
152
295
  }
153
296
  );
154
297
  };
155
298
 
156
299
  exports.DropPlaceholder = DropPlaceholder;
300
+ exports.getLayoutType = getLayoutType;
@@ -17,6 +17,8 @@ const DROP_INDICATOR_HOVER = "before:bg-blue-500 before:shadow-xl";
17
17
  const DROP_INDICATOR_DEFAULT = "before:bg-blue-400/50";
18
18
  const DROP_INDICATOR_INLINE_HOVER = "before:bg-blue-500 before:shadow-lg";
19
19
  const DROP_INDICATOR_INLINE_DEFAULT = "before:bg-blue-400/40";
20
+ const DROP_INDICATOR_DISABLED = "before:bg-gray-400/30";
21
+ const DROP_INDICATOR_DISABLED_HOVER = "before:bg-gray-500/40";
20
22
  function getLayoutType(element) {
21
23
  const styles = window.getComputedStyle(element);
22
24
  const display = styles.display;
@@ -53,6 +55,11 @@ const DropPlaceholder = ({
53
55
  isActive = false,
54
56
  style
55
57
  }) => {
58
+ const [layoutType, setLayoutType] = React__default.useState("block");
59
+ const [calculatedStyle, setCalculatedStyle] = React__default.useState(null);
60
+ const [element, setElement] = React__default.useState(null);
61
+ const dndContext = useDndContext();
62
+ const isDropValid = dndContext.canDropOnLayer(parentId);
56
63
  const {
57
64
  isOver,
58
65
  setNodeRef
@@ -62,31 +69,113 @@ const DropPlaceholder = ({
62
69
  type: "drop-zone",
63
70
  parentId,
64
71
  position
65
- }
72
+ },
73
+ // Disable the droppable when drop is not valid (e.g., childOf constraint violation)
74
+ disabled: !isDropValid
66
75
  });
67
- const [layoutType, setLayoutType] = React__default.useState("block");
68
- const [element, setElement] = React__default.useState(null);
69
- const dndContext = useDndContext();
70
76
  React__default.useLayoutEffect(() => {
71
77
  if (!isActive || !element) return;
72
- let parentElement = element.parentElement;
73
- while (parentElement && parentElement.classList.contains("relative") && parentElement.children.length === 2) {
74
- parentElement = parentElement.parentElement;
78
+ const parentContainer = element.closest(`[data-layer-id="${parentId}"]`);
79
+ if (!parentContainer) return;
80
+ let detectedLayout = getLayoutType(parentContainer);
81
+ if (dndContext?.activeLayerId) {
82
+ const draggedElement = document.querySelector(`[data-layer-id="${dndContext.activeLayerId}"]`);
83
+ if (draggedElement) {
84
+ const draggedTagName = draggedElement.tagName.toLowerCase();
85
+ const draggedDisplay = window.getComputedStyle(draggedElement).display;
86
+ const isInlineElement = draggedDisplay === "inline" || draggedDisplay === "inline-block" || ["span", "a", "strong", "em", "code", "small", "mark", "del", "ins", "sub", "sup"].includes(draggedTagName);
87
+ if (isInlineElement && detectedLayout === "block") {
88
+ detectedLayout = "inline";
89
+ }
90
+ }
91
+ }
92
+ setLayoutType(detectedLayout);
93
+ const nextSibling = element.nextElementSibling;
94
+ let siblingElement = null;
95
+ if (nextSibling) {
96
+ if (nextSibling.hasAttribute("data-layer-id")) {
97
+ siblingElement = nextSibling;
98
+ } else {
99
+ siblingElement = nextSibling.querySelector("[data-layer-id]");
100
+ }
75
101
  }
76
- if (parentElement) {
77
- let detectedLayout = getLayoutType(parentElement);
78
- if (dndContext?.activeLayerId) {
79
- const draggedElement = document.querySelector(`[data-layer-id="${dndContext.activeLayerId}"]`);
80
- if (draggedElement) {
81
- const draggedTagName = draggedElement.tagName.toLowerCase();
82
- const draggedDisplay = window.getComputedStyle(draggedElement).display;
83
- const isInlineElement = draggedDisplay === "inline" || draggedDisplay === "inline-block" || ["span", "a", "strong", "em", "code", "small", "mark", "del", "ins", "sub", "sup"].includes(draggedTagName);
84
- if (isInlineElement && detectedLayout === "block") {
85
- detectedLayout = "inline";
86
- }
102
+ const parentRect = parentContainer.getBoundingClientRect();
103
+ if (siblingElement) {
104
+ const siblingRect = siblingElement.getBoundingClientRect();
105
+ const positionStyle = {
106
+ position: "absolute",
107
+ pointerEvents: "auto",
108
+ zIndex: 999
109
+ };
110
+ if (detectedLayout === "flex-row") {
111
+ positionStyle.left = siblingRect.left - parentRect.left - 4;
112
+ positionStyle.top = siblingRect.top - parentRect.top;
113
+ positionStyle.width = 8;
114
+ positionStyle.height = siblingRect.height;
115
+ } else if (detectedLayout === "inline") {
116
+ positionStyle.left = siblingRect.left - parentRect.left - 3;
117
+ positionStyle.top = siblingRect.top - parentRect.top;
118
+ positionStyle.width = 6;
119
+ positionStyle.height = siblingRect.height;
120
+ } else if (detectedLayout === "grid") {
121
+ positionStyle.left = siblingRect.left - parentRect.left - 4;
122
+ positionStyle.top = siblingRect.top - parentRect.top - 4;
123
+ positionStyle.width = 8;
124
+ positionStyle.height = 8;
125
+ } else {
126
+ positionStyle.left = siblingRect.left - parentRect.left;
127
+ positionStyle.top = siblingRect.top - parentRect.top - 4;
128
+ positionStyle.width = siblingRect.width;
129
+ positionStyle.height = 8;
130
+ }
131
+ setCalculatedStyle(positionStyle);
132
+ } else {
133
+ const childLayers = parentContainer.querySelectorAll("[data-layer-id]");
134
+ const directChildLayers = Array.from(childLayers).filter((el) => {
135
+ const closestLayerParent = el.parentElement?.closest("[data-layer-id]");
136
+ return closestLayerParent === parentContainer || closestLayerParent === null;
137
+ });
138
+ const lastChild = directChildLayers[directChildLayers.length - 1];
139
+ if (lastChild) {
140
+ const lastChildRect = lastChild.getBoundingClientRect();
141
+ const positionStyle = {
142
+ position: "absolute",
143
+ pointerEvents: "auto",
144
+ zIndex: 999
145
+ };
146
+ if (detectedLayout === "flex-row") {
147
+ positionStyle.left = lastChildRect.right - parentRect.left - 4;
148
+ positionStyle.top = lastChildRect.top - parentRect.top;
149
+ positionStyle.width = 8;
150
+ positionStyle.height = lastChildRect.height;
151
+ } else if (detectedLayout === "inline") {
152
+ positionStyle.left = lastChildRect.right - parentRect.left - 3;
153
+ positionStyle.top = lastChildRect.top - parentRect.top;
154
+ positionStyle.width = 6;
155
+ positionStyle.height = lastChildRect.height;
156
+ } else if (detectedLayout === "grid") {
157
+ positionStyle.left = lastChildRect.right - parentRect.left - 4;
158
+ positionStyle.top = lastChildRect.top - parentRect.top - 4;
159
+ positionStyle.width = 8;
160
+ positionStyle.height = 8;
161
+ } else {
162
+ positionStyle.left = lastChildRect.left - parentRect.left;
163
+ positionStyle.top = lastChildRect.bottom - parentRect.top - 4;
164
+ positionStyle.width = lastChildRect.width;
165
+ positionStyle.height = 8;
87
166
  }
167
+ setCalculatedStyle(positionStyle);
168
+ } else {
169
+ setCalculatedStyle({
170
+ position: "absolute",
171
+ pointerEvents: "auto",
172
+ zIndex: 999,
173
+ left: 0,
174
+ top: 0,
175
+ right: 0,
176
+ height: 8
177
+ });
88
178
  }
89
- setLayoutType(detectedLayout);
90
179
  }
91
180
  }, [isActive, element, parentId, position, dndContext?.activeLayerId]);
92
181
  const combinedRef = React__default.useCallback((node) => {
@@ -94,57 +183,111 @@ const DropPlaceholder = ({
94
183
  setNodeRef(node);
95
184
  }, [setNodeRef]);
96
185
  if (!isActive) return null;
186
+ const useCalculatedPositioning = calculatedStyle !== null;
187
+ const getIndicatorStyles = (baseHover, baseDefault) => {
188
+ if (!isDropValid) {
189
+ return isOver ? DROP_INDICATOR_DISABLED_HOVER : DROP_INDICATOR_DISABLED;
190
+ }
191
+ return isOver ? baseHover : baseDefault;
192
+ };
97
193
  return /* @__PURE__ */ jsx(
98
194
  "div",
99
195
  {
100
196
  ref: combinedRef,
101
197
  className: cn(
198
+ // Always apply absolute positioning base classes
102
199
  "absolute pointer-events-auto z-[999]",
103
200
  "before:content-[''] before:absolute",
104
201
  BEFORE_TRANSITION,
105
202
  "before:pointer-events-none",
106
- // Use custom style if provided, otherwise use default layout-based positioning
107
- !style && [
108
- // Flex-row layout: vertical lines on the left edge
203
+ // Disabled state visual cue
204
+ !isDropValid && "opacity-60 cursor-not-allowed",
205
+ // Fallback positioning classes when calculatedStyle is not available
206
+ !useCalculatedPositioning && !style && [
207
+ // Flex-row layout: vertical lines on left edge
109
208
  layoutType === "flex-row" && [
110
- "-left-4 top-0 bottom-0 w-8",
209
+ "-left-1 top-0 bottom-0 w-2",
111
210
  BEFORE_CENTER_TRANSFORM,
112
211
  "before:w-1 before:h-8",
113
- isOver ? `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:w-6 before:h-20` : `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:w-1 before:h-6`
212
+ getIndicatorStyles(
213
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:w-2 before:h-16`,
214
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:w-1 before:h-6`
215
+ )
114
216
  ],
115
- // Inline layout: subtle vertical indicators for text flow
217
+ // Inline layout: subtle vertical indicators
116
218
  layoutType === "inline" && [
117
- "-left-3 top-0 bottom-0 w-6",
219
+ "-left-1 top-0 bottom-0 w-2",
118
220
  BEFORE_CENTER_TRANSFORM,
119
221
  "before:w-0.5 before:h-4",
120
- isOver ? `${DROP_INDICATOR_INLINE_HOVER} ${BEFORE_ROUNDED_HOVER} before:w-4 before:h-10` : `${DROP_INDICATOR_INLINE_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:w-0.5 before:h-3`
222
+ getIndicatorStyles(
223
+ `${DROP_INDICATOR_INLINE_HOVER} ${BEFORE_ROUNDED_HOVER} before:w-1 before:h-8`,
224
+ `${DROP_INDICATOR_INLINE_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:w-0.5 before:h-3`
225
+ )
121
226
  ],
122
- // Vertical layout (flex-col, block): horizontal lines on the top edge
227
+ // Vertical layout (flex-col, block): horizontal lines on top edge
123
228
  (layoutType === "flex-col" || layoutType === "block") && [
124
- "left-0 right-0 -top-4 h-8",
229
+ "left-0 right-0 -top-1 h-2",
125
230
  BEFORE_CENTER_TRANSFORM,
126
231
  "before:h-1 before:w-8",
127
- isOver ? `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:h-6 before:w-20` : `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:h-1 before:w-6`
232
+ getIndicatorStyles(
233
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:h-2 before:w-16`,
234
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:h-1 before:w-6`
235
+ )
128
236
  ],
129
237
  // Grid layout: corner indicator
130
238
  layoutType === "grid" && [
131
- "-left-4 -top-4 w-8 h-8",
239
+ "-left-1 -top-1 w-2 h-2",
132
240
  BEFORE_CENTER_TRANSFORM,
133
241
  "before:h-2 before:w-2",
134
- isOver ? `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:h-6 before:w-6` : `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:h-2 before:w-2`
242
+ getIndicatorStyles(
243
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:h-4 before:w-4`,
244
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT} before:h-2 before:w-2`
245
+ )
246
+ ]
247
+ ],
248
+ // When using calculated positioning, only apply visual indicator classes
249
+ useCalculatedPositioning && [
250
+ BEFORE_CENTER_TRANSFORM,
251
+ layoutType === "flex-row" && [
252
+ "before:w-1 before:h-8",
253
+ getIndicatorStyles(
254
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:w-2 before:h-16`,
255
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT}`
256
+ )
257
+ ],
258
+ layoutType === "inline" && [
259
+ "before:w-0.5 before:h-4",
260
+ getIndicatorStyles(
261
+ `${DROP_INDICATOR_INLINE_HOVER} ${BEFORE_ROUNDED_HOVER}`,
262
+ `${DROP_INDICATOR_INLINE_DEFAULT} ${BEFORE_ROUNDED_DEFAULT}`
263
+ )
264
+ ],
265
+ (layoutType === "flex-col" || layoutType === "block") && [
266
+ "before:h-1 before:w-8",
267
+ getIndicatorStyles(
268
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER} before:h-2 before:w-16`,
269
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT}`
270
+ )
271
+ ],
272
+ layoutType === "grid" && [
273
+ "before:h-2 before:w-2",
274
+ getIndicatorStyles(
275
+ `${DROP_INDICATOR_HOVER} ${BEFORE_ROUNDED_HOVER}`,
276
+ `${DROP_INDICATOR_DEFAULT} ${BEFORE_ROUNDED_DEFAULT}`
277
+ )
135
278
  ]
136
279
  ],
137
- // Custom style overrides for absolute positioning
280
+ // Custom style overrides
138
281
  style && [
139
- "before:bg-blue-500/60 before:rounded-sm",
140
- isOver && "before:bg-blue-600 before:shadow-md"
282
+ isDropValid ? ["before:bg-blue-500/60 before:rounded-sm", isOver && "before:bg-blue-600 before:shadow-md"] : ["before:bg-gray-400/30 before:rounded-sm", isOver && "before:bg-gray-500/40"]
141
283
  ]
142
284
  ),
143
- style,
285
+ style: useCalculatedPositioning ? calculatedStyle : style,
144
286
  "data-testid": `drop-placeholder-${parentId}-${position}`,
145
- "data-drop-indicator": true
287
+ "data-drop-indicator": true,
288
+ "data-drop-valid": isDropValid
146
289
  }
147
290
  );
148
291
  };
149
292
 
150
- export { DropPlaceholder };
293
+ export { DropPlaceholder, getLayoutType };
@@ -338,7 +338,7 @@ const EditorPanelContent = ({
338
338
  ]
339
339
  }
340
340
  ),
341
- /* @__PURE__ */ jsxRuntime.jsx(addComponentPopover.AddComponentsPopover, { parentLayerId: selectedPageId, children: /* @__PURE__ */ jsxRuntime.jsx(
341
+ /* @__PURE__ */ jsxRuntime.jsx(addComponentPopover.AddComponentsPopover, { parentLayerId: selectedPageId, enableDragToCanvas: true, children: /* @__PURE__ */ jsxRuntime.jsx(
342
342
  button.Button,
343
343
  {
344
344
  variant: "secondary",
@@ -336,7 +336,7 @@ const EditorPanelContent = ({
336
336
  ]
337
337
  }
338
338
  ),
339
- /* @__PURE__ */ jsx(AddComponentsPopover, { parentLayerId: selectedPageId, children: /* @__PURE__ */ jsx(
339
+ /* @__PURE__ */ jsx(AddComponentsPopover, { parentLayerId: selectedPageId, enableDragToCanvas: true, children: /* @__PURE__ */ jsx(
340
340
  Button,
341
341
  {
342
342
  variant: "secondary",
@@ -48,7 +48,7 @@ function ClassNameGroupControl({
48
48
  {
49
49
  onClick: handleDropdownClick(String(key)),
50
50
  className: selectedKey === String(key) ? "bg-secondary-foreground/10" : "",
51
- children: config.CONFIG[String(key)].label || String(key)
51
+ children: config.CONFIG[String(key)]?.label || String(key)
52
52
  },
53
53
  String(key)
54
54
  )) })
@@ -46,7 +46,7 @@ function ClassNameGroupControl({
46
46
  {
47
47
  onClick: handleDropdownClick(String(key)),
48
48
  className: selectedKey === String(key) ? "bg-secondary-foreground/10" : "",
49
- children: CONFIG[String(key)].label || String(key)
49
+ children: CONFIG[String(key)]?.label || String(key)
50
50
  },
51
51
  String(key)
52
52
  )) })
@@ -34,7 +34,9 @@ function ClassNameItemControl({ value, onChange }) {
34
34
  }
35
35
  }
36
36
  });
37
- if (!found) initialSelected[group.label] = group.keys[0];
37
+ if (!found && group.keys[0]) {
38
+ initialSelected[group.label] = group.keys[0];
39
+ }
38
40
  });
39
41
  const handledTokens = new Set(
40
42
  Object.values(parsedState).flatMap(
@@ -52,7 +54,9 @@ function ClassNameItemControl({ value, onChange }) {
52
54
  } else {
53
55
  const initialSelected = {};
54
56
  config.LAYOUT_GROUPS.forEach((group) => {
55
- initialSelected[group.label] = group.keys[0];
57
+ if (group.keys[0]) {
58
+ initialSelected[group.label] = group.keys[0];
59
+ }
56
60
  });
57
61
  return {
58
62
  parsedState: {},
@@ -174,7 +178,9 @@ function ClassNameItemControl({ value, onChange }) {
174
178
  const keys = group.keys.map(String);
175
179
  const selectedKey = selectedKeys[group.label] || keys[0];
176
180
  if (entry.isVisible && !entry.isVisible(state)) return null;
181
+ if (!selectedKey) return null;
177
182
  const groupConfig = config.CONFIG[selectedKey];
183
+ if (!groupConfig) return null;
178
184
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: utils.cn("w-full", entry.className), "data-testid": `group-${group.label.toLowerCase().replace(/\s+/g, "-")}`, children: /* @__PURE__ */ jsxRuntime.jsx(
179
185
  classnameGroupControl.ClassNameGroupControl,
180
186
  {
@@ -190,6 +196,7 @@ function ClassNameItemControl({ value, onChange }) {
190
196
  if (entry.isVisible && !entry.isVisible(state)) return null;
191
197
  const configKey = entry.key;
192
198
  const ungroupedConfig = config.CONFIG[configKey];
199
+ if (!ungroupedConfig) return null;
193
200
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: utils.cn("w-full", entry.className), "data-testid": `item-${configKey}`, children: /* @__PURE__ */ jsxRuntime.jsx(
194
201
  ungroupedConfig.component,
195
202
  {