@kubuild/editor 0.1.0 → 0.1.2

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.
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  // src/editor.tsx
2
- import React19, { useEffect as useEffect10, useMemo as useMemo4 } from "react";
2
+ import React19, { useEffect as useEffect10, useMemo as useMemo4, useState as useState17 } from "react";
3
3
  import { createDefaultComponentRegistry as createDefaultComponentRegistry2 } from "@kubuild/components";
4
- import { buildSampleVariablesFromCatalog } from "@kubuild/core";
4
+ import {
5
+ buildSampleVariablesFromCatalog
6
+ } from "@kubuild/core";
5
7
 
6
8
  // src/store.ts
7
9
  import { create } from "zustand";
@@ -25,6 +27,9 @@ import {
25
27
  saveDraftAsTemplate,
26
28
  cloneTemplateAsPage
27
29
  } from "@kubuild/core";
30
+ import {
31
+ STARTER_BLOCKS
32
+ } from "@kubuild/components";
28
33
  function formatCommandError(err) {
29
34
  if (err && typeof err === "object" && Array.isArray(err.issues)) {
30
35
  const issues = err.issues;
@@ -49,7 +54,11 @@ function buildDefaultChildren(specs, existingIds, registry) {
49
54
  const def = registry.get(spec.type);
50
55
  const nodeProps = deepClone(spec.props ?? def?.defaultProps ?? {});
51
56
  const nodeStyles = spec.styles ?? def?.defaultStyles;
52
- const childNodes = buildDefaultChildren(spec.children ?? def?.defaultChildren, existingIds, registry);
57
+ const childNodes = buildDefaultChildren(
58
+ spec.children ?? def?.defaultChildren,
59
+ existingIds,
60
+ registry
61
+ );
53
62
  const node = {
54
63
  id,
55
64
  type: spec.type,
@@ -69,6 +78,7 @@ var useEditorStore = create((set, get) => ({
69
78
  document: historyManager.document,
70
79
  selectedNodeId: null,
71
80
  hoveredNodeId: null,
81
+ dragPayload: null,
72
82
  viewport: "desktop",
73
83
  isDirty: false,
74
84
  canUndo: false,
@@ -78,6 +88,7 @@ var useEditorStore = create((set, get) => ({
78
88
  variableCatalog: [],
79
89
  navigatorMode: "floating",
80
90
  tableSpreadsheetMode: "floating",
91
+ setDragPayload: (payload) => set({ dragPayload: payload }),
81
92
  setVariableCatalog: (catalog) => set({ variableCatalog: catalog }),
82
93
  setNavigatorMode: (mode) => set({ navigatorMode: mode }),
83
94
  toggleNavigator: () => set((state) => ({
@@ -94,6 +105,7 @@ var useEditorStore = create((set, get) => ({
94
105
  isDirty: false,
95
106
  selectedNodeId: null,
96
107
  hoveredNodeId: null,
108
+ dragPayload: null,
97
109
  clipboard: null,
98
110
  canUndo: false,
99
111
  canRedo: false
@@ -112,12 +124,15 @@ var useEditorStore = create((set, get) => ({
112
124
  });
113
125
  onChangeHandler?.(result.document);
114
126
  },
115
- insertComponent: (type, registry, parentId) => {
127
+ insertComponent: (type, registry, parentId, index) => {
116
128
  const state = get();
117
129
  const targetParentId = parentId ?? state.selectedNodeId ?? state.document.document.id;
118
130
  const parentNode = findNodeById(state.document.document, targetParentId);
119
131
  if (!parentNode) {
120
- return { success: false, error: `Insertion target "${targetParentId}" was not found in the document.` };
132
+ return {
133
+ success: false,
134
+ error: `Insertion target "${targetParentId}" was not found in the document.`
135
+ };
121
136
  }
122
137
  const definition = registry.get(type);
123
138
  if (!definition) {
@@ -134,7 +149,10 @@ var useEditorStore = create((set, get) => ({
134
149
  return { success: false, error: propResult.join(" ") };
135
150
  }
136
151
  if (propResult === false) {
137
- return { success: false, error: `Default props for "${definition.label}" failed validation.` };
152
+ return {
153
+ success: false,
154
+ error: `Default props for "${definition.label}" failed validation.`
155
+ };
138
156
  }
139
157
  }
140
158
  const existingIds = collectNodeIdSet(state.document.document);
@@ -148,10 +166,49 @@ var useEditorStore = create((set, get) => ({
148
166
  ...definition.defaultStyles ? { styles: deepClone(definition.defaultStyles) } : {},
149
167
  ...children ? { children } : {}
150
168
  };
151
- get().dispatch((doc) => insertNode(doc, { parentId: targetParentId, node }));
169
+ get().dispatch((doc) => insertNode(doc, { parentId: targetParentId, node, index }));
152
170
  get().selectNode(nodeId);
153
171
  return { success: true, nodeId };
154
172
  },
173
+ insertBlock: (blockOrId, parentId, index) => {
174
+ const state = get();
175
+ const targetParentId = parentId ?? state.selectedNodeId ?? state.document.document.id;
176
+ let blockDef;
177
+ if (typeof blockOrId === "string") {
178
+ blockDef = STARTER_BLOCKS.find((b) => b.id === blockOrId);
179
+ } else {
180
+ blockDef = blockOrId;
181
+ }
182
+ if (!blockDef) {
183
+ return { success: false, error: "Block definition not found." };
184
+ }
185
+ const existingIds = collectNodeIdSet(state.document.document);
186
+ let counter = 1;
187
+ const generateId = (prefix = "node") => {
188
+ let id = `${prefix}-${Date.now().toString(36)}-${counter++}`;
189
+ while (existingIds.has(id)) {
190
+ id = `${prefix}-${Date.now().toString(36)}-${counter++}`;
191
+ }
192
+ existingIds.add(id);
193
+ return id;
194
+ };
195
+ const nodeTree = blockDef.createNodeTree(generateId);
196
+ try {
197
+ get().dispatch((doc) => insertNode(doc, { parentId: targetParentId, node: nodeTree, index }));
198
+ get().selectNode(nodeTree.id);
199
+ return { success: true, nodeId: nodeTree.id };
200
+ } catch {
201
+ try {
202
+ get().dispatch(
203
+ (doc) => insertNode(doc, { parentId: state.document.document.id, node: nodeTree })
204
+ );
205
+ get().selectNode(nodeTree.id);
206
+ return { success: true, nodeId: nodeTree.id };
207
+ } catch (err) {
208
+ return { success: false, error: formatCommandError(err) };
209
+ }
210
+ }
211
+ },
155
212
  moveComponent: (nodeId, targetParentId, registry, index) => {
156
213
  const state = get();
157
214
  if (nodeId === state.document.document.id) {
@@ -163,10 +220,16 @@ var useEditorStore = create((set, get) => ({
163
220
  }
164
221
  const targetParent = findNodeById(state.document.document, targetParentId);
165
222
  if (!targetParent) {
166
- return { success: false, error: `Move target "${targetParentId}" was not found in the document.` };
223
+ return {
224
+ success: false,
225
+ error: `Move target "${targetParentId}" was not found in the document.`
226
+ };
167
227
  }
168
228
  if (isDescendantOf(sourceLocation.node, targetParentId)) {
169
- return { success: false, error: "Cannot move a node into itself or one of its own descendants." };
229
+ return {
230
+ success: false,
231
+ error: "Cannot move a node into itself or one of its own descendants."
232
+ };
170
233
  }
171
234
  const policy = registry.canInsertChild(targetParent.type, sourceLocation.node.type);
172
235
  if (!policy.valid) {
@@ -303,10 +366,16 @@ var useEditorStore = create((set, get) => ({
303
366
  }
304
367
  const targetParent = findNodeById(document2.document, targetParentId);
305
368
  if (!targetParent) {
306
- return { success: false, error: `Paste target "${targetParentId}" was not found in the document.` };
369
+ return {
370
+ success: false,
371
+ error: `Paste target "${targetParentId}" was not found in the document.`
372
+ };
307
373
  }
308
374
  if (isDescendantOf(clipboard, targetParentId)) {
309
- return { success: false, error: "Cannot paste a node into itself or one of its own descendants." };
375
+ return {
376
+ success: false,
377
+ error: "Cannot paste a node into itself or one of its own descendants."
378
+ };
310
379
  }
311
380
  const policy = registry.canInsertChild(targetParent.type, clipboard.type);
312
381
  if (!policy.valid) {
@@ -315,7 +384,9 @@ var useEditorStore = create((set, get) => ({
315
384
  const existingIds = collectNodeIdSet(document2.document);
316
385
  const { clonedNode } = cloneTreeWithNewIds(clipboard, void 0, existingIds);
317
386
  try {
318
- get().dispatch((doc) => insertNode(doc, { parentId: targetParentId, node: clonedNode, index }));
387
+ get().dispatch(
388
+ (doc) => insertNode(doc, { parentId: targetParentId, node: clonedNode, index })
389
+ );
319
390
  } catch (err) {
320
391
  return { success: false, error: formatCommandError(err) };
321
392
  }
@@ -485,7 +556,19 @@ var EditorCanvas = ({
485
556
  onDiagnostic,
486
557
  config
487
558
  }) => {
488
- const { document: storeDoc, selectedNodeId, hoveredNodeId, selectNode, hoverNode, updateNodeProps } = useEditorStore();
559
+ const {
560
+ document: storeDoc,
561
+ selectedNodeId,
562
+ hoveredNodeId,
563
+ dragPayload,
564
+ selectNode,
565
+ hoverNode,
566
+ updateNodeProps,
567
+ setDragPayload,
568
+ insertComponent,
569
+ insertBlock,
570
+ moveComponent
571
+ } = useEditorStore();
489
572
  const document2 = propDoc ?? storeDoc;
490
573
  const showFloatingBadges = config?.showFloatingBadges !== false;
491
574
  const containerRef = useRef(null);
@@ -562,7 +645,11 @@ var EditorCanvas = ({
562
645
  const direction = ARROW_DIRECTIONS[e.key];
563
646
  if (direction) {
564
647
  e.preventDefault();
565
- const target = getNavigationTarget(state.document.document, state.selectedNodeId, direction);
648
+ const target = getNavigationTarget(
649
+ state.document.document,
650
+ state.selectedNodeId,
651
+ direction
652
+ );
566
653
  if (target) state.selectNode(target);
567
654
  }
568
655
  };
@@ -589,24 +676,71 @@ var EditorCanvas = ({
589
676
  e.stopPropagation();
590
677
  e.dataTransfer.effectAllowed = "move";
591
678
  e.dataTransfer.setData("text/plain", nodeId);
679
+ e.dataTransfer.setData("application/kubuild-drag-type", "node");
680
+ e.dataTransfer.setData("application/kubuild-node-id", nodeId);
592
681
  setDraggingId(nodeId);
682
+ setDragPayload({ type: "node", nodeId });
593
683
  selectNode(nodeId);
594
684
  };
595
685
  const handleDragOver = (e) => {
596
- if (!draggingId) return;
686
+ const activePayload = dragPayload ?? (draggingId ? { type: "node", nodeId: draggingId } : null);
687
+ if (!activePayload) return;
597
688
  const container = containerRef.current;
598
- const hoveredEl = e.target.closest("[data-kubuild-node]");
599
- if (!container || !hoveredEl) return;
600
- const hoveredId = hoveredEl.getAttribute("data-kubuild-node");
601
- const draggedNode = hoveredId ? findNodeById3(document2.document, draggingId) : null;
602
- const hoveredNode = hoveredId ? findNodeById3(document2.document, hoveredId) : null;
603
- if (!hoveredId || !draggedNode || !hoveredNode || isDescendantOf2(draggedNode, hoveredId)) {
689
+ if (!container) return;
690
+ const containerRect = container.getBoundingClientRect();
691
+ const hoveredEl = e.target.closest(
692
+ "[data-kubuild-node]"
693
+ );
694
+ let incomingType = null;
695
+ if (activePayload.type === "node") {
696
+ const draggedNode = findNodeById3(document2.document, activePayload.nodeId);
697
+ incomingType = draggedNode?.type ?? null;
698
+ } else if (activePayload.type === "component") {
699
+ incomingType = activePayload.componentType;
700
+ } else if (activePayload.type === "block") {
701
+ incomingType = "section";
702
+ }
703
+ if (!incomingType) {
604
704
  setDropTarget(null);
605
705
  e.dataTransfer.dropEffect = "none";
606
706
  return;
607
707
  }
708
+ if (!hoveredEl) {
709
+ const rootNode = document2.document;
710
+ const policy2 = registry.canInsertChild(rootNode.type, incomingType);
711
+ if (!policy2.valid) {
712
+ setDropTarget(null);
713
+ e.dataTransfer.dropEffect = "none";
714
+ return;
715
+ }
716
+ e.preventDefault();
717
+ e.dataTransfer.dropEffect = activePayload.type === "node" ? "move" : "copy";
718
+ setDropTarget({
719
+ parentId: rootNode.id,
720
+ index: rootNode.children?.length ?? 0,
721
+ position: "inside",
722
+ rect: {
723
+ top: 0,
724
+ left: 0,
725
+ width: containerRect.width,
726
+ height: Math.max(containerRect.height, 200)
727
+ }
728
+ });
729
+ return;
730
+ }
731
+ const hoveredId = hoveredEl.getAttribute("data-kubuild-node");
732
+ if (!hoveredId) return;
733
+ if (activePayload.type === "node") {
734
+ const draggedNode = findNodeById3(document2.document, activePayload.nodeId);
735
+ if (!draggedNode || isDescendantOf2(draggedNode, hoveredId)) {
736
+ setDropTarget(null);
737
+ e.dataTransfer.dropEffect = "none";
738
+ return;
739
+ }
740
+ }
741
+ const hoveredNode = findNodeById3(document2.document, hoveredId);
742
+ if (!hoveredNode) return;
608
743
  const elRect = hoveredEl.getBoundingClientRect();
609
- const containerRect = container.getBoundingClientRect();
610
744
  const ratio = elRect.height > 0 ? (e.clientY - elRect.top) / elRect.height : 0.5;
611
745
  const hoveredDef = registry.get(hoveredNode.type);
612
746
  const canGoInside = !!hoveredDef?.acceptsChildren;
@@ -627,18 +761,21 @@ var EditorCanvas = ({
627
761
  targetType: location.parent.type
628
762
  };
629
763
  } else {
630
- setDropTarget(null);
631
- e.dataTransfer.dropEffect = "none";
632
- return;
764
+ candidate = {
765
+ parentId: document2.document.id,
766
+ index: document2.document.children?.length ?? 0,
767
+ position: "inside",
768
+ targetType: document2.document.type
769
+ };
633
770
  }
634
- const policy = registry.canInsertChild(candidate.targetType, draggedNode.type);
771
+ const policy = registry.canInsertChild(candidate.targetType, incomingType);
635
772
  if (!policy.valid) {
636
773
  setDropTarget(null);
637
774
  e.dataTransfer.dropEffect = "none";
638
775
  return;
639
776
  }
640
777
  e.preventDefault();
641
- e.dataTransfer.dropEffect = "move";
778
+ e.dataTransfer.dropEffect = activePayload.type === "node" ? "move" : "copy";
642
779
  setDropTarget({
643
780
  parentId: candidate.parentId,
644
781
  index: candidate.index,
@@ -653,15 +790,29 @@ var EditorCanvas = ({
653
790
  };
654
791
  const handleDrop = (e) => {
655
792
  e.preventDefault();
656
- if (draggingId && dropTarget) {
657
- useEditorStore.getState().moveComponent(draggingId, dropTarget.parentId, registry, dropTarget.index);
793
+ const activePayload = dragPayload ?? (draggingId ? { type: "node", nodeId: draggingId } : null);
794
+ if (activePayload && dropTarget) {
795
+ if (activePayload.type === "node") {
796
+ moveComponent(activePayload.nodeId, dropTarget.parentId, registry, dropTarget.index);
797
+ } else if (activePayload.type === "component") {
798
+ insertComponent(
799
+ activePayload.componentType,
800
+ registry,
801
+ dropTarget.parentId,
802
+ dropTarget.index
803
+ );
804
+ } else if (activePayload.type === "block") {
805
+ insertBlock(activePayload.blockId, dropTarget.parentId, dropTarget.index);
806
+ }
658
807
  }
659
808
  setDraggingId(null);
660
809
  setDropTarget(null);
810
+ setDragPayload(null);
661
811
  };
662
812
  const handleDragEnd = () => {
663
813
  setDraggingId(null);
664
814
  setDropTarget(null);
815
+ setDragPayload(null);
665
816
  };
666
817
  return /* @__PURE__ */ jsxs2(
667
818
  "div",
@@ -1877,14 +2028,7 @@ var CodeViewerModal = ({
1877
2028
  };
1878
2029
 
1879
2030
  // src/toolbar.tsx
1880
- import {
1881
- Copy as Copy2,
1882
- ClipboardPaste,
1883
- CopyPlus,
1884
- Trash2 as Trash22,
1885
- Undo2,
1886
- Redo2
1887
- } from "lucide-react";
2031
+ import { Copy as Copy2, ClipboardPaste, CopyPlus, Trash2 as Trash22, Undo2, Redo2 } from "lucide-react";
1888
2032
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1889
2033
  var ToolbarIconButton = ({
1890
2034
  icon,
@@ -2028,21 +2172,32 @@ var EditorToolbar = ({
2028
2172
  type: "button",
2029
2173
  title: `Navigator / Element Tree (${navigatorMode !== "hidden" ? "Open" : "Hidden"})`,
2030
2174
  onClick: toggleNavigator,
2031
- className: `flex items-center gap-1 text-xs px-2.5 py-1 rounded border transition font-medium cursor-pointer ${navigatorMode !== "hidden" ? "border-blue-500 bg-blue-50 text-blue-700 shadow-xs" : "border-slate-200 bg-white hover:border-slate-300 text-slate-700"}`,
2175
+ className: `hidden sm:flex items-center gap-1 text-xs px-2.5 py-1 rounded border transition font-medium cursor-pointer ${navigatorMode !== "hidden" ? "border-blue-500 bg-blue-50 text-blue-700 shadow-xs" : "border-slate-200 bg-white hover:border-slate-300 text-slate-700"}`,
2032
2176
  children: [
2033
- /* @__PURE__ */ jsxs5("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
2034
- /* @__PURE__ */ jsx6("line", { x1: "8", y1: "6", x2: "21", y2: "6" }),
2035
- /* @__PURE__ */ jsx6("line", { x1: "8", y1: "12", x2: "21", y2: "12" }),
2036
- /* @__PURE__ */ jsx6("line", { x1: "8", y1: "18", x2: "21", y2: "18" }),
2037
- /* @__PURE__ */ jsx6("line", { x1: "3", y1: "6", x2: "3.01", y2: "6" }),
2038
- /* @__PURE__ */ jsx6("line", { x1: "3", y1: "12", x2: "3.01", y2: "12" }),
2039
- /* @__PURE__ */ jsx6("line", { x1: "3", y1: "18", x2: "3.01", y2: "18" })
2040
- ] }),
2177
+ /* @__PURE__ */ jsxs5(
2178
+ "svg",
2179
+ {
2180
+ width: "13",
2181
+ height: "13",
2182
+ viewBox: "0 0 24 24",
2183
+ fill: "none",
2184
+ stroke: "currentColor",
2185
+ strokeWidth: "2",
2186
+ children: [
2187
+ /* @__PURE__ */ jsx6("line", { x1: "8", y1: "6", x2: "21", y2: "6" }),
2188
+ /* @__PURE__ */ jsx6("line", { x1: "8", y1: "12", x2: "21", y2: "12" }),
2189
+ /* @__PURE__ */ jsx6("line", { x1: "8", y1: "18", x2: "21", y2: "18" }),
2190
+ /* @__PURE__ */ jsx6("line", { x1: "3", y1: "6", x2: "3.01", y2: "6" }),
2191
+ /* @__PURE__ */ jsx6("line", { x1: "3", y1: "12", x2: "3.01", y2: "12" }),
2192
+ /* @__PURE__ */ jsx6("line", { x1: "3", y1: "18", x2: "3.01", y2: "18" })
2193
+ ]
2194
+ }
2195
+ ),
2041
2196
  /* @__PURE__ */ jsx6("span", { children: "Navigator" })
2042
2197
  ]
2043
2198
  }
2044
2199
  ),
2045
- showNavigatorToggle && (showClipboard || showHistory || showCodeViewer || showExportImport) && /* @__PURE__ */ jsx6("div", { className: "h-4 w-px bg-slate-200 mx-1" }),
2200
+ showNavigatorToggle && (showClipboard || showHistory || showCodeViewer || showExportImport) && /* @__PURE__ */ jsx6("div", { className: "hidden sm:block h-4 w-px bg-slate-200 mx-1" }),
2046
2201
  (showClipboard || showHistory) && /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1", children: [
2047
2202
  showClipboard && /* @__PURE__ */ jsxs5(Fragment2, { children: [
2048
2203
  /* @__PURE__ */ jsx6(
@@ -2117,21 +2272,21 @@ var EditorToolbar = ({
2117
2272
  )
2118
2273
  ] })
2119
2274
  ] }),
2120
- (showClipboard || showHistory) && (showCodeViewer || showExportImport) && /* @__PURE__ */ jsx6("div", { className: "h-4 w-px bg-slate-200 mx-1" }),
2275
+ (showClipboard || showHistory) && (showCodeViewer || showExportImport) && /* @__PURE__ */ jsx6("div", { className: "hidden sm:block h-4 w-px bg-slate-200 mx-1" }),
2121
2276
  showCodeViewer && /* @__PURE__ */ jsxs5(
2122
2277
  "button",
2123
2278
  {
2124
2279
  type: "button",
2125
2280
  title: "View Semantic HTML & CSS (< >)",
2126
2281
  onClick: () => setIsCodeViewerModalOpen(true),
2127
- className: "flex items-center gap-1.5 text-xs px-2.5 py-1 rounded border border-slate-200 bg-white hover:border-blue-500 hover:text-blue-600 font-medium text-slate-700 transition",
2282
+ className: "flex items-center gap-1.5 text-xs px-2 sm:px-2.5 py-1 rounded border border-slate-200 bg-white hover:border-blue-500 hover:text-blue-600 font-medium text-slate-700 transition",
2128
2283
  children: [
2129
2284
  /* @__PURE__ */ jsx6("span", { className: "font-mono text-[11px] font-bold text-blue-600", children: "<>" }),
2130
- /* @__PURE__ */ jsx6("span", { children: "View Code" })
2285
+ /* @__PURE__ */ jsx6("span", { className: "hidden sm:inline", children: "View Code" })
2131
2286
  ]
2132
2287
  }
2133
2288
  ),
2134
- showExportImport && /* @__PURE__ */ jsxs5(Fragment2, { children: [
2289
+ showExportImport && /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1", children: [
2135
2290
  showCodeViewer && /* @__PURE__ */ jsx6("div", { className: "h-4 w-px bg-slate-200 mx-1" }),
2136
2291
  /* @__PURE__ */ jsx6(
2137
2292
  "button",
@@ -2139,7 +2294,7 @@ var EditorToolbar = ({
2139
2294
  type: "button",
2140
2295
  title: "Import .stora Package",
2141
2296
  onClick: () => setIsImportModalOpen(true),
2142
- className: "text-xs px-2.5 py-1 rounded border border-slate-200 bg-white hover:border-blue-500 hover:text-blue-600 font-medium text-slate-700 transition",
2297
+ className: "text-xs px-2 sm:px-2.5 py-1 rounded border border-slate-200 bg-white hover:border-blue-500 hover:text-blue-600 font-medium text-slate-700 transition",
2143
2298
  children: "Import"
2144
2299
  }
2145
2300
  ),
@@ -2150,8 +2305,8 @@ var EditorToolbar = ({
2150
2305
  title: "Export .stora Archive",
2151
2306
  disabled: isExporting,
2152
2307
  onClick: handleExportStora,
2153
- className: "text-xs px-2.5 py-1 rounded border border-blue-600 bg-blue-600 hover:bg-blue-500 text-white font-medium disabled:opacity-50 transition shadow-sm",
2154
- children: isExporting ? "Exporting..." : "Export .stora"
2308
+ className: "text-xs px-2 sm:px-2.5 py-1 rounded border border-blue-600 bg-blue-600 hover:bg-blue-500 text-white font-medium disabled:opacity-50 transition shadow-xs",
2309
+ children: isExporting ? "..." : "Export"
2155
2310
  }
2156
2311
  ),
2157
2312
  /* @__PURE__ */ jsx6(
@@ -2160,7 +2315,7 @@ var EditorToolbar = ({
2160
2315
  type: "button",
2161
2316
  title: "Download page.json",
2162
2317
  onClick: handleExportJson,
2163
- className: "text-xs px-2 py-1 rounded border border-slate-200 bg-white hover:border-slate-400 text-slate-600 transition",
2318
+ className: "hidden md:inline-block text-xs px-2 py-1 rounded border border-slate-200 bg-white hover:border-slate-400 text-slate-600 transition",
2164
2319
  children: "JSON"
2165
2320
  }
2166
2321
  )
@@ -6973,7 +7128,15 @@ import { Boxes, Blocks, Layers } from "lucide-react";
6973
7128
  // src/component-panel.tsx
6974
7129
  import { useState as useState14 } from "react";
6975
7130
  import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
6976
- var CATEGORY_ORDER = ["layout", "typography", "media", "form", "interactive", "data", "custom"];
7131
+ var CATEGORY_ORDER = [
7132
+ "layout",
7133
+ "typography",
7134
+ "media",
7135
+ "form",
7136
+ "interactive",
7137
+ "data",
7138
+ "custom"
7139
+ ];
6977
7140
  var CATEGORY_LABELS = {
6978
7141
  layout: "Layout",
6979
7142
  typography: "Typography",
@@ -6985,6 +7148,7 @@ var CATEGORY_LABELS = {
6985
7148
  };
6986
7149
  var ComponentPanel = ({ registry, className }) => {
6987
7150
  const insertComponent = useEditorStore((s) => s.insertComponent);
7151
+ const setDragPayload = useEditorStore((s) => s.setDragPayload);
6988
7152
  const [error, setError] = useState14(null);
6989
7153
  const groups = CATEGORY_ORDER.map((category) => ({
6990
7154
  category,
@@ -6994,6 +7158,16 @@ var ComponentPanel = ({ registry, className }) => {
6994
7158
  const result = insertComponent(definition.type, registry);
6995
7159
  setError(result.success ? null : result.error ?? `Could not insert "${definition.label}".`);
6996
7160
  };
7161
+ const handleDragStart = (e, definition) => {
7162
+ e.dataTransfer.effectAllowed = "copy";
7163
+ e.dataTransfer.setData("text/plain", `component:${definition.type}`);
7164
+ e.dataTransfer.setData("application/kubuild-drag-type", "component");
7165
+ e.dataTransfer.setData("application/kubuild-component-type", definition.type);
7166
+ setDragPayload({ type: "component", componentType: definition.type });
7167
+ };
7168
+ const handleDragEnd = () => {
7169
+ setDragPayload(null);
7170
+ };
6997
7171
  return /* @__PURE__ */ jsxs19("div", { className: `flex flex-col gap-4 p-3 overflow-y-auto h-full min-h-0 ${className || ""}`, children: [
6998
7172
  error && /* @__PURE__ */ jsx20(
6999
7173
  "div",
@@ -7009,12 +7183,16 @@ var ComponentPanel = ({ registry, className }) => {
7009
7183
  "button",
7010
7184
  {
7011
7185
  type: "button",
7186
+ draggable: true,
7187
+ onDragStart: (e) => handleDragStart(e, definition),
7188
+ onDragEnd: handleDragEnd,
7012
7189
  onClick: () => handleInsert(definition),
7013
- title: definition.description || definition.label,
7014
- className: "flex flex-col items-center justify-center p-2.5 min-h-[74px] rounded-lg border border-slate-200 bg-white hover:border-blue-400 hover:bg-blue-50/50 hover:shadow text-slate-700 transition group cursor-pointer text-center",
7190
+ "data-testid": `component-item-${definition.type}`,
7191
+ title: definition.description || `Click to insert or drag to canvas: ${definition.label}`,
7192
+ className: "flex flex-col items-center justify-center p-2.5 min-h-[74px] rounded-lg border border-slate-200 bg-white hover:border-blue-400 hover:bg-blue-50/50 hover:shadow text-slate-700 transition group cursor-grab active:cursor-grabbing text-center select-none",
7015
7193
  children: [
7016
- /* @__PURE__ */ jsx20("span", { className: "mb-1.5 text-slate-500 group-hover:text-blue-600 transition-colors", children: /* @__PURE__ */ jsx20(ComponentIcon, { iconOrType: definition.icon ?? definition.type, size: 22 }) }),
7017
- /* @__PURE__ */ jsx20("span", { className: "text-[11px] font-medium text-slate-700 group-hover:text-blue-600 text-center leading-tight break-words w-full select-none", children: definition.label })
7194
+ /* @__PURE__ */ jsx20("span", { className: "mb-1.5 text-slate-500 group-hover:text-blue-600 transition-colors pointer-events-none", children: /* @__PURE__ */ jsx20(ComponentIcon, { iconOrType: definition.icon ?? definition.type, size: 22 }) }),
7195
+ /* @__PURE__ */ jsx20("span", { className: "text-[11px] font-medium text-slate-700 group-hover:text-blue-600 text-center leading-tight break-words w-full pointer-events-none", children: definition.label })
7018
7196
  ]
7019
7197
  },
7020
7198
  definition.type
@@ -7025,7 +7203,7 @@ var ComponentPanel = ({ registry, className }) => {
7025
7203
 
7026
7204
  // src/blocks-panel.tsx
7027
7205
  import { useState as useState15, useMemo as useMemo3 } from "react";
7028
- import { STARTER_BLOCKS } from "@kubuild/components";
7206
+ import { STARTER_BLOCKS as STARTER_BLOCKS2 } from "@kubuild/components";
7029
7207
  import { insertNode as insertNode2, collectNodeIdSet as collectNodeIdSet2 } from "@kubuild/core";
7030
7208
  import { Image as ImageIcon } from "lucide-react";
7031
7209
  import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
@@ -7091,7 +7269,7 @@ var BlockThumbnail = ({ block }) => {
7091
7269
  }
7092
7270
  };
7093
7271
  var BlocksPanel = ({
7094
- blocks = STARTER_BLOCKS,
7272
+ blocks = STARTER_BLOCKS2,
7095
7273
  className,
7096
7274
  onInsertBlock
7097
7275
  }) => {
@@ -7135,6 +7313,17 @@ var BlocksPanel = ({
7135
7313
  selectNode(nodeTree.id);
7136
7314
  }
7137
7315
  };
7316
+ const setDragPayload = useEditorStore((s) => s.setDragPayload);
7317
+ const handleDragStart = (e, block) => {
7318
+ e.dataTransfer.effectAllowed = "copy";
7319
+ e.dataTransfer.setData("text/plain", `block:${block.id}`);
7320
+ e.dataTransfer.setData("application/kubuild-drag-type", "block");
7321
+ e.dataTransfer.setData("application/kubuild-block-id", block.id);
7322
+ setDragPayload({ type: "block", blockId: block.id });
7323
+ };
7324
+ const handleDragEnd = () => {
7325
+ setDragPayload(null);
7326
+ };
7138
7327
  return /* @__PURE__ */ jsxs20(
7139
7328
  "div",
7140
7329
  {
@@ -7165,13 +7354,17 @@ var BlocksPanel = ({
7165
7354
  "button",
7166
7355
  {
7167
7356
  type: "button",
7357
+ draggable: true,
7358
+ onDragStart: (e) => handleDragStart(e, block),
7359
+ onDragEnd: handleDragEnd,
7168
7360
  "data-testid": "block-card",
7169
7361
  "data-block-id": block.id,
7170
7362
  onClick: () => handleInsert(block),
7171
- className: "flex flex-col justify-between p-2 rounded-lg border border-slate-200 bg-white hover:border-blue-400 hover:shadow-md hover:bg-blue-50/20 transition-all text-left group cursor-pointer",
7363
+ title: block.description || `Click to insert or drag to canvas: ${block.name}`,
7364
+ className: "flex flex-col justify-between p-2 rounded-lg border border-slate-200 bg-white hover:border-blue-400 hover:shadow-md hover:bg-blue-50/20 transition-all text-left group cursor-grab active:cursor-grabbing select-none",
7172
7365
  children: [
7173
- /* @__PURE__ */ jsx21("div", { className: "mb-2 w-full", children: /* @__PURE__ */ jsx21(BlockThumbnail, { block }) }),
7174
- /* @__PURE__ */ jsxs20("div", { className: "w-full", children: [
7366
+ /* @__PURE__ */ jsx21("div", { className: "mb-2 w-full pointer-events-none", children: /* @__PURE__ */ jsx21(BlockThumbnail, { block }) }),
7367
+ /* @__PURE__ */ jsxs20("div", { className: "w-full pointer-events-none", children: [
7175
7368
  /* @__PURE__ */ jsx21("div", { className: "flex items-center justify-between gap-1 mb-0.5", children: /* @__PURE__ */ jsx21("span", { className: "text-[11px] font-semibold text-slate-800 group-hover:text-blue-600 truncate", children: block.name }) }),
7176
7369
  /* @__PURE__ */ jsx21("span", { className: "inline-block text-[9px] uppercase tracking-wider font-semibold text-slate-400 bg-slate-100 px-1 py-0.2 rounded", children: block.category })
7177
7370
  ] })
@@ -7321,6 +7514,18 @@ function resolveEditorConfig(config) {
7321
7514
  }
7322
7515
 
7323
7516
  // src/editor.tsx
7517
+ import {
7518
+ Monitor,
7519
+ Tablet,
7520
+ Smartphone,
7521
+ Plus,
7522
+ Layers as Layers2,
7523
+ Sliders,
7524
+ Undo2 as Undo22,
7525
+ Redo2 as Redo22,
7526
+ X as X5,
7527
+ Boxes as Boxes2
7528
+ } from "lucide-react";
7324
7529
  import { jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
7325
7530
  var KubuildEditor = ({
7326
7531
  initialDocument,
@@ -7342,9 +7547,16 @@ var KubuildEditor = ({
7342
7547
  selectedNodeId,
7343
7548
  navigatorMode,
7344
7549
  tableSpreadsheetMode,
7345
- setTableSpreadsheetMode
7550
+ setTableSpreadsheetMode,
7551
+ undo,
7552
+ redo,
7553
+ canUndo,
7554
+ canRedo
7346
7555
  } = useEditorStore();
7347
7556
  const lastLoadedDocRef = React19.useRef(void 0);
7557
+ const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState17(false);
7558
+ const [isMobileInspectorOpen, setIsMobileInspectorOpen] = useState17(false);
7559
+ const [isMobileLayersOpen, setIsMobileLayersOpen] = useState17(false);
7348
7560
  const activeTable = useMemo4(
7349
7561
  () => findActiveTableNode(document2.document, selectedNodeId),
7350
7562
  [document2.document, selectedNodeId]
@@ -7373,65 +7585,245 @@ var KubuildEditor = ({
7373
7585
  }, [context, sampleVariables]);
7374
7586
  const viewportWidthMap = {
7375
7587
  desktop: "w-full max-w-6xl",
7376
- tablet: "w-[768px]",
7377
- mobile: "w-[375px]"
7588
+ tablet: "w-full max-w-[768px] sm:w-[768px]",
7589
+ mobile: "w-full max-w-[375px] sm:w-[375px]"
7590
+ };
7591
+ const viewportIcons = {
7592
+ desktop: /* @__PURE__ */ jsx23(Monitor, { className: "w-3.5 h-3.5" }),
7593
+ tablet: /* @__PURE__ */ jsx23(Tablet, { className: "w-3.5 h-3.5" }),
7594
+ mobile: /* @__PURE__ */ jsx23(Smartphone, { className: "w-3.5 h-3.5" })
7378
7595
  };
7379
7596
  const resolvedConfig = useMemo4(() => resolveEditorConfig(config), [config]);
7380
- return /* @__PURE__ */ jsxs22("div", { className: `flex flex-col h-full bg-slate-100 text-slate-900 relative ${className || ""}`, children: [
7381
- navigatorMode === "floating" && /* @__PURE__ */ jsx23(LayersPanel, { registry }),
7382
- activeTable && tableSpreadsheetMode === "floating" && /* @__PURE__ */ jsx23(
7383
- TableSpreadsheetEditor,
7384
- {
7385
- registry,
7386
- tableNode: activeTable,
7387
- mode: "floating",
7388
- onToggleMode: () => setTableSpreadsheetMode("docked"),
7389
- onClose: () => setTableSpreadsheetMode("hidden")
7390
- }
7391
- ),
7392
- resolvedConfig.toolbar.enabled && /* @__PURE__ */ jsxs22("div", { className: "flex items-center justify-between px-4 py-2 bg-white border-b border-slate-200", children: [
7393
- resolvedConfig.toolbar.showTitle ? /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
7394
- /* @__PURE__ */ jsx23("span", { className: "font-semibold text-slate-800 text-sm", children: "KUBUILD Editor" }),
7395
- /* @__PURE__ */ jsx23("span", { className: "text-xs bg-slate-100 text-slate-600 px-2 py-0.5 rounded border", children: document2.metadata?.title || "Untitled" })
7396
- ] }) : /* @__PURE__ */ jsx23("div", {}),
7397
- /* @__PURE__ */ jsx23(EditorToolbar, { registry, config: resolvedConfig.toolbar }),
7398
- resolvedConfig.toolbar.showViewportSwitcher && /* @__PURE__ */ jsx23("div", { className: "flex items-center gap-1 bg-slate-100 p-1 rounded-md text-xs", children: ["desktop", "tablet", "mobile"].map((vp) => /* @__PURE__ */ jsx23(
7399
- "button",
7400
- {
7401
- type: "button",
7402
- onClick: () => setViewport(vp),
7403
- className: `px-3 py-1 rounded capitalize font-medium transition ${viewport === vp ? "bg-white text-blue-600 shadow-sm" : "text-slate-600 hover:text-slate-900"}`,
7404
- children: vp
7405
- },
7406
- vp
7407
- )) }),
7408
- resolvedConfig.toolbar.showSelectionStatus && /* @__PURE__ */ jsx23("div", { className: "text-xs text-slate-500", children: selectedNodeId ? `Selected: #${selectedNodeId}` : "No element selected" })
7409
- ] }),
7410
- /* @__PURE__ */ jsxs22("div", { className: "flex flex-1 overflow-hidden min-h-0", children: [
7411
- resolvedConfig.sidebar.enabled && /* @__PURE__ */ jsx23("div", { className: "w-80 shrink-0 bg-white border-r border-slate-200 overflow-hidden flex flex-col min-h-0 h-full", children: /* @__PURE__ */ jsx23(LeftSidebar, { registry, config: resolvedConfig.sidebar }) }),
7412
- navigatorMode === "docked" && /* @__PURE__ */ jsx23("div", { className: "w-60 shrink-0 bg-white border-r border-slate-200 overflow-hidden flex flex-col min-h-0 h-full", children: /* @__PURE__ */ jsx23(LayersPanel, { registry }) }),
7413
- /* @__PURE__ */ jsxs22("div", { className: "flex-1 overflow-hidden flex flex-col min-h-0 h-full", children: [
7414
- /* @__PURE__ */ jsx23("div", { className: "flex-1 overflow-auto p-8 flex justify-center items-start min-h-0", children: /* @__PURE__ */ jsx23(
7415
- "div",
7597
+ return /* @__PURE__ */ jsxs22(
7598
+ "div",
7599
+ {
7600
+ className: `flex flex-col h-full bg-slate-100 text-slate-900 relative overflow-hidden ${className || ""}`,
7601
+ children: [
7602
+ navigatorMode === "floating" && /* @__PURE__ */ jsx23(LayersPanel, { registry }),
7603
+ activeTable && tableSpreadsheetMode === "floating" && /* @__PURE__ */ jsx23(
7604
+ TableSpreadsheetEditor,
7416
7605
  {
7417
- className: `${viewportWidthMap[viewport]} bg-white shadow-md rounded-lg overflow-hidden transition-all duration-200 min-h-[600px] border border-slate-200`,
7418
- children: /* @__PURE__ */ jsx23(
7419
- EditorCanvas,
7606
+ registry,
7607
+ tableNode: activeTable,
7608
+ mode: "floating",
7609
+ onToggleMode: () => setTableSpreadsheetMode("docked"),
7610
+ onClose: () => setTableSpreadsheetMode("hidden")
7611
+ }
7612
+ ),
7613
+ isMobileSidebarOpen && resolvedConfig.sidebar.enabled && /* @__PURE__ */ jsxs22("div", { className: "fixed inset-0 z-50 flex lg:hidden animate-in fade-in duration-200", children: [
7614
+ /* @__PURE__ */ jsx23(
7615
+ "div",
7616
+ {
7617
+ className: "fixed inset-0 bg-slate-950/50 backdrop-blur-xs transition-opacity",
7618
+ onClick: () => setIsMobileSidebarOpen(false)
7619
+ }
7620
+ ),
7621
+ /* @__PURE__ */ jsxs22("div", { className: "relative w-80 max-w-[85vw] bg-white h-full shadow-2xl flex flex-col z-10 animate-in slide-in-from-left duration-200", children: [
7622
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center justify-between px-3.5 py-2.5 border-b border-slate-200 bg-slate-50", children: [
7623
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
7624
+ /* @__PURE__ */ jsx23(Boxes2, { className: "w-4 h-4 text-blue-600" }),
7625
+ /* @__PURE__ */ jsx23("span", { className: "font-bold text-xs text-slate-800", children: "Add Components & Blocks" })
7626
+ ] }),
7627
+ /* @__PURE__ */ jsx23(
7628
+ "button",
7629
+ {
7630
+ type: "button",
7631
+ onClick: () => setIsMobileSidebarOpen(false),
7632
+ className: "p-1 rounded-md text-slate-400 hover:text-slate-700 hover:bg-slate-200 transition",
7633
+ children: /* @__PURE__ */ jsx23(X5, { className: "w-4 h-4" })
7634
+ }
7635
+ )
7636
+ ] }),
7637
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 overflow-hidden min-h-0", children: /* @__PURE__ */ jsx23(LeftSidebar, { registry, config: resolvedConfig.sidebar }) })
7638
+ ] })
7639
+ ] }),
7640
+ isMobileInspectorOpen && resolvedConfig.inspector.enabled && /* @__PURE__ */ jsxs22("div", { className: "fixed inset-0 z-50 flex justify-end lg:hidden animate-in fade-in duration-200", children: [
7641
+ /* @__PURE__ */ jsx23(
7642
+ "div",
7643
+ {
7644
+ className: "fixed inset-0 bg-slate-950/50 backdrop-blur-xs transition-opacity",
7645
+ onClick: () => setIsMobileInspectorOpen(false)
7646
+ }
7647
+ ),
7648
+ /* @__PURE__ */ jsxs22("div", { className: "relative w-84 max-w-[90vw] sm:max-w-md bg-white h-full shadow-2xl flex flex-col z-10 animate-in slide-in-from-right duration-200", children: [
7649
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center justify-between px-3.5 py-2.5 border-b border-slate-200 bg-slate-50", children: [
7650
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
7651
+ /* @__PURE__ */ jsx23(Sliders, { className: "w-4 h-4 text-blue-600" }),
7652
+ /* @__PURE__ */ jsx23("span", { className: "font-bold text-xs text-slate-800", children: selectedNodeId ? `Inspector (#${selectedNodeId})` : "Inspector & Properties" })
7653
+ ] }),
7654
+ /* @__PURE__ */ jsx23(
7655
+ "button",
7656
+ {
7657
+ type: "button",
7658
+ onClick: () => setIsMobileInspectorOpen(false),
7659
+ className: "p-1 rounded-md text-slate-400 hover:text-slate-700 hover:bg-slate-200 transition",
7660
+ children: /* @__PURE__ */ jsx23(X5, { className: "w-4 h-4" })
7661
+ }
7662
+ )
7663
+ ] }),
7664
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 overflow-hidden min-h-0", children: /* @__PURE__ */ jsx23(InspectorPanel, { registry, config: resolvedConfig.inspector }) })
7665
+ ] })
7666
+ ] }),
7667
+ isMobileLayersOpen && /* @__PURE__ */ jsxs22("div", { className: "fixed inset-0 z-50 flex lg:hidden animate-in fade-in duration-200", children: [
7668
+ /* @__PURE__ */ jsx23(
7669
+ "div",
7670
+ {
7671
+ className: "fixed inset-0 bg-slate-950/50 backdrop-blur-xs transition-opacity",
7672
+ onClick: () => setIsMobileLayersOpen(false)
7673
+ }
7674
+ ),
7675
+ /* @__PURE__ */ jsxs22("div", { className: "relative w-72 max-w-[85vw] bg-white h-full shadow-2xl flex flex-col z-10 animate-in slide-in-from-left duration-200", children: [
7676
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center justify-between px-3.5 py-2.5 border-b border-slate-200 bg-slate-50", children: [
7677
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
7678
+ /* @__PURE__ */ jsx23(Layers2, { className: "w-4 h-4 text-blue-600" }),
7679
+ /* @__PURE__ */ jsx23("span", { className: "font-bold text-xs text-slate-800", children: "Element Tree / Layers" })
7680
+ ] }),
7681
+ /* @__PURE__ */ jsx23(
7682
+ "button",
7683
+ {
7684
+ type: "button",
7685
+ onClick: () => setIsMobileLayersOpen(false),
7686
+ className: "p-1 rounded-md text-slate-400 hover:text-slate-700 hover:bg-slate-200 transition",
7687
+ children: /* @__PURE__ */ jsx23(X5, { className: "w-4 h-4" })
7688
+ }
7689
+ )
7690
+ ] }),
7691
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 overflow-hidden min-h-0", children: /* @__PURE__ */ jsx23(LayersPanel, { registry }) })
7692
+ ] })
7693
+ ] }),
7694
+ resolvedConfig.toolbar.enabled && /* @__PURE__ */ jsxs22("div", { className: "flex items-center justify-between px-3 py-1.5 sm:px-4 sm:py-2 bg-white border-b border-slate-200 gap-2 overflow-x-auto min-h-[44px]", children: [
7695
+ resolvedConfig.toolbar.showTitle ? /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2 shrink-0", children: [
7696
+ /* @__PURE__ */ jsx23("span", { className: "font-bold text-slate-800 text-xs sm:text-sm tracking-tight", children: "KUBUILD Editor" }),
7697
+ /* @__PURE__ */ jsx23("span", { className: "text-[11px] bg-slate-100 text-slate-600 px-1.5 py-0.5 rounded border max-w-[120px] sm:max-w-[200px] truncate font-medium", children: document2.metadata?.title || "Untitled" })
7698
+ ] }) : /* @__PURE__ */ jsx23("div", {}),
7699
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2 shrink-0", children: [
7700
+ /* @__PURE__ */ jsx23(EditorToolbar, { registry, config: resolvedConfig.toolbar }),
7701
+ resolvedConfig.toolbar.showViewportSwitcher && /* @__PURE__ */ jsx23("div", { className: "flex items-center gap-0.5 bg-slate-100 p-0.5 sm:p-1 rounded-md text-xs border border-slate-200/80", children: ["desktop", "tablet", "mobile"].map((vp) => /* @__PURE__ */ jsxs22(
7702
+ "button",
7703
+ {
7704
+ type: "button",
7705
+ onClick: () => setViewport(vp),
7706
+ title: `Switch to ${vp} preview`,
7707
+ className: `px-2 py-1 sm:px-2.5 sm:py-1 rounded capitalize font-medium transition flex items-center gap-1 text-[11px] sm:text-xs ${viewport === vp ? "bg-white text-blue-600 shadow-xs font-semibold" : "text-slate-600 hover:text-slate-900"}`,
7708
+ children: [
7709
+ viewportIcons[vp],
7710
+ /* @__PURE__ */ jsx23("span", { className: "hidden md:inline", children: vp })
7711
+ ]
7712
+ },
7713
+ vp
7714
+ )) })
7715
+ ] }),
7716
+ resolvedConfig.toolbar.showSelectionStatus && /* @__PURE__ */ jsx23("div", { className: "hidden xl:block text-xs text-slate-500 shrink-0 font-mono", children: selectedNodeId ? `Selected: #${selectedNodeId}` : "No element selected" })
7717
+ ] }),
7718
+ /* @__PURE__ */ jsxs22("div", { className: "flex flex-1 overflow-hidden min-h-0 relative", children: [
7719
+ resolvedConfig.sidebar.enabled && /* @__PURE__ */ jsx23("div", { className: "hidden lg:flex w-80 shrink-0 bg-white border-r border-slate-200 overflow-hidden flex-col min-h-0 h-full", children: /* @__PURE__ */ jsx23(LeftSidebar, { registry, config: resolvedConfig.sidebar }) }),
7720
+ navigatorMode === "docked" && /* @__PURE__ */ jsx23("div", { className: "hidden lg:flex w-60 shrink-0 bg-white border-r border-slate-200 overflow-hidden flex-col min-h-0 h-full", children: /* @__PURE__ */ jsx23(LayersPanel, { registry }) }),
7721
+ /* @__PURE__ */ jsxs22("div", { className: "flex-1 overflow-hidden flex flex-col min-h-0 h-full bg-slate-100/90 relative", children: [
7722
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 overflow-auto p-2 sm:p-4 md:p-8 flex justify-center items-start min-h-0", children: /* @__PURE__ */ jsx23(
7723
+ "div",
7420
7724
  {
7421
- registry,
7422
- context: previewContext,
7423
- viewport,
7424
- onDiagnostic,
7425
- config: resolvedConfig.canvas
7725
+ className: `${viewportWidthMap[viewport]} bg-white shadow-md rounded-lg overflow-hidden transition-all duration-200 min-h-[500px] border border-slate-200`,
7726
+ children: /* @__PURE__ */ jsx23(
7727
+ EditorCanvas,
7728
+ {
7729
+ registry,
7730
+ context: previewContext,
7731
+ viewport,
7732
+ onDiagnostic,
7733
+ config: resolvedConfig.canvas
7734
+ }
7735
+ )
7736
+ }
7737
+ ) }),
7738
+ selectedNodeId && !isMobileInspectorOpen && resolvedConfig.inspector.enabled && /* @__PURE__ */ jsx23("div", { className: "lg:hidden fixed bottom-14 left-1/2 -translate-x-1/2 z-30 animate-in fade-in slide-in-from-bottom-2", children: /* @__PURE__ */ jsxs22(
7739
+ "button",
7740
+ {
7741
+ type: "button",
7742
+ onClick: () => setIsMobileInspectorOpen(true),
7743
+ className: "flex items-center gap-2 bg-blue-600 hover:bg-blue-500 text-white text-xs font-semibold px-4 py-2 rounded-full shadow-xl border border-blue-400/40 active:scale-95 transition",
7744
+ children: [
7745
+ /* @__PURE__ */ jsx23(Sliders, { className: "w-3.5 h-3.5" }),
7746
+ /* @__PURE__ */ jsxs22("span", { children: [
7747
+ "Edit Element (#",
7748
+ selectedNodeId,
7749
+ ")"
7750
+ ] })
7751
+ ]
7752
+ }
7753
+ ) }),
7754
+ resolvedConfig.canvas.showBreadcrumbs && /* @__PURE__ */ jsx23(HierarchyBreadcrumbs, { registry })
7755
+ ] }),
7756
+ resolvedConfig.inspector.enabled && /* @__PURE__ */ jsx23("div", { className: "hidden lg:flex w-72 shrink-0 bg-white border-l border-slate-200 overflow-hidden flex-col min-h-0 h-full", children: /* @__PURE__ */ jsx23(InspectorPanel, { registry, config: resolvedConfig.inspector }) })
7757
+ ] }),
7758
+ /* @__PURE__ */ jsxs22("div", { className: "flex lg:hidden items-center justify-around bg-white border-t border-slate-200 px-2 py-1.5 z-20 shrink-0 shadow-lg select-none min-h-[48px]", children: [
7759
+ resolvedConfig.sidebar.enabled && /* @__PURE__ */ jsxs22(
7760
+ "button",
7761
+ {
7762
+ type: "button",
7763
+ onClick: () => setIsMobileSidebarOpen(true),
7764
+ className: "flex flex-col items-center justify-center gap-0.5 px-3 py-1 rounded-md text-slate-600 hover:text-blue-600 hover:bg-slate-50 active:bg-slate-100 transition",
7765
+ children: [
7766
+ /* @__PURE__ */ jsx23(Plus, { className: "w-4 h-4 text-blue-600" }),
7767
+ /* @__PURE__ */ jsx23("span", { className: "text-[10px] font-medium", children: "Add" })
7768
+ ]
7769
+ }
7770
+ ),
7771
+ /* @__PURE__ */ jsxs22(
7772
+ "button",
7773
+ {
7774
+ type: "button",
7775
+ onClick: () => setIsMobileLayersOpen(true),
7776
+ className: "flex flex-col items-center justify-center gap-0.5 px-3 py-1 rounded-md text-slate-600 hover:text-blue-600 hover:bg-slate-50 active:bg-slate-100 transition",
7777
+ children: [
7778
+ /* @__PURE__ */ jsx23(Layers2, { className: "w-4 h-4" }),
7779
+ /* @__PURE__ */ jsx23("span", { className: "text-[10px] font-medium", children: "Layers" })
7780
+ ]
7781
+ }
7782
+ ),
7783
+ resolvedConfig.inspector.enabled && /* @__PURE__ */ jsxs22(
7784
+ "button",
7785
+ {
7786
+ type: "button",
7787
+ onClick: () => setIsMobileInspectorOpen(true),
7788
+ className: `flex flex-col items-center justify-center gap-0.5 px-3 py-1 rounded-md transition relative ${selectedNodeId ? "text-blue-600 font-semibold" : "text-slate-600 hover:text-blue-600 hover:bg-slate-50"}`,
7789
+ children: [
7790
+ /* @__PURE__ */ jsxs22("div", { className: "relative", children: [
7791
+ /* @__PURE__ */ jsx23(Sliders, { className: "w-4 h-4" }),
7792
+ selectedNodeId && /* @__PURE__ */ jsx23("span", { className: "absolute -top-1 -right-1 w-2 h-2 rounded-full bg-blue-600 ring-2 ring-white" })
7793
+ ] }),
7794
+ /* @__PURE__ */ jsx23("span", { className: "text-[10px]", children: selectedNodeId ? "Inspect *" : "Inspect" })
7795
+ ]
7796
+ }
7797
+ ),
7798
+ /* @__PURE__ */ jsx23("div", { className: "h-5 w-px bg-slate-200 mx-0.5" }),
7799
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-1", children: [
7800
+ /* @__PURE__ */ jsx23(
7801
+ "button",
7802
+ {
7803
+ type: "button",
7804
+ disabled: !canUndo,
7805
+ onClick: undo,
7806
+ title: "Undo",
7807
+ className: "p-2 rounded text-slate-600 hover:text-blue-600 disabled:opacity-30 disabled:hover:text-slate-600 transition",
7808
+ children: /* @__PURE__ */ jsx23(Undo22, { className: "w-4 h-4" })
7809
+ }
7810
+ ),
7811
+ /* @__PURE__ */ jsx23(
7812
+ "button",
7813
+ {
7814
+ type: "button",
7815
+ disabled: !canRedo,
7816
+ onClick: redo,
7817
+ title: "Redo",
7818
+ className: "p-2 rounded text-slate-600 hover:text-blue-600 disabled:opacity-30 disabled:hover:text-slate-600 transition",
7819
+ children: /* @__PURE__ */ jsx23(Redo22, { className: "w-4 h-4" })
7426
7820
  }
7427
7821
  )
7428
- }
7429
- ) }),
7430
- resolvedConfig.canvas.showBreadcrumbs && /* @__PURE__ */ jsx23(HierarchyBreadcrumbs, { registry })
7431
- ] }),
7432
- resolvedConfig.inspector.enabled && /* @__PURE__ */ jsx23("div", { className: "w-72 shrink-0 bg-white border-l border-slate-200 overflow-hidden flex flex-col min-h-0 h-full", children: /* @__PURE__ */ jsx23(InspectorPanel, { registry, config: resolvedConfig.inspector }) })
7433
- ] })
7434
- ] });
7822
+ ] })
7823
+ ] })
7824
+ ]
7825
+ }
7826
+ );
7435
7827
  };
7436
7828
  export {
7437
7829
  ANIMATION_TYPES,