@kubuild/editor 0.1.0 → 0.1.3

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
  )
@@ -3001,15 +3156,18 @@ var TableSpreadsheetEditor = ({
3001
3156
  setIsImportModalOpen(false);
3002
3157
  setCsvInputText("");
3003
3158
  };
3004
- const [pos, setPos] = useState6({ x: 260, y: 120 });
3159
+ const [pos, setPos] = useState6(() => ({
3160
+ x: typeof window !== "undefined" ? Math.max(10, Math.min(window.innerWidth - 400, 20)) : 20,
3161
+ y: 120
3162
+ }));
3005
3163
  const [isDragging, setIsDragging] = useState6(false);
3006
3164
  const dragStartRef = useRef4({
3007
3165
  startX: 0,
3008
3166
  startY: 0,
3009
- posX: 260,
3167
+ posX: 20,
3010
3168
  posY: 120
3011
3169
  });
3012
- const handleHeaderMouseDown = (e) => {
3170
+ const handleHeaderPointerDown = (e) => {
3013
3171
  if (e.target.closest("button, input, select, textarea")) return;
3014
3172
  setIsDragging(true);
3015
3173
  dragStartRef.current = {
@@ -3021,22 +3179,26 @@ var TableSpreadsheetEditor = ({
3021
3179
  };
3022
3180
  useEffect2(() => {
3023
3181
  if (!isDragging) return;
3024
- const handleMouseMove = (e) => {
3182
+ const handlePointerMove = (e) => {
3025
3183
  const dx = e.clientX - dragStartRef.current.startX;
3026
3184
  const dy = e.clientY - dragStartRef.current.startY;
3185
+ const maxX = Math.max(8, window.innerWidth - 320);
3186
+ const maxY = Math.max(8, window.innerHeight - 100);
3027
3187
  setPos({
3028
- x: Math.max(10, Math.min(window.innerWidth - 350, dragStartRef.current.posX + dx)),
3029
- y: Math.max(10, Math.min(window.innerHeight - 150, dragStartRef.current.posY + dy))
3188
+ x: Math.max(8, Math.min(maxX, dragStartRef.current.posX + dx)),
3189
+ y: Math.max(8, Math.min(maxY, dragStartRef.current.posY + dy))
3030
3190
  });
3031
3191
  };
3032
- const handleMouseUp = () => {
3192
+ const handlePointerUp = () => {
3033
3193
  setIsDragging(false);
3034
3194
  };
3035
- window.addEventListener("mousemove", handleMouseMove);
3036
- window.addEventListener("mouseup", handleMouseUp);
3195
+ window.addEventListener("pointermove", handlePointerMove);
3196
+ window.addEventListener("pointerup", handlePointerUp);
3197
+ window.addEventListener("pointercancel", handlePointerUp);
3037
3198
  return () => {
3038
- window.removeEventListener("mousemove", handleMouseMove);
3039
- window.removeEventListener("mouseup", handleMouseUp);
3199
+ window.removeEventListener("pointermove", handlePointerMove);
3200
+ window.removeEventListener("pointerup", handlePointerUp);
3201
+ window.removeEventListener("pointercancel", handlePointerUp);
3040
3202
  };
3041
3203
  }, [isDragging]);
3042
3204
  if (!tableNode) return null;
@@ -3590,9 +3752,9 @@ var TableSpreadsheetEditor = ({
3590
3752
  /* @__PURE__ */ jsx10(
3591
3753
  "div",
3592
3754
  {
3593
- style: { left: `${pos.x}px`, top: `${pos.y}px` },
3594
- onMouseDown: handleHeaderMouseDown,
3595
- className: `fixed z-40 min-w-[380px] max-w-[620px] bg-white/95 backdrop-blur-md rounded-xl shadow-2xl border border-slate-300 flex flex-col max-h-[460px] overflow-hidden ${className}`,
3755
+ style: { left: `${pos.x}px`, top: `${pos.y}px`, touchAction: "none" },
3756
+ onPointerDown: handleHeaderPointerDown,
3757
+ className: `fixed z-40 min-w-[320px] sm:min-w-[380px] max-w-[620px] bg-white/95 backdrop-blur-md rounded-xl shadow-2xl border border-slate-300 flex flex-col max-h-[460px] overflow-hidden cursor-grab active:cursor-grabbing touch-none ${className}`,
3596
3758
  children: content
3597
3759
  }
3598
3760
  )
@@ -6657,16 +6819,19 @@ var LayersPanel = ({ registry, className }) => {
6657
6819
  const [expandedIds, setExpandedIds] = useState13(() => /* @__PURE__ */ new Set([document2.document.id]));
6658
6820
  const [draggingId, setDraggingId] = useState13(null);
6659
6821
  const [dropCandidate, setDropCandidate] = useState13(null);
6660
- const [pos, setPos] = useState13({ x: 280, y: 70 });
6822
+ const [pos, setPos] = useState13(() => ({
6823
+ x: typeof window !== "undefined" ? Math.max(12, Math.min(window.innerWidth - 300, 24)) : 24,
6824
+ y: 70
6825
+ }));
6661
6826
  const [isDraggingWindow, setIsDraggingWindow] = useState13(false);
6662
6827
  const dragStartRef = useRef9({
6663
6828
  startX: 0,
6664
6829
  startY: 0,
6665
- posX: 280,
6830
+ posX: 24,
6666
6831
  posY: 70
6667
6832
  });
6668
- const handleHeaderMouseDown = (e) => {
6669
- if (e.target.closest("button")) return;
6833
+ const handleHeaderPointerDown = (e) => {
6834
+ if (e.target.closest("button, input, select, textarea")) return;
6670
6835
  setIsDraggingWindow(true);
6671
6836
  dragStartRef.current = {
6672
6837
  startX: e.clientX,
@@ -6677,22 +6842,26 @@ var LayersPanel = ({ registry, className }) => {
6677
6842
  };
6678
6843
  useEffect9(() => {
6679
6844
  if (!isDraggingWindow) return;
6680
- const handleMouseMove = (e) => {
6845
+ const handlePointerMove = (e) => {
6681
6846
  const dx = e.clientX - dragStartRef.current.startX;
6682
6847
  const dy = e.clientY - dragStartRef.current.startY;
6848
+ const maxX = Math.max(8, window.innerWidth - 296);
6849
+ const maxY = Math.max(8, window.innerHeight - 100);
6683
6850
  setPos({
6684
- x: Math.max(10, Math.min(window.innerWidth - 280, dragStartRef.current.posX + dx)),
6685
- y: Math.max(10, Math.min(window.innerHeight - 120, dragStartRef.current.posY + dy))
6851
+ x: Math.max(8, Math.min(maxX, dragStartRef.current.posX + dx)),
6852
+ y: Math.max(8, Math.min(maxY, dragStartRef.current.posY + dy))
6686
6853
  });
6687
6854
  };
6688
- const handleMouseUp = () => {
6855
+ const handlePointerUp = () => {
6689
6856
  setIsDraggingWindow(false);
6690
6857
  };
6691
- window.addEventListener("mousemove", handleMouseMove);
6692
- window.addEventListener("mouseup", handleMouseUp);
6858
+ window.addEventListener("pointermove", handlePointerMove);
6859
+ window.addEventListener("pointerup", handlePointerUp);
6860
+ window.addEventListener("pointercancel", handlePointerUp);
6693
6861
  return () => {
6694
- window.removeEventListener("mousemove", handleMouseMove);
6695
- window.removeEventListener("mouseup", handleMouseUp);
6862
+ window.removeEventListener("pointermove", handlePointerMove);
6863
+ window.removeEventListener("pointerup", handlePointerUp);
6864
+ window.removeEventListener("pointercancel", handlePointerUp);
6696
6865
  };
6697
6866
  }, [isDraggingWindow]);
6698
6867
  useEffect9(() => {
@@ -6843,8 +7012,9 @@ var LayersPanel = ({ registry, className }) => {
6843
7012
  const header = /* @__PURE__ */ jsxs17(
6844
7013
  "div",
6845
7014
  {
6846
- onMouseDown: isFloating ? handleHeaderMouseDown : void 0,
6847
- className: `flex items-center justify-between px-3 py-2 border-b border-slate-200/90 select-none ${isFloating ? "cursor-grab active:cursor-grabbing bg-slate-50/90 rounded-t-xl" : "bg-slate-50"}`,
7015
+ onPointerDown: isFloating ? handleHeaderPointerDown : void 0,
7016
+ style: { touchAction: isFloating ? "none" : "auto" },
7017
+ className: `flex items-center justify-between px-3 py-2 border-b border-slate-200/90 select-none ${isFloating ? "cursor-grab active:cursor-grabbing bg-slate-50/90 rounded-t-xl touch-none" : "bg-slate-50"}`,
6848
7018
  children: [
6849
7019
  /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-1.5 font-semibold text-xs text-slate-700", children: [
6850
7020
  isFloating && /* @__PURE__ */ jsx18(GripVertical, { className: "w-3 h-3 text-slate-400 mr-0.5", "aria-hidden": "true" }),
@@ -6973,7 +7143,15 @@ import { Boxes, Blocks, Layers } from "lucide-react";
6973
7143
  // src/component-panel.tsx
6974
7144
  import { useState as useState14 } from "react";
6975
7145
  import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
6976
- var CATEGORY_ORDER = ["layout", "typography", "media", "form", "interactive", "data", "custom"];
7146
+ var CATEGORY_ORDER = [
7147
+ "layout",
7148
+ "typography",
7149
+ "media",
7150
+ "form",
7151
+ "interactive",
7152
+ "data",
7153
+ "custom"
7154
+ ];
6977
7155
  var CATEGORY_LABELS = {
6978
7156
  layout: "Layout",
6979
7157
  typography: "Typography",
@@ -6985,6 +7163,7 @@ var CATEGORY_LABELS = {
6985
7163
  };
6986
7164
  var ComponentPanel = ({ registry, className }) => {
6987
7165
  const insertComponent = useEditorStore((s) => s.insertComponent);
7166
+ const setDragPayload = useEditorStore((s) => s.setDragPayload);
6988
7167
  const [error, setError] = useState14(null);
6989
7168
  const groups = CATEGORY_ORDER.map((category) => ({
6990
7169
  category,
@@ -6994,6 +7173,16 @@ var ComponentPanel = ({ registry, className }) => {
6994
7173
  const result = insertComponent(definition.type, registry);
6995
7174
  setError(result.success ? null : result.error ?? `Could not insert "${definition.label}".`);
6996
7175
  };
7176
+ const handleDragStart = (e, definition) => {
7177
+ e.dataTransfer.effectAllowed = "copy";
7178
+ e.dataTransfer.setData("text/plain", `component:${definition.type}`);
7179
+ e.dataTransfer.setData("application/kubuild-drag-type", "component");
7180
+ e.dataTransfer.setData("application/kubuild-component-type", definition.type);
7181
+ setDragPayload({ type: "component", componentType: definition.type });
7182
+ };
7183
+ const handleDragEnd = () => {
7184
+ setDragPayload(null);
7185
+ };
6997
7186
  return /* @__PURE__ */ jsxs19("div", { className: `flex flex-col gap-4 p-3 overflow-y-auto h-full min-h-0 ${className || ""}`, children: [
6998
7187
  error && /* @__PURE__ */ jsx20(
6999
7188
  "div",
@@ -7009,12 +7198,16 @@ var ComponentPanel = ({ registry, className }) => {
7009
7198
  "button",
7010
7199
  {
7011
7200
  type: "button",
7201
+ draggable: true,
7202
+ onDragStart: (e) => handleDragStart(e, definition),
7203
+ onDragEnd: handleDragEnd,
7012
7204
  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",
7205
+ "data-testid": `component-item-${definition.type}`,
7206
+ title: definition.description || `Click to insert or drag to canvas: ${definition.label}`,
7207
+ 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
7208
  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 })
7209
+ /* @__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 }) }),
7210
+ /* @__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
7211
  ]
7019
7212
  },
7020
7213
  definition.type
@@ -7025,7 +7218,7 @@ var ComponentPanel = ({ registry, className }) => {
7025
7218
 
7026
7219
  // src/blocks-panel.tsx
7027
7220
  import { useState as useState15, useMemo as useMemo3 } from "react";
7028
- import { STARTER_BLOCKS } from "@kubuild/components";
7221
+ import { STARTER_BLOCKS as STARTER_BLOCKS2 } from "@kubuild/components";
7029
7222
  import { insertNode as insertNode2, collectNodeIdSet as collectNodeIdSet2 } from "@kubuild/core";
7030
7223
  import { Image as ImageIcon } from "lucide-react";
7031
7224
  import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
@@ -7091,7 +7284,7 @@ var BlockThumbnail = ({ block }) => {
7091
7284
  }
7092
7285
  };
7093
7286
  var BlocksPanel = ({
7094
- blocks = STARTER_BLOCKS,
7287
+ blocks = STARTER_BLOCKS2,
7095
7288
  className,
7096
7289
  onInsertBlock
7097
7290
  }) => {
@@ -7135,6 +7328,17 @@ var BlocksPanel = ({
7135
7328
  selectNode(nodeTree.id);
7136
7329
  }
7137
7330
  };
7331
+ const setDragPayload = useEditorStore((s) => s.setDragPayload);
7332
+ const handleDragStart = (e, block) => {
7333
+ e.dataTransfer.effectAllowed = "copy";
7334
+ e.dataTransfer.setData("text/plain", `block:${block.id}`);
7335
+ e.dataTransfer.setData("application/kubuild-drag-type", "block");
7336
+ e.dataTransfer.setData("application/kubuild-block-id", block.id);
7337
+ setDragPayload({ type: "block", blockId: block.id });
7338
+ };
7339
+ const handleDragEnd = () => {
7340
+ setDragPayload(null);
7341
+ };
7138
7342
  return /* @__PURE__ */ jsxs20(
7139
7343
  "div",
7140
7344
  {
@@ -7165,13 +7369,17 @@ var BlocksPanel = ({
7165
7369
  "button",
7166
7370
  {
7167
7371
  type: "button",
7372
+ draggable: true,
7373
+ onDragStart: (e) => handleDragStart(e, block),
7374
+ onDragEnd: handleDragEnd,
7168
7375
  "data-testid": "block-card",
7169
7376
  "data-block-id": block.id,
7170
7377
  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",
7378
+ title: block.description || `Click to insert or drag to canvas: ${block.name}`,
7379
+ 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
7380
  children: [
7173
- /* @__PURE__ */ jsx21("div", { className: "mb-2 w-full", children: /* @__PURE__ */ jsx21(BlockThumbnail, { block }) }),
7174
- /* @__PURE__ */ jsxs20("div", { className: "w-full", children: [
7381
+ /* @__PURE__ */ jsx21("div", { className: "mb-2 w-full pointer-events-none", children: /* @__PURE__ */ jsx21(BlockThumbnail, { block }) }),
7382
+ /* @__PURE__ */ jsxs20("div", { className: "w-full pointer-events-none", children: [
7175
7383
  /* @__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
7384
  /* @__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
7385
  ] })
@@ -7321,6 +7529,18 @@ function resolveEditorConfig(config) {
7321
7529
  }
7322
7530
 
7323
7531
  // src/editor.tsx
7532
+ import {
7533
+ Monitor,
7534
+ Tablet,
7535
+ Smartphone,
7536
+ Plus,
7537
+ Layers as Layers2,
7538
+ Sliders,
7539
+ Undo2 as Undo22,
7540
+ Redo2 as Redo22,
7541
+ X as X5,
7542
+ Boxes as Boxes2
7543
+ } from "lucide-react";
7324
7544
  import { jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
7325
7545
  var KubuildEditor = ({
7326
7546
  initialDocument,
@@ -7342,9 +7562,16 @@ var KubuildEditor = ({
7342
7562
  selectedNodeId,
7343
7563
  navigatorMode,
7344
7564
  tableSpreadsheetMode,
7345
- setTableSpreadsheetMode
7565
+ setTableSpreadsheetMode,
7566
+ undo,
7567
+ redo,
7568
+ canUndo,
7569
+ canRedo
7346
7570
  } = useEditorStore();
7347
7571
  const lastLoadedDocRef = React19.useRef(void 0);
7572
+ const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState17(false);
7573
+ const [isMobileInspectorOpen, setIsMobileInspectorOpen] = useState17(false);
7574
+ const [isMobileLayersOpen, setIsMobileLayersOpen] = useState17(false);
7348
7575
  const activeTable = useMemo4(
7349
7576
  () => findActiveTableNode(document2.document, selectedNodeId),
7350
7577
  [document2.document, selectedNodeId]
@@ -7373,65 +7600,245 @@ var KubuildEditor = ({
7373
7600
  }, [context, sampleVariables]);
7374
7601
  const viewportWidthMap = {
7375
7602
  desktop: "w-full max-w-6xl",
7376
- tablet: "w-[768px]",
7377
- mobile: "w-[375px]"
7603
+ tablet: "w-full max-w-[768px] sm:w-[768px]",
7604
+ mobile: "w-full max-w-[375px] sm:w-[375px]"
7605
+ };
7606
+ const viewportIcons = {
7607
+ desktop: /* @__PURE__ */ jsx23(Monitor, { className: "w-3.5 h-3.5" }),
7608
+ tablet: /* @__PURE__ */ jsx23(Tablet, { className: "w-3.5 h-3.5" }),
7609
+ mobile: /* @__PURE__ */ jsx23(Smartphone, { className: "w-3.5 h-3.5" })
7378
7610
  };
7379
7611
  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",
7612
+ return /* @__PURE__ */ jsxs22(
7613
+ "div",
7614
+ {
7615
+ className: `flex flex-col h-full bg-slate-100 text-slate-900 relative overflow-hidden ${className || ""}`,
7616
+ children: [
7617
+ navigatorMode === "floating" && /* @__PURE__ */ jsx23(LayersPanel, { registry }),
7618
+ activeTable && tableSpreadsheetMode === "floating" && /* @__PURE__ */ jsx23(
7619
+ TableSpreadsheetEditor,
7416
7620
  {
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,
7621
+ registry,
7622
+ tableNode: activeTable,
7623
+ mode: "floating",
7624
+ onToggleMode: () => setTableSpreadsheetMode("docked"),
7625
+ onClose: () => setTableSpreadsheetMode("hidden")
7626
+ }
7627
+ ),
7628
+ isMobileSidebarOpen && resolvedConfig.sidebar.enabled && /* @__PURE__ */ jsxs22("div", { className: "fixed inset-0 z-50 flex lg:hidden animate-in fade-in duration-200", children: [
7629
+ /* @__PURE__ */ jsx23(
7630
+ "div",
7631
+ {
7632
+ className: "fixed inset-0 bg-slate-950/50 backdrop-blur-xs transition-opacity",
7633
+ onClick: () => setIsMobileSidebarOpen(false)
7634
+ }
7635
+ ),
7636
+ /* @__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: [
7637
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center justify-between px-3.5 py-2.5 border-b border-slate-200 bg-slate-50", children: [
7638
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
7639
+ /* @__PURE__ */ jsx23(Boxes2, { className: "w-4 h-4 text-blue-600" }),
7640
+ /* @__PURE__ */ jsx23("span", { className: "font-bold text-xs text-slate-800", children: "Add Components & Blocks" })
7641
+ ] }),
7642
+ /* @__PURE__ */ jsx23(
7643
+ "button",
7644
+ {
7645
+ type: "button",
7646
+ onClick: () => setIsMobileSidebarOpen(false),
7647
+ className: "p-1 rounded-md text-slate-400 hover:text-slate-700 hover:bg-slate-200 transition",
7648
+ children: /* @__PURE__ */ jsx23(X5, { className: "w-4 h-4" })
7649
+ }
7650
+ )
7651
+ ] }),
7652
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 overflow-hidden min-h-0", children: /* @__PURE__ */ jsx23(LeftSidebar, { registry, config: resolvedConfig.sidebar }) })
7653
+ ] })
7654
+ ] }),
7655
+ 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: [
7656
+ /* @__PURE__ */ jsx23(
7657
+ "div",
7658
+ {
7659
+ className: "fixed inset-0 bg-slate-950/50 backdrop-blur-xs transition-opacity",
7660
+ onClick: () => setIsMobileInspectorOpen(false)
7661
+ }
7662
+ ),
7663
+ /* @__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: [
7664
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center justify-between px-3.5 py-2.5 border-b border-slate-200 bg-slate-50", children: [
7665
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
7666
+ /* @__PURE__ */ jsx23(Sliders, { className: "w-4 h-4 text-blue-600" }),
7667
+ /* @__PURE__ */ jsx23("span", { className: "font-bold text-xs text-slate-800", children: selectedNodeId ? `Inspector (#${selectedNodeId})` : "Inspector & Properties" })
7668
+ ] }),
7669
+ /* @__PURE__ */ jsx23(
7670
+ "button",
7671
+ {
7672
+ type: "button",
7673
+ onClick: () => setIsMobileInspectorOpen(false),
7674
+ className: "p-1 rounded-md text-slate-400 hover:text-slate-700 hover:bg-slate-200 transition",
7675
+ children: /* @__PURE__ */ jsx23(X5, { className: "w-4 h-4" })
7676
+ }
7677
+ )
7678
+ ] }),
7679
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 overflow-hidden min-h-0", children: /* @__PURE__ */ jsx23(InspectorPanel, { registry, config: resolvedConfig.inspector }) })
7680
+ ] })
7681
+ ] }),
7682
+ isMobileLayersOpen && /* @__PURE__ */ jsxs22("div", { className: "fixed inset-0 z-50 flex lg:hidden animate-in fade-in duration-200", children: [
7683
+ /* @__PURE__ */ jsx23(
7684
+ "div",
7685
+ {
7686
+ className: "fixed inset-0 bg-slate-950/50 backdrop-blur-xs transition-opacity",
7687
+ onClick: () => setIsMobileLayersOpen(false)
7688
+ }
7689
+ ),
7690
+ /* @__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: [
7691
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center justify-between px-3.5 py-2.5 border-b border-slate-200 bg-slate-50", children: [
7692
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
7693
+ /* @__PURE__ */ jsx23(Layers2, { className: "w-4 h-4 text-blue-600" }),
7694
+ /* @__PURE__ */ jsx23("span", { className: "font-bold text-xs text-slate-800", children: "Element Tree / Layers" })
7695
+ ] }),
7696
+ /* @__PURE__ */ jsx23(
7697
+ "button",
7698
+ {
7699
+ type: "button",
7700
+ onClick: () => setIsMobileLayersOpen(false),
7701
+ className: "p-1 rounded-md text-slate-400 hover:text-slate-700 hover:bg-slate-200 transition",
7702
+ children: /* @__PURE__ */ jsx23(X5, { className: "w-4 h-4" })
7703
+ }
7704
+ )
7705
+ ] }),
7706
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 overflow-hidden min-h-0", children: /* @__PURE__ */ jsx23(LayersPanel, { registry }) })
7707
+ ] })
7708
+ ] }),
7709
+ 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: [
7710
+ resolvedConfig.toolbar.showTitle ? /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2 shrink-0", children: [
7711
+ /* @__PURE__ */ jsx23("span", { className: "font-bold text-slate-800 text-xs sm:text-sm tracking-tight", children: "KUBUILD Editor" }),
7712
+ /* @__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" })
7713
+ ] }) : /* @__PURE__ */ jsx23("div", {}),
7714
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2 shrink-0", children: [
7715
+ /* @__PURE__ */ jsx23(EditorToolbar, { registry, config: resolvedConfig.toolbar }),
7716
+ 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(
7717
+ "button",
7718
+ {
7719
+ type: "button",
7720
+ onClick: () => setViewport(vp),
7721
+ title: `Switch to ${vp} preview`,
7722
+ 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"}`,
7723
+ children: [
7724
+ viewportIcons[vp],
7725
+ /* @__PURE__ */ jsx23("span", { className: "hidden md:inline", children: vp })
7726
+ ]
7727
+ },
7728
+ vp
7729
+ )) })
7730
+ ] }),
7731
+ 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" })
7732
+ ] }),
7733
+ /* @__PURE__ */ jsxs22("div", { className: "flex flex-1 overflow-hidden min-h-0 relative", children: [
7734
+ 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 }) }),
7735
+ 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 }) }),
7736
+ /* @__PURE__ */ jsxs22("div", { className: "flex-1 overflow-hidden flex flex-col min-h-0 h-full bg-slate-100/90 relative", children: [
7737
+ /* @__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(
7738
+ "div",
7739
+ {
7740
+ className: `${viewportWidthMap[viewport]} bg-white shadow-md rounded-lg overflow-hidden transition-all duration-200 min-h-[500px] border border-slate-200`,
7741
+ children: /* @__PURE__ */ jsx23(
7742
+ EditorCanvas,
7743
+ {
7744
+ registry,
7745
+ context: previewContext,
7746
+ viewport,
7747
+ onDiagnostic,
7748
+ config: resolvedConfig.canvas
7749
+ }
7750
+ )
7751
+ }
7752
+ ) }),
7753
+ 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(
7754
+ "button",
7755
+ {
7756
+ type: "button",
7757
+ onClick: () => setIsMobileInspectorOpen(true),
7758
+ 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",
7759
+ children: [
7760
+ /* @__PURE__ */ jsx23(Sliders, { className: "w-3.5 h-3.5" }),
7761
+ /* @__PURE__ */ jsxs22("span", { children: [
7762
+ "Edit Element (#",
7763
+ selectedNodeId,
7764
+ ")"
7765
+ ] })
7766
+ ]
7767
+ }
7768
+ ) }),
7769
+ resolvedConfig.canvas.showBreadcrumbs && /* @__PURE__ */ jsx23(HierarchyBreadcrumbs, { registry })
7770
+ ] }),
7771
+ 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 }) })
7772
+ ] }),
7773
+ /* @__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: [
7774
+ resolvedConfig.sidebar.enabled && /* @__PURE__ */ jsxs22(
7775
+ "button",
7776
+ {
7777
+ type: "button",
7778
+ onClick: () => setIsMobileSidebarOpen(true),
7779
+ 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",
7780
+ children: [
7781
+ /* @__PURE__ */ jsx23(Plus, { className: "w-4 h-4 text-blue-600" }),
7782
+ /* @__PURE__ */ jsx23("span", { className: "text-[10px] font-medium", children: "Add" })
7783
+ ]
7784
+ }
7785
+ ),
7786
+ /* @__PURE__ */ jsxs22(
7787
+ "button",
7788
+ {
7789
+ type: "button",
7790
+ onClick: () => setIsMobileLayersOpen(true),
7791
+ 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",
7792
+ children: [
7793
+ /* @__PURE__ */ jsx23(Layers2, { className: "w-4 h-4" }),
7794
+ /* @__PURE__ */ jsx23("span", { className: "text-[10px] font-medium", children: "Layers" })
7795
+ ]
7796
+ }
7797
+ ),
7798
+ resolvedConfig.inspector.enabled && /* @__PURE__ */ jsxs22(
7799
+ "button",
7800
+ {
7801
+ type: "button",
7802
+ onClick: () => setIsMobileInspectorOpen(true),
7803
+ 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"}`,
7804
+ children: [
7805
+ /* @__PURE__ */ jsxs22("div", { className: "relative", children: [
7806
+ /* @__PURE__ */ jsx23(Sliders, { className: "w-4 h-4" }),
7807
+ selectedNodeId && /* @__PURE__ */ jsx23("span", { className: "absolute -top-1 -right-1 w-2 h-2 rounded-full bg-blue-600 ring-2 ring-white" })
7808
+ ] }),
7809
+ /* @__PURE__ */ jsx23("span", { className: "text-[10px]", children: selectedNodeId ? "Inspect *" : "Inspect" })
7810
+ ]
7811
+ }
7812
+ ),
7813
+ /* @__PURE__ */ jsx23("div", { className: "h-5 w-px bg-slate-200 mx-0.5" }),
7814
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-1", children: [
7815
+ /* @__PURE__ */ jsx23(
7816
+ "button",
7817
+ {
7818
+ type: "button",
7819
+ disabled: !canUndo,
7820
+ onClick: undo,
7821
+ title: "Undo",
7822
+ className: "p-2 rounded text-slate-600 hover:text-blue-600 disabled:opacity-30 disabled:hover:text-slate-600 transition",
7823
+ children: /* @__PURE__ */ jsx23(Undo22, { className: "w-4 h-4" })
7824
+ }
7825
+ ),
7826
+ /* @__PURE__ */ jsx23(
7827
+ "button",
7420
7828
  {
7421
- registry,
7422
- context: previewContext,
7423
- viewport,
7424
- onDiagnostic,
7425
- config: resolvedConfig.canvas
7829
+ type: "button",
7830
+ disabled: !canRedo,
7831
+ onClick: redo,
7832
+ title: "Redo",
7833
+ className: "p-2 rounded text-slate-600 hover:text-blue-600 disabled:opacity-30 disabled:hover:text-slate-600 transition",
7834
+ children: /* @__PURE__ */ jsx23(Redo22, { className: "w-4 h-4" })
7426
7835
  }
7427
7836
  )
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
- ] });
7837
+ ] })
7838
+ ] })
7839
+ ]
7840
+ }
7841
+ );
7435
7842
  };
7436
7843
  export {
7437
7844
  ANIMATION_TYPES,