@kubuild/editor 0.3.0 → 0.3.1

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.cjs CHANGED
@@ -543,19 +543,55 @@ var useEditorStore = (0, import_zustand.create)((set, get) => ({
543
543
  setAiGenerationStatus: (status) => set({ aiGenerationStatus: status }),
544
544
  insertComponent: (type, registry, parentId, index) => {
545
545
  const state = get();
546
- const targetParentId = parentId ?? state.selectedNodeId ?? state.document.document.id;
547
- const parentNode = (0, import_core.findNodeById)(state.document.document, targetParentId);
546
+ let targetParentId = parentId ?? state.selectedNodeId ?? state.document.document.id;
547
+ let targetIndex = index;
548
+ let parentNode = (0, import_core.findNodeById)(state.document.document, targetParentId);
548
549
  if (!parentNode) {
549
- return {
550
- success: false,
551
- error: `Insertion target "${targetParentId}" was not found in the document.`
552
- };
550
+ targetParentId = state.document.document.id;
551
+ parentNode = state.document.document;
553
552
  }
554
553
  const definition = registry.get(type);
555
554
  if (!definition) {
556
555
  return { success: false, error: `Unknown component type "${type}".` };
557
556
  }
558
- const policy = registry.canInsertChild(parentNode.type, type);
557
+ let policy = registry.canInsertChild(parentNode.type, type);
558
+ if (!policy.valid && !parentId) {
559
+ const loc = (0, import_core.findNodeLocation)(state.document.document, targetParentId);
560
+ if (loc && loc.parent) {
561
+ const parentPolicy = registry.canInsertChild(loc.parent.type, type);
562
+ if (parentPolicy.valid) {
563
+ targetParentId = loc.parent.id;
564
+ targetIndex = loc.index + 1;
565
+ parentNode = loc.parent;
566
+ policy = parentPolicy;
567
+ }
568
+ }
569
+ if (!policy.valid && parentNode.type === "page" && type !== "section") {
570
+ const sections = parentNode.children?.filter((c) => c.type === "section") ?? [];
571
+ const lastSection = sections[sections.length - 1];
572
+ if (lastSection) {
573
+ const sectionPolicy = registry.canInsertChild(lastSection.type, type);
574
+ if (sectionPolicy.valid) {
575
+ targetParentId = lastSection.id;
576
+ targetIndex = lastSection.children?.length ?? 0;
577
+ parentNode = lastSection;
578
+ policy = sectionPolicy;
579
+ } else {
580
+ const containers = lastSection.children?.filter((c) => c.type === "container") ?? [];
581
+ const lastContainer = containers[containers.length - 1];
582
+ if (lastContainer) {
583
+ const containerPolicy = registry.canInsertChild(lastContainer.type, type);
584
+ if (containerPolicy.valid) {
585
+ targetParentId = lastContainer.id;
586
+ targetIndex = lastContainer.children?.length ?? 0;
587
+ parentNode = lastContainer;
588
+ policy = containerPolicy;
589
+ }
590
+ }
591
+ }
592
+ }
593
+ }
594
+ }
559
595
  if (!policy.valid) {
560
596
  return { success: false, error: policy.errors.join(" ") };
561
597
  }
@@ -583,7 +619,7 @@ var useEditorStore = (0, import_zustand.create)((set, get) => ({
583
619
  ...definition.defaultStyles ? { styles: (0, import_core.deepClone)(definition.defaultStyles) } : {},
584
620
  ...children ? { children } : {}
585
621
  };
586
- get().dispatch((doc) => (0, import_core.insertNode)(doc, { parentId: targetParentId, node, index }));
622
+ get().dispatch((doc) => (0, import_core.insertNode)(doc, { parentId: targetParentId, node, index: targetIndex }));
587
623
  if (!state.activeArtboardId && OVERLAY_COMPONENT_TYPES.includes(type)) {
588
624
  const detached = get().detachNodeToArtboard(nodeId, { name: definition.label });
589
625
  if (detached.success && detached.triggerId && detached.stubNodeId) {
@@ -685,14 +721,36 @@ var useEditorStore = (0, import_zustand.create)((set, get) => ({
685
721
  error: "Cannot move a node into itself or one of its own descendants."
686
722
  };
687
723
  }
688
- const policy = registry.canInsertChild(targetParent.type, sourceLocation.node.type);
689
- if (!policy.valid) {
690
- return { success: false, error: policy.errors.join(" ") };
724
+ if (registry) {
725
+ const policy = registry.canInsertChild(targetParent.type, sourceLocation.node.type);
726
+ if (!policy.valid) {
727
+ return { success: false, error: policy.errors.join(" ") };
728
+ }
691
729
  }
692
730
  const adjustedIndex = sourceLocation.parent.id === targetParentId && typeof index === "number" && index > sourceLocation.index ? index - 1 : index;
693
731
  get().dispatch((doc) => (0, import_core.moveNode)(doc, { nodeId, targetParentId, index: adjustedIndex }));
694
732
  return { success: true };
695
733
  },
734
+ moveComponentUp: (nodeId, registry) => {
735
+ const state = get();
736
+ const loc = (0, import_core.findNodeLocation)(state.document.document, nodeId);
737
+ if (!loc || !loc.parent || loc.index <= 0) {
738
+ return { success: false, error: "Cannot move up: already at the top." };
739
+ }
740
+ return get().moveComponent(nodeId, loc.parent.id, registry, loc.index - 1);
741
+ },
742
+ moveComponentDown: (nodeId, registry) => {
743
+ const state = get();
744
+ const loc = (0, import_core.findNodeLocation)(state.document.document, nodeId);
745
+ if (!loc || !loc.parent) {
746
+ return { success: false, error: "Node parent not found." };
747
+ }
748
+ const siblingCount = loc.parent.children?.length ?? 1;
749
+ if (loc.index >= siblingCount - 1) {
750
+ return { success: false, error: "Cannot move down: already at the bottom." };
751
+ }
752
+ return get().moveComponent(nodeId, loc.parent.id, registry, loc.index + 2);
753
+ },
696
754
  duplicateComponent: (nodeId, _registry) => {
697
755
  const state = get();
698
756
  if (nodeId === state.document.document.id) {
@@ -4637,6 +4695,8 @@ var FloatingActionBadges = ({
4637
4695
  selectNode,
4638
4696
  duplicateComponent,
4639
4697
  deleteComponent,
4698
+ moveComponentUp,
4699
+ moveComponentDown,
4640
4700
  detachNodeToArtboard,
4641
4701
  activateArtboard,
4642
4702
  activeArtboardId
@@ -4694,6 +4754,30 @@ var FloatingActionBadges = ({
4694
4754
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react6.Move, { className: "w-3.5 h-3.5", "aria-hidden": "true" })
4695
4755
  }
4696
4756
  ),
4757
+ !isRoot && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4758
+ "button",
4759
+ {
4760
+ type: "button",
4761
+ "data-testid": "floating-badge-move-up",
4762
+ title: "Move Up",
4763
+ "aria-label": "Move Up",
4764
+ onClick: () => moveComponentUp(node.id, registry),
4765
+ className: "px-1.5 py-1 hover:bg-blue-500 active:bg-blue-700 transition flex items-center justify-center border-l border-blue-500/40 text-[11px]",
4766
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react6.ChevronUp, { className: "w-3.5 h-3.5", "aria-hidden": "true" })
4767
+ }
4768
+ ),
4769
+ !isRoot && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4770
+ "button",
4771
+ {
4772
+ type: "button",
4773
+ "data-testid": "floating-badge-move-down",
4774
+ title: "Move Down",
4775
+ "aria-label": "Move Down",
4776
+ onClick: () => moveComponentDown(node.id, registry),
4777
+ className: "px-1.5 py-1 hover:bg-blue-500 active:bg-blue-700 transition flex items-center justify-center border-l border-blue-500/40 text-[11px]",
4778
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react6.ChevronDown, { className: "w-3.5 h-3.5", "aria-hidden": "true" })
4779
+ }
4780
+ ),
4697
4781
  !isRoot && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4698
4782
  "button",
4699
4783
  {
@@ -5244,6 +5328,7 @@ var ResizeHandles = ({
5244
5328
  borderRadius: "1px",
5245
5329
  boxShadow: "0 1px 2px rgba(0, 0, 0, 0.15)",
5246
5330
  pointerEvents: "auto",
5331
+ touchAction: "none",
5247
5332
  cursor,
5248
5333
  zIndex: 51,
5249
5334
  boxSizing: "border-box",
@@ -6234,6 +6319,7 @@ function useCanvasPanZoom({
6234
6319
  const isSpacePressedRef = (0, import_react11.useRef)(isSpacePressed);
6235
6320
  isSpacePressedRef.current = isSpacePressed;
6236
6321
  const dragStartRef = (0, import_react11.useRef)(null);
6322
+ const activePointerIdRef = (0, import_react11.useRef)(null);
6237
6323
  (0, import_react11.useEffect)(() => {
6238
6324
  if (!enabled) return;
6239
6325
  const onKeyDown = (e) => {
@@ -6293,16 +6379,94 @@ function useCanvasPanZoom({
6293
6379
  container.addEventListener("wheel", onWheel, { passive: false });
6294
6380
  return () => container.removeEventListener("wheel", onWheel);
6295
6381
  }, [containerRef, enabled]);
6382
+ const rafPanRef = (0, import_react11.useRef)(null);
6383
+ const pendingPanRef = (0, import_react11.useRef)(null);
6384
+ (0, import_react11.useEffect)(() => {
6385
+ const container = containerRef.current;
6386
+ if (!container || !enabled) return;
6387
+ let touchStartDistance = 0;
6388
+ let touchStartZoom = 1;
6389
+ let touchStartPan = { x: 0, y: 0 };
6390
+ let touchStartMid = { x: 0, y: 0 };
6391
+ let isPinching = false;
6392
+ const onTouchStart = (e) => {
6393
+ if (e.touches.length === 2) {
6394
+ e.preventDefault();
6395
+ activePointerIdRef.current = null;
6396
+ dragStartRef.current = null;
6397
+ pendingPanRef.current = null;
6398
+ if (rafPanRef.current !== null) {
6399
+ cancelAnimationFrame(rafPanRef.current);
6400
+ rafPanRef.current = null;
6401
+ }
6402
+ const t1 = e.touches[0];
6403
+ const t2 = e.touches[1];
6404
+ touchStartDistance = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY);
6405
+ touchStartZoom = zoomRef.current;
6406
+ touchStartPan = { ...panRef.current };
6407
+ touchStartMid = {
6408
+ x: (t1.clientX + t2.clientX) / 2,
6409
+ y: (t1.clientY + t2.clientY) / 2
6410
+ };
6411
+ isPinching = true;
6412
+ setIsPanning(true);
6413
+ }
6414
+ };
6415
+ const onTouchMove = (e) => {
6416
+ if (e.touches.length === 2 && isPinching && touchStartDistance > 0) {
6417
+ e.preventDefault();
6418
+ const t1 = e.touches[0];
6419
+ const t2 = e.touches[1];
6420
+ const currentDistance = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY);
6421
+ const scale = currentDistance / touchStartDistance;
6422
+ const nextZoom = clampZoom(touchStartZoom * scale);
6423
+ const currentMidX = (t1.clientX + t2.clientX) / 2;
6424
+ const currentMidY = (t1.clientY + t2.clientY) / 2;
6425
+ const rect = container.getBoundingClientRect();
6426
+ const midContainerX = touchStartMid.x - rect.left;
6427
+ const midContainerY = touchStartMid.y - rect.top;
6428
+ const newPanX = midContainerX - (midContainerX - touchStartPan.x) * (nextZoom / touchStartZoom) + (currentMidX - touchStartMid.x);
6429
+ const newPanY = midContainerY - (midContainerY - touchStartPan.y) * (nextZoom / touchStartZoom) + (currentMidY - touchStartMid.y);
6430
+ if (rafPanRef.current === null) {
6431
+ rafPanRef.current = requestAnimationFrame(() => {
6432
+ setZoom(nextZoom);
6433
+ setPan({ x: Math.round(newPanX), y: Math.round(newPanY) });
6434
+ rafPanRef.current = null;
6435
+ });
6436
+ }
6437
+ }
6438
+ };
6439
+ const onTouchEnd = (e) => {
6440
+ if (isPinching && e.touches.length < 2) {
6441
+ isPinching = false;
6442
+ touchStartDistance = 0;
6443
+ setIsPanning(false);
6444
+ }
6445
+ };
6446
+ container.addEventListener("touchstart", onTouchStart, { passive: false });
6447
+ container.addEventListener("touchmove", onTouchMove, { passive: false });
6448
+ container.addEventListener("touchend", onTouchEnd, { passive: false });
6449
+ container.addEventListener("touchcancel", onTouchEnd, { passive: false });
6450
+ return () => {
6451
+ container.removeEventListener("touchstart", onTouchStart);
6452
+ container.removeEventListener("touchmove", onTouchMove);
6453
+ container.removeEventListener("touchend", onTouchEnd);
6454
+ container.removeEventListener("touchcancel", onTouchEnd);
6455
+ };
6456
+ }, [containerRef, enabled]);
6296
6457
  const handlePointerDown = (0, import_react11.useCallback)(
6297
- (e) => {
6458
+ (e, forcePan = false) => {
6298
6459
  if (!enabled) return;
6460
+ const isTouch = e.pointerType === "touch" || e.pointerType === "pen";
6461
+ if (isTouch && e.isPrimary === false) return;
6299
6462
  const isMiddleClick = e.button === 1;
6300
6463
  const isSpacePan = isSpacePressedRef.current && e.button === 0;
6301
- const isHandMode = toolModeRef.current === "hand" && e.button === 0;
6302
- if (isMiddleClick || isSpacePan || isHandMode) {
6464
+ const isHandMode = toolModeRef.current === "hand" && (e.button === 0 || isTouch);
6465
+ if (forcePan || isMiddleClick || isSpacePan || isHandMode) {
6303
6466
  e.preventDefault();
6304
6467
  e.stopPropagation();
6305
6468
  setIsPanning(true);
6469
+ activePointerIdRef.current = e.pointerId;
6306
6470
  dragStartRef.current = {
6307
6471
  startX: e.clientX,
6308
6472
  startY: e.clientY,
@@ -6316,14 +6480,33 @@ function useCanvasPanZoom({
6316
6480
  const handlePointerMove = (0, import_react11.useCallback)((e) => {
6317
6481
  const drag = dragStartRef.current;
6318
6482
  if (!drag) return;
6483
+ if (activePointerIdRef.current !== null && e.pointerId !== activePointerIdRef.current) return;
6319
6484
  const deltaX = e.clientX - drag.startX;
6320
6485
  const deltaY = e.clientY - drag.startY;
6321
- setPan({
6486
+ pendingPanRef.current = {
6322
6487
  x: Math.round(drag.initialPanX + deltaX),
6323
6488
  y: Math.round(drag.initialPanY + deltaY)
6324
- });
6489
+ };
6490
+ if (rafPanRef.current === null) {
6491
+ rafPanRef.current = requestAnimationFrame(() => {
6492
+ if (pendingPanRef.current) {
6493
+ setPan(pendingPanRef.current);
6494
+ }
6495
+ rafPanRef.current = null;
6496
+ });
6497
+ }
6325
6498
  }, []);
6326
- const handlePointerUp = (0, import_react11.useCallback)(() => {
6499
+ const handlePointerUp = (0, import_react11.useCallback)((e) => {
6500
+ if (activePointerIdRef.current !== null && e.pointerId !== activePointerIdRef.current) return;
6501
+ if (rafPanRef.current !== null) {
6502
+ cancelAnimationFrame(rafPanRef.current);
6503
+ rafPanRef.current = null;
6504
+ }
6505
+ if (pendingPanRef.current) {
6506
+ setPan(pendingPanRef.current);
6507
+ pendingPanRef.current = null;
6508
+ }
6509
+ activePointerIdRef.current = null;
6327
6510
  if (dragStartRef.current) {
6328
6511
  dragStartRef.current = null;
6329
6512
  setIsPanning(false);
@@ -6336,6 +6519,10 @@ function useCanvasPanZoom({
6336
6519
  return () => {
6337
6520
  window.removeEventListener("pointermove", handlePointerMove);
6338
6521
  window.removeEventListener("pointerup", handlePointerUp);
6522
+ if (rafPanRef.current !== null) {
6523
+ cancelAnimationFrame(rafPanRef.current);
6524
+ rafPanRef.current = null;
6525
+ }
6339
6526
  };
6340
6527
  }
6341
6528
  }, [isPanning, handlePointerMove, handlePointerUp]);
@@ -6631,6 +6818,7 @@ var MultiDevicePreview = ({
6631
6818
  context,
6632
6819
  viewport: device.id,
6633
6820
  mode: previewMode ? "runtime" : "editor",
6821
+ selectedNodeId,
6634
6822
  onNodeClick: (id) => {
6635
6823
  selectNode(id);
6636
6824
  setViewport(device.id);
@@ -7323,7 +7511,7 @@ var ViewportResizer = ({
7323
7511
  showPresets && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
7324
7512
  "div",
7325
7513
  {
7326
- className: "flex items-center justify-between gap-2 w-full px-1 py-1 mb-2 select-none text-xs",
7514
+ className: "flex flex-wrap items-center justify-between gap-x-2 gap-y-1 w-full px-1 py-1 mb-2 select-none text-xs",
7327
7515
  onPointerDown: onHeaderPointerDown,
7328
7516
  children: [
7329
7517
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2 min-w-0 cursor-grab active:cursor-grabbing", children: [
@@ -7523,42 +7711,34 @@ var EditorCanvas = ({
7523
7711
  activeArtboardId: propActiveArtboardId,
7524
7712
  className
7525
7713
  }) => {
7526
- const {
7527
- document: storeDoc,
7528
- selectedNodeId,
7529
- selectedNodeIds,
7530
- hoveredNodeId,
7531
- dragPayload,
7532
- selectNode,
7533
- selectMultipleNodes,
7534
- toggleNodeSelection,
7535
- wrapSelectedIntoFrame,
7536
- ungroupSelectedFrame,
7537
- hoverNode,
7538
- updateNodeProps,
7539
- setDragPayload,
7540
- insertComponent,
7541
- insertBlock,
7542
- moveComponent,
7543
- deleteComponent,
7544
- duplicateComponent,
7545
- copyNode,
7546
- pasteNode,
7547
- undo,
7548
- redo,
7549
- previewMode,
7550
- multiDeviceMode,
7551
- toggleMultiDeviceMode,
7552
- addActionLog,
7553
- setLiveFormState,
7554
- aiGenerationStatus: storeAiGenerationStatus,
7555
- componentArtboards: storeComponentArtboards,
7556
- activeArtboardId: storeActiveArtboardId,
7557
- activateArtboard,
7558
- removeComponentArtboard,
7559
- setComponentArtboardPosition,
7560
- setComponentArtboardWidth
7561
- } = useEditorStore();
7714
+ const storeDoc = useEditorStore((s) => s.document);
7715
+ const selectedNodeId = useEditorStore((s) => s.selectedNodeId);
7716
+ const selectedNodeIds = useEditorStore((s) => s.selectedNodeIds);
7717
+ const hoveredNodeId = useEditorStore((s) => s.hoveredNodeId);
7718
+ const dragPayload = useEditorStore((s) => s.dragPayload);
7719
+ const selectNode = useEditorStore((s) => s.selectNode);
7720
+ const selectMultipleNodes = useEditorStore((s) => s.selectMultipleNodes);
7721
+ const toggleNodeSelection = useEditorStore((s) => s.toggleNodeSelection);
7722
+ const hoverNode = useEditorStore((s) => s.hoverNode);
7723
+ const updateNodeProps = useEditorStore((s) => s.updateNodeProps);
7724
+ const setDragPayload = useEditorStore((s) => s.setDragPayload);
7725
+ const insertComponent = useEditorStore((s) => s.insertComponent);
7726
+ const insertBlock = useEditorStore((s) => s.insertBlock);
7727
+ const moveComponent = useEditorStore((s) => s.moveComponent);
7728
+ const deleteComponent = useEditorStore((s) => s.deleteComponent);
7729
+ const duplicateComponent = useEditorStore((s) => s.duplicateComponent);
7730
+ const previewMode = useEditorStore((s) => s.previewMode);
7731
+ const multiDeviceMode = useEditorStore((s) => s.multiDeviceMode);
7732
+ const toggleMultiDeviceMode = useEditorStore((s) => s.toggleMultiDeviceMode);
7733
+ const addActionLog = useEditorStore((s) => s.addActionLog);
7734
+ const setLiveFormState = useEditorStore((s) => s.setLiveFormState);
7735
+ const storeAiGenerationStatus = useEditorStore((s) => s.aiGenerationStatus);
7736
+ const storeComponentArtboards = useEditorStore((s) => s.componentArtboards);
7737
+ const storeActiveArtboardId = useEditorStore((s) => s.activeArtboardId);
7738
+ const activateArtboard = useEditorStore((s) => s.activateArtboard);
7739
+ const removeComponentArtboard = useEditorStore((s) => s.removeComponentArtboard);
7740
+ const setComponentArtboardPosition = useEditorStore((s) => s.setComponentArtboardPosition);
7741
+ const setComponentArtboardWidth = useEditorStore((s) => s.setComponentArtboardWidth);
7562
7742
  const document2 = propDoc ?? storeDoc;
7563
7743
  const aiGenerationStatus = propAiGenerationStatus ?? storeAiGenerationStatus;
7564
7744
  const componentArtboards = propComponentArtboards ?? storeComponentArtboards;
@@ -7819,6 +7999,14 @@ var EditorCanvas = ({
7819
7999
  const [draggingArtboardId, setDraggingArtboardId] = (0, import_react16.useState)(null);
7820
8000
  const [dragPosition, setDragPosition] = (0, import_react16.useState)(null);
7821
8001
  const marqueeDragRef = (0, import_react16.useRef)(null);
8002
+ const isTouchDevice = (0, import_react16.useMemo)(() => {
8003
+ if (typeof window === "undefined") return false;
8004
+ return window.matchMedia && window.matchMedia("(pointer: coarse)").matches || "ontouchstart" in window;
8005
+ }, []);
8006
+ const isSmallScreen = (0, import_react16.useMemo)(() => {
8007
+ if (typeof window === "undefined") return false;
8008
+ return window.innerWidth < 768;
8009
+ }, []);
7822
8010
  const {
7823
8011
  pan,
7824
8012
  setPan,
@@ -7913,14 +8101,23 @@ var EditorCanvas = ({
7913
8101
  setSelectedRects(multi);
7914
8102
  };
7915
8103
  recompute();
8104
+ let rafId = null;
8105
+ const throttledRecompute = () => {
8106
+ if (rafId !== null) return;
8107
+ rafId = requestAnimationFrame(() => {
8108
+ recompute();
8109
+ rafId = null;
8110
+ });
8111
+ };
7916
8112
  const container = containerRef.current;
7917
- window.addEventListener("resize", recompute);
7918
- window.addEventListener("scroll", recompute, true);
7919
- container?.addEventListener("input", recompute);
8113
+ window.addEventListener("resize", throttledRecompute);
8114
+ window.addEventListener("scroll", throttledRecompute, { passive: true, capture: true });
8115
+ container?.addEventListener("input", throttledRecompute);
7920
8116
  return () => {
7921
- window.removeEventListener("resize", recompute);
7922
- window.removeEventListener("scroll", recompute, true);
7923
- container?.removeEventListener("input", recompute);
8117
+ if (rafId !== null) cancelAnimationFrame(rafId);
8118
+ window.removeEventListener("resize", throttledRecompute);
8119
+ window.removeEventListener("scroll", throttledRecompute, true);
8120
+ container?.removeEventListener("input", throttledRecompute);
7924
8121
  };
7925
8122
  }, [activeDoc, selectedNodeId, selectedNodeIds, hoveredNodeId, viewport, zoom, pan, fluidWidth, effectiveActivePageId]);
7926
8123
  (0, import_react16.useEffect)(() => {
@@ -8015,6 +8212,7 @@ var EditorCanvas = ({
8015
8212
  };
8016
8213
  }, [registry]);
8017
8214
  const candidateRects = (0, import_react16.useMemo)(() => {
8215
+ if (isTouchDevice && isSmallScreen) return [];
8018
8216
  const layer = layerRef.current;
8019
8217
  if (!layer || !selectedNodeId) return [];
8020
8218
  const elements = layer.querySelectorAll("[data-kubuild-node]");
@@ -8034,12 +8232,16 @@ var EditorCanvas = ({
8034
8232
  }
8035
8233
  });
8036
8234
  return results;
8037
- }, [document2, selectedNodeId, zoom]);
8235
+ }, [document2, selectedNodeId, zoom, isTouchDevice, isSmallScreen]);
8038
8236
  const handleMouseOver = (e) => {
8237
+ if (isTouchDevice) return;
8039
8238
  const el = e.target.closest("[data-kubuild-node]");
8040
8239
  if (el) hoverNode(el.getAttribute("data-kubuild-node"));
8041
8240
  };
8042
- const handleMouseLeave = () => hoverNode(null);
8241
+ const handleMouseLeave = () => {
8242
+ if (isTouchDevice) return;
8243
+ hoverNode(null);
8244
+ };
8043
8245
  const handleCanvasPointerDown = (e) => {
8044
8246
  if (e.button === 1 || isSpacePressed || toolMode === "hand") {
8045
8247
  handlePanPointerDown(e);
@@ -8051,7 +8253,14 @@ var EditorCanvas = ({
8051
8253
  const isRootOrEmpty = !clickedNode || clickedNode.getAttribute("data-kubuild-node") === document2.document.id;
8052
8254
  const isDirectCanvasBg = target === containerRef.current || target === layerRef.current || target.getAttribute("data-testid") === "canvas-viewport-container" || target.getAttribute("data-testid") === "canvas-transform-layer";
8053
8255
  if (isDirectCanvasBg && !e.shiftKey) {
8054
- handlePanPointerDown(e);
8256
+ handlePanPointerDown(e, true);
8257
+ return;
8258
+ }
8259
+ if (e.pointerType === "touch" || e.pointerType === "pen") {
8260
+ if (isRootOrEmpty) {
8261
+ selectNode(null);
8262
+ handlePanPointerDown(e, true);
8263
+ }
8055
8264
  return;
8056
8265
  }
8057
8266
  if (isRootOrEmpty) {
@@ -8400,9 +8609,12 @@ var EditorCanvas = ({
8400
8609
  overflow: "hidden",
8401
8610
  cursor: cursorStyle,
8402
8611
  backgroundColor: "#f1f5f9",
8403
- backgroundImage: `radial-gradient(circle, #cbd5e1 ${Math.max(0.75, Math.min(2.5, 1.2 * zoom))}px, transparent ${Math.max(0.75, Math.min(2.5, 1.2 * zoom))}px)`,
8404
- backgroundSize: `${24 * zoom}px ${24 * zoom}px`,
8405
- backgroundPosition: `${pan.x}px ${pan.y}px`
8612
+ // App owns single-finger pan and two-finger pinch-zoom on this container itself,
8613
+ // so native browser pan/zoom must stay fully off to avoid the two fighting.
8614
+ touchAction: "none",
8615
+ backgroundImage: isSmallScreen && isTouchDevice ? void 0 : `radial-gradient(circle, #cbd5e1 ${Math.max(0.75, Math.min(2.5, 1.2 * zoom))}px, transparent ${Math.max(0.75, Math.min(2.5, 1.2 * zoom))}px)`,
8616
+ backgroundSize: isSmallScreen && isTouchDevice ? void 0 : `${24 * zoom}px ${24 * zoom}px`,
8617
+ backgroundPosition: isSmallScreen && isTouchDevice ? void 0 : `${pan.x}px ${pan.y}px`
8406
8618
  },
8407
8619
  onPointerDown: handleCanvasPointerDown,
8408
8620
  onPointerMove: handleCanvasPointerMove,
@@ -8481,6 +8693,7 @@ var EditorCanvas = ({
8481
8693
  context: contextForArtboard(pageItem),
8482
8694
  viewport: pageViewport,
8483
8695
  mode: previewMode ? "runtime" : "editor",
8696
+ selectedNodeId,
8484
8697
  onNodeClick: (id, e) => {
8485
8698
  if (!previewMode) {
8486
8699
  if (e?.shiftKey) {
@@ -8616,7 +8829,7 @@ var EditorCanvas = ({
8616
8829
  containerRef: activeArtboardRef
8617
8830
  }
8618
8831
  ),
8619
- !previewMode && !isMultiSelecting && selectedRect && selectedNodeId && selectedNodeId !== activeDoc.document.id && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
8832
+ !previewMode && !isMultiSelecting && !(isTouchDevice && isSmallScreen) && selectedRect && selectedNodeId && selectedNodeId !== activeDoc.document.id && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
8620
8833
  ResizeHandles,
8621
8834
  {
8622
8835
  selectedNodeId,
@@ -8626,7 +8839,7 @@ var EditorCanvas = ({
8626
8839
  onGuidesChange: setActiveGuides
8627
8840
  }
8628
8841
  ),
8629
- !previewMode && !isMultiSelecting && selectedRect && selectedNodeId && selectedNodeId !== activeDoc.document.id && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
8842
+ !previewMode && !isMultiSelecting && !(isTouchDevice && isSmallScreen) && selectedRect && selectedNodeId && selectedNodeId !== activeDoc.document.id && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
8630
8843
  SpacingSliders,
8631
8844
  {
8632
8845
  selectedNodeId,
@@ -18063,7 +18276,12 @@ var CATEGORY_LABELS = {
18063
18276
  data: "Data",
18064
18277
  custom: "Custom"
18065
18278
  };
18066
- var ComponentPanel = ({ registry, config, className }) => {
18279
+ var ComponentPanel = ({
18280
+ registry,
18281
+ config,
18282
+ className,
18283
+ onItemInserted
18284
+ }) => {
18067
18285
  const insertComponent = useEditorStore((s) => s.insertComponent);
18068
18286
  const setDragPayload = useEditorStore((s) => s.setDragPayload);
18069
18287
  const [error, setError] = (0, import_react36.useState)(null);
@@ -18095,7 +18313,12 @@ var ComponentPanel = ({ registry, config, className }) => {
18095
18313
  }).filter((group) => group.items.length > 0);
18096
18314
  const handleInsert = (definition) => {
18097
18315
  const result = insertComponent(definition.type, registry);
18098
- setError(result.success ? null : result.error ?? `Could not insert "${definition.label}".`);
18316
+ if (result.success) {
18317
+ setError(null);
18318
+ onItemInserted?.();
18319
+ } else {
18320
+ setError(result.error ?? `Could not insert "${definition.label}".`);
18321
+ }
18099
18322
  };
18100
18323
  const handleDragStart = (e, definition) => {
18101
18324
  e.dataTransfer.effectAllowed = "copy";
@@ -18261,7 +18484,8 @@ var BlockThumbnail = ({ block }) => {
18261
18484
  var BlocksPanel = ({
18262
18485
  blocks = import_components8.STARTER_BLOCKS,
18263
18486
  className,
18264
- onInsertBlock
18487
+ onInsertBlock,
18488
+ onItemInserted
18265
18489
  }) => {
18266
18490
  const { document: document2, selectedNodeId, dispatch, selectNode } = useEditorStore();
18267
18491
  const [selectedCategory, setSelectedCategory] = (0, import_react37.useState)("all");
@@ -18281,6 +18505,7 @@ var BlocksPanel = ({
18281
18505
  const handleInsert = (block) => {
18282
18506
  if (onInsertBlock) {
18283
18507
  onInsertBlock(block);
18508
+ onItemInserted?.();
18284
18509
  return;
18285
18510
  }
18286
18511
  const existingIds = (0, import_core15.collectNodeIdSet)(document2.document);
@@ -18298,9 +18523,11 @@ var BlocksPanel = ({
18298
18523
  try {
18299
18524
  dispatch((doc) => (0, import_core15.insertNode)(doc, { parentId: targetParentId, node: nodeTree }));
18300
18525
  selectNode(nodeTree.id);
18526
+ onItemInserted?.();
18301
18527
  } catch {
18302
18528
  dispatch((doc) => (0, import_core15.insertNode)(doc, { parentId: document2.document.id, node: nodeTree }));
18303
18529
  selectNode(nodeTree.id);
18530
+ onItemInserted?.();
18304
18531
  }
18305
18532
  };
18306
18533
  const setDragPayload = useEditorStore((s) => s.setDragPayload);
@@ -18374,7 +18601,8 @@ var LeftSidebar = ({
18374
18601
  defaultTab: propDefaultTab,
18375
18602
  availableTabs: propAvailableTabs,
18376
18603
  config,
18377
- className
18604
+ className,
18605
+ onItemInserted
18378
18606
  }) => {
18379
18607
  const tabsList = config?.availableTabs ?? propAvailableTabs ?? ["components", "blocks", "layers"];
18380
18608
  const initialTabCandidate = config?.defaultTab ?? propDefaultTab ?? "components";
@@ -18449,8 +18677,8 @@ var LeftSidebar = ({
18449
18677
  }
18450
18678
  ),
18451
18679
  /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex-1 overflow-hidden min-h-0", children: [
18452
- activeTab === "components" && tabsList.includes("components") && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { role: "tabpanel", id: "tabpanel-components", "aria-labelledby": "tab-components", className: "h-full", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ComponentPanel, { registry, config }) }),
18453
- activeTab === "blocks" && tabsList.includes("blocks") && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { role: "tabpanel", id: "tabpanel-blocks", "aria-labelledby": "tab-blocks", className: "h-full", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(BlocksPanel, { registry }) }),
18680
+ activeTab === "components" && tabsList.includes("components") && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { role: "tabpanel", id: "tabpanel-components", "aria-labelledby": "tab-components", className: "h-full", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(ComponentPanel, { registry, config, onItemInserted }) }),
18681
+ activeTab === "blocks" && tabsList.includes("blocks") && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { role: "tabpanel", id: "tabpanel-blocks", "aria-labelledby": "tab-blocks", className: "h-full", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(BlocksPanel, { registry, onItemInserted }) }),
18454
18682
  activeTab === "layers" && tabsList.includes("layers") && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { role: "tabpanel", id: "tabpanel-layers", "aria-labelledby": "tab-layers", className: "h-full", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(LayersPanel, { registry }) })
18455
18683
  ] })
18456
18684
  ] });
@@ -18799,6 +19027,7 @@ var KubuildEditor = ({
18799
19027
  initialDocument,
18800
19028
  pages,
18801
19029
  activePageId,
19030
+ selectedNodeId: propSelectedNodeId,
18802
19031
  onActivePageChange,
18803
19032
  onPagesChange,
18804
19033
  registry = (0, import_components9.createDefaultComponentRegistry)(),
@@ -18810,27 +19039,29 @@ var KubuildEditor = ({
18810
19039
  ai,
18811
19040
  className
18812
19041
  }) => {
18813
- const {
18814
- document: document2,
18815
- setDocument,
18816
- setOnChangeHandler,
18817
- setVariableCatalog,
18818
- viewport,
18819
- setViewport,
18820
- selectedNodeId,
18821
- tableSpreadsheetMode,
18822
- setTableSpreadsheetMode,
18823
- aiChatMode,
18824
- setAiChatMode,
18825
- undo,
18826
- redo,
18827
- canUndo,
18828
- canRedo,
18829
- previewMode,
18830
- multiDeviceMode,
18831
- toggleMultiDeviceMode,
18832
- actionDebuggerOpen
18833
- } = useEditorStore();
19042
+ const document2 = useEditorStore((state) => state.document);
19043
+ const setDocument = useEditorStore((state) => state.setDocument);
19044
+ const setOnChangeHandler = useEditorStore((state) => state.setOnChangeHandler);
19045
+ const setVariableCatalog = useEditorStore((state) => state.setVariableCatalog);
19046
+ const viewport = useEditorStore((state) => state.viewport);
19047
+ const setViewport = useEditorStore((state) => state.setViewport);
19048
+ const storeSelectedNodeId = useEditorStore((state) => state.selectedNodeId);
19049
+ const selectedNodeId = propSelectedNodeId !== void 0 ? propSelectedNodeId : storeSelectedNodeId;
19050
+ const tableSpreadsheetMode = useEditorStore((state) => state.tableSpreadsheetMode);
19051
+ const setTableSpreadsheetMode = useEditorStore((state) => state.setTableSpreadsheetMode);
19052
+ const aiChatMode = useEditorStore((state) => state.aiChatMode);
19053
+ const setAiChatMode = useEditorStore((state) => state.setAiChatMode);
19054
+ const undo = useEditorStore((state) => state.undo);
19055
+ const redo = useEditorStore((state) => state.redo);
19056
+ const canUndo = useEditorStore((state) => state.canUndo);
19057
+ const canRedo = useEditorStore((state) => state.canRedo);
19058
+ const previewMode = useEditorStore((state) => state.previewMode);
19059
+ const multiDeviceMode = useEditorStore((state) => state.multiDeviceMode);
19060
+ const toggleMultiDeviceMode = useEditorStore((state) => state.toggleMultiDeviceMode);
19061
+ const actionDebuggerOpen = useEditorStore((state) => state.actionDebuggerOpen);
19062
+ const moveComponentUp = useEditorStore((state) => state.moveComponentUp);
19063
+ const moveComponentDown = useEditorStore((state) => state.moveComponentDown);
19064
+ const deleteComponent = useEditorStore((state) => state.deleteComponent);
18834
19065
  const lastLoadedDocRef = import_react40.default.useRef(void 0);
18835
19066
  const [isMobileSidebarOpen, setIsMobileSidebarOpen] = (0, import_react40.useState)(false);
18836
19067
  const [isMobileInspectorOpen, setIsMobileInspectorOpen] = (0, import_react40.useState)(false);
@@ -19007,7 +19238,14 @@ var KubuildEditor = ({
19007
19238
  }
19008
19239
  )
19009
19240
  ] }),
19010
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "flex-1 overflow-hidden min-h-0", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(LeftSidebar, { registry, config: resolvedConfig.sidebar }) })
19241
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "flex-1 overflow-hidden min-h-0", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
19242
+ LeftSidebar,
19243
+ {
19244
+ registry,
19245
+ config: resolvedConfig.sidebar,
19246
+ onItemInserted: () => setIsMobileSidebarOpen(false)
19247
+ }
19248
+ ) })
19011
19249
  ] })
19012
19250
  ] }),
19013
19251
  isMobileInspectorOpen && resolvedConfig.inspector.enabled && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "fixed inset-0 z-50 flex justify-end lg:hidden animate-in fade-in duration-200", children: [
@@ -19150,22 +19388,61 @@ var KubuildEditor = ({
19150
19388
  className: "flex-1 min-h-0"
19151
19389
  }
19152
19390
  ),
19153
- selectedNodeId && !isMobileInspectorOpen && resolvedConfig.inspector.enabled && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("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__ */ (0, import_jsx_runtime46.jsxs)(
19154
- "button",
19391
+ selectedNodeId && !isMobileInspectorOpen && resolvedConfig.inspector.enabled && /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
19392
+ "div",
19155
19393
  {
19156
- type: "button",
19157
- onClick: () => setIsMobileInspectorOpen(true),
19158
- 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",
19394
+ "data-testid": "mobile-selected-node-actions",
19395
+ className: "lg:hidden fixed bottom-14 left-1/2 -translate-x-1/2 z-30 animate-in fade-in slide-in-from-bottom-2 flex items-center bg-slate-900/90 text-white rounded-full shadow-2xl backdrop-blur-md border border-slate-700/60 p-1 gap-1",
19159
19396
  children: [
19160
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react22.Sliders, { className: "w-3.5 h-3.5" }),
19161
- /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { children: [
19162
- "Edit Element (#",
19163
- selectedNodeId,
19164
- ")"
19165
- ] })
19397
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
19398
+ "button",
19399
+ {
19400
+ type: "button",
19401
+ onClick: () => moveComponentUp(selectedNodeId, registry),
19402
+ title: "Move Up",
19403
+ "aria-label": "Move Up",
19404
+ className: "p-1.5 rounded-full hover:bg-slate-800 active:bg-slate-700 text-slate-200 hover:text-white transition cursor-pointer",
19405
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react22.ArrowUp, { className: "w-3.5 h-3.5" })
19406
+ }
19407
+ ),
19408
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
19409
+ "button",
19410
+ {
19411
+ type: "button",
19412
+ onClick: () => moveComponentDown(selectedNodeId, registry),
19413
+ title: "Move Down",
19414
+ "aria-label": "Move Down",
19415
+ className: "p-1.5 rounded-full hover:bg-slate-800 active:bg-slate-700 text-slate-200 hover:text-white transition cursor-pointer",
19416
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react22.ArrowDown, { className: "w-3.5 h-3.5" })
19417
+ }
19418
+ ),
19419
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "w-px h-3.5 bg-slate-700 mx-0.5" }),
19420
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
19421
+ "button",
19422
+ {
19423
+ type: "button",
19424
+ onClick: () => setIsMobileInspectorOpen(true),
19425
+ className: "flex items-center gap-1.5 bg-blue-600 hover:bg-blue-500 text-white text-[11px] font-semibold px-2.5 py-1 rounded-full shadow-md active:scale-95 transition cursor-pointer",
19426
+ children: [
19427
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react22.Sliders, { className: "w-3 h-3" }),
19428
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: `Edit (#${selectedNodeId})` })
19429
+ ]
19430
+ }
19431
+ ),
19432
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
19433
+ "button",
19434
+ {
19435
+ type: "button",
19436
+ onClick: () => deleteComponent(selectedNodeId),
19437
+ title: "Delete",
19438
+ "aria-label": "Delete",
19439
+ className: "p-1.5 rounded-full hover:bg-red-900/40 text-red-400 hover:text-red-300 transition active:scale-95 cursor-pointer",
19440
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react22.Trash2, { className: "w-3.5 h-3.5" })
19441
+ }
19442
+ )
19166
19443
  ]
19167
19444
  }
19168
- ) }),
19445
+ ),
19169
19446
  resolvedConfig.canvas.showBreadcrumbs && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(HierarchyBreadcrumbs, { registry }),
19170
19447
  previewMode && actionDebuggerOpen && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "fixed bottom-4 right-4 z-50 animate-in fade-in slide-in-from-bottom-3 duration-200", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(ActionDebuggerPanel, {}) })
19171
19448
  ] }),