@archie/devtools 0.0.44 → 0.0.46

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.
@@ -616,44 +616,6 @@ function getFirstCollision(collisions, property) {
616
616
  const [firstCollision] = collisions;
617
617
  return property ? firstCollision[property] : firstCollision;
618
618
  }
619
- function centerOfRectangle(rect, left, top) {
620
- if (left === void 0) {
621
- left = rect.left;
622
- }
623
- if (top === void 0) {
624
- top = rect.top;
625
- }
626
- return {
627
- x: left + rect.width * 0.5,
628
- y: top + rect.height * 0.5
629
- };
630
- }
631
- var closestCenter = (_ref) => {
632
- let {
633
- collisionRect,
634
- droppableRects,
635
- droppableContainers
636
- } = _ref;
637
- const centerRect = centerOfRectangle(collisionRect, collisionRect.left, collisionRect.top);
638
- const collisions = [];
639
- for (const droppableContainer of droppableContainers) {
640
- const {
641
- id
642
- } = droppableContainer;
643
- const rect = droppableRects.get(id);
644
- if (rect) {
645
- const distBetween = distanceBetween(centerOfRectangle(rect), centerRect);
646
- collisions.push({
647
- id,
648
- data: {
649
- droppableContainer,
650
- value: distBetween
651
- }
652
- });
653
- }
654
- }
655
- return collisions.sort(sortCollisionsAsc);
656
- };
657
619
  var closestCorners = (_ref) => {
658
620
  let {
659
621
  collisionRect,
@@ -5260,9 +5222,12 @@ function ensureUniqueNodeId(base, used) {
5260
5222
  return `${base}::${idx}`;
5261
5223
  }
5262
5224
  var INSERT_NODE_ID_PREFIX = "insert:";
5225
+ var INSERT_CHILD_NODE_ID_PREFIX = "insert-child:";
5263
5226
  function resolveStableNodeId(el, used) {
5264
5227
  const persisted = el.getAttribute(DND_NODE_ID_ATTR);
5265
- if (persisted && persisted.startsWith(INSERT_NODE_ID_PREFIX)) return persisted;
5228
+ if (persisted && (persisted.startsWith(INSERT_NODE_ID_PREFIX) || persisted.startsWith(INSERT_CHILD_NODE_ID_PREFIX))) {
5229
+ return persisted;
5230
+ }
5266
5231
  if (persisted && !used.has(persisted)) return persisted;
5267
5232
  const projected = projectUsageMeta(el);
5268
5233
  if (projected) return ensureUniqueNodeId(projected.nodeId, used);
@@ -5579,9 +5544,29 @@ function useElementScanner(enabled) {
5579
5544
  // src/client/dnd/design-mode/ElementHighlight.tsx
5580
5545
  var import_react7 = require("react");
5581
5546
  var import_jsx_runtime2 = require("react/jsx-runtime");
5547
+ function computeLaneKinshipContainerIds({
5548
+ candidateContainerIds,
5549
+ activeContainerId,
5550
+ draggingNodeId,
5551
+ getParentContainerId,
5552
+ getContainerChildren: getContainerChildren2
5553
+ }) {
5554
+ const anchorId = activeContainerId ?? getParentContainerId(draggingNodeId);
5555
+ if (!anchorId) return /* @__PURE__ */ new Set();
5556
+ const parentId = getParentContainerId(anchorId);
5557
+ const kin = /* @__PURE__ */ new Set();
5558
+ if (!parentId) return kin;
5559
+ if (candidateContainerIds.has(parentId)) kin.add(parentId);
5560
+ for (const siblingId of getContainerChildren2(parentId)) {
5561
+ if (siblingId === anchorId) continue;
5562
+ if (candidateContainerIds.has(siblingId)) kin.add(siblingId);
5563
+ }
5564
+ return kin;
5565
+ }
5582
5566
  var HANDLES = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
5583
5567
  var HANDLE_HIT_SIZE = 24;
5584
5568
  var HANDLE_KNOB_SIZE = 8;
5569
+ var EMPTY_SCROLL_DELTA = { x: NaN, y: NaN };
5585
5570
  var OVERLAY_ROOT_STYLE = {
5586
5571
  position: "fixed",
5587
5572
  inset: 0,
@@ -5655,16 +5640,38 @@ function handlePosition(dir) {
5655
5640
  };
5656
5641
  return positions[dir];
5657
5642
  }
5658
- function getOutlineStyle(rect, isSelected, inspectorTheme) {
5643
+ function getSelectedOutlineStyle(rect, inspectorTheme) {
5659
5644
  return {
5660
5645
  ...OUTLINE_BASE_STYLE,
5661
5646
  left: rect.left,
5662
5647
  top: rect.top,
5663
5648
  width: rect.width,
5664
5649
  height: rect.height,
5665
- border: isSelected ? `2px solid ${inspectorTheme.accent}` : `1px dashed ${inspectorTheme.shared}`,
5666
- backgroundColor: isSelected ? inspectorTheme.accentSoft : "transparent"
5650
+ border: `2px solid ${inspectorTheme.accent}`,
5651
+ backgroundColor: inspectorTheme.accentSoft
5652
+ };
5653
+ }
5654
+ function getGuideOutlineStyle(rect, tier, inspectorTheme) {
5655
+ const base = {
5656
+ ...OUTLINE_BASE_STYLE,
5657
+ left: rect.left,
5658
+ top: rect.top,
5659
+ width: rect.width,
5660
+ height: rect.height
5667
5661
  };
5662
+ switch (tier) {
5663
+ case "candidate":
5664
+ return { ...base, border: `1px solid ${inspectorTheme.accent}`, opacity: 0.2 };
5665
+ case "lane-child":
5666
+ return { ...base, border: `1px dashed ${inspectorTheme.accent}`, opacity: 0.45 };
5667
+ case "lane":
5668
+ return {
5669
+ ...base,
5670
+ border: `1.5px solid ${inspectorTheme.accent}`,
5671
+ backgroundColor: inspectorTheme.accentSoft,
5672
+ opacity: 0.75
5673
+ };
5674
+ }
5668
5675
  }
5669
5676
  function getResizeHandleStyle(position) {
5670
5677
  return {
@@ -5688,40 +5695,58 @@ function getLabelStyle(labelLeft, labelTop, labelColor) {
5688
5695
  backgroundColor: labelColor
5689
5696
  };
5690
5697
  }
5698
+ function projectDragRect(dragGuide, nodeId, sdx, sdy) {
5699
+ const r = dragGuide.rects.get(nodeId);
5700
+ if (!r) return null;
5701
+ return { left: r.left - sdx, top: r.top - sdy, width: r.width, height: r.height };
5702
+ }
5691
5703
  function ElementHighlight({
5692
- elements,
5693
- draggingNodeId,
5694
- activeEl,
5695
- arrangeSelectedNodeId,
5704
+ selected,
5705
+ dragGuide,
5696
5706
  externalDragActive,
5697
5707
  onResizeHandlePointerDown
5698
5708
  }) {
5699
5709
  const inspectorTheme = useDesignModeStore((s) => s.inspectorTheme);
5700
- const internalDragInProgress = draggingNodeId !== null;
5701
- const overlayVisible = arrangeSelectedNodeId !== null || internalDragInProgress;
5702
5710
  const containerRef = (0, import_react7.useRef)(null);
5703
5711
  const rafRef = (0, import_react7.useRef)(0);
5712
+ const lastScrollDeltaRef = (0, import_react7.useRef)(EMPTY_SCROLL_DELTA);
5704
5713
  const syncPositions = (0, import_react7.useCallback)(() => {
5705
5714
  const container = containerRef.current;
5706
5715
  if (!container) return;
5707
- const outlines = container.querySelectorAll("[data-outline-for]");
5708
- for (const outline of outlines) {
5709
- const nodeId = outline.getAttribute("data-outline-for");
5710
- const el = elements.find((e) => e.nodeId === nodeId);
5711
- if (!el) continue;
5712
- const rect = el.element.getBoundingClientRect();
5713
- outline.style.left = `${rect.left}px`;
5714
- outline.style.top = `${rect.top}px`;
5715
- outline.style.width = `${rect.width}px`;
5716
- outline.style.height = `${rect.height}px`;
5717
- const label = outline.nextElementSibling;
5718
- if (label) {
5719
- label.style.left = `${rect.left}px`;
5720
- label.style.top = `${rect.top}px`;
5716
+ if (dragGuide) {
5717
+ const sdx = window.scrollX - dragGuide.startScroll.x;
5718
+ const sdy = window.scrollY - dragGuide.startScroll.y;
5719
+ if (sdx === lastScrollDeltaRef.current.x && sdy === lastScrollDeltaRef.current.y) return;
5720
+ lastScrollDeltaRef.current = { x: sdx, y: sdy };
5721
+ const outlines = container.querySelectorAll("[data-guide-for]");
5722
+ for (const outline2 of outlines) {
5723
+ const nodeId = outline2.getAttribute("data-guide-for");
5724
+ const r = dragGuide.rects.get(nodeId);
5725
+ if (!r) continue;
5726
+ outline2.style.left = `${r.left - sdx}px`;
5727
+ outline2.style.top = `${r.top - sdy}px`;
5721
5728
  }
5729
+ return;
5722
5730
  }
5723
- }, [elements]);
5731
+ if (!selected) return;
5732
+ const rect2 = selected.element.getBoundingClientRect();
5733
+ const outline = container.querySelector("[data-outline-for]");
5734
+ if (outline) {
5735
+ outline.style.left = `${rect2.left}px`;
5736
+ outline.style.top = `${rect2.top}px`;
5737
+ outline.style.width = `${rect2.width}px`;
5738
+ outline.style.height = `${rect2.height}px`;
5739
+ }
5740
+ const label = container.querySelector("[data-drag-handle]");
5741
+ if (label) {
5742
+ label.style.left = `${rect2.left + 12}px`;
5743
+ label.style.top = `${Math.max(0, rect2.top - 20)}px`;
5744
+ }
5745
+ }, [dragGuide, selected]);
5746
+ const loopActive = !externalDragActive && (dragGuide !== null || selected !== null);
5724
5747
  (0, import_react7.useEffect)(() => {
5748
+ if (!loopActive) return;
5749
+ lastScrollDeltaRef.current = EMPTY_SCROLL_DELTA;
5725
5750
  let running = true;
5726
5751
  const loop = () => {
5727
5752
  if (!running) return;
@@ -5733,81 +5758,113 @@ function ElementHighlight({
5733
5758
  running = false;
5734
5759
  cancelAnimationFrame(rafRef.current);
5735
5760
  };
5736
- }, [syncPositions]);
5737
- if (!overlayVisible) return null;
5738
- if (externalDragActive) return null;
5739
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5740
- "div",
5741
- {
5742
- ref: containerRef,
5743
- "data-design-mode-ui": true,
5744
- style: OVERLAY_ROOT_STYLE,
5745
- children: elements.map((el) => {
5746
- if (el.nodeId === draggingNodeId) return null;
5747
- if (activeEl && activeEl !== el.element && activeEl.contains(el.element)) return null;
5748
- const isSelected = el.nodeId === arrangeSelectedNodeId;
5749
- if (!isSelected && !internalDragInProgress) return null;
5750
- const rect = el.element.getBoundingClientRect();
5751
- const showLabel = isSelected;
5752
- const labelTop = isSelected ? Math.max(0, rect.top - 20) : rect.top;
5753
- const labelLeft = isSelected ? rect.left + 12 : rect.left;
5754
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
5755
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5756
- "div",
5757
- {
5758
- "data-outline-for": el.nodeId,
5759
- "data-design-mode-ui": true,
5760
- style: getOutlineStyle(rect, isSelected, inspectorTheme),
5761
- children: isSelected && el.draggable && HANDLES.map((dir) => {
5762
- const pos = handlePosition(dir);
5763
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5764
- "div",
5765
- {
5766
- "data-design-mode-ui": true,
5767
- "data-resize-handle": dir,
5768
- onPointerDown: (e) => {
5769
- e.stopPropagation();
5770
- onResizeHandlePointerDown?.(e, el.nodeId, dir);
5771
- },
5772
- style: getResizeHandleStyle(pos),
5773
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5774
- "span",
5775
- {
5776
- "data-design-mode-ui": true,
5777
- style: getResizeKnobStyle(inspectorTheme.accent)
5778
- }
5779
- )
5780
- },
5781
- dir
5782
- );
5783
- })
5784
- }
5785
- ),
5786
- showLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
5761
+ }, [loopActive, syncPositions]);
5762
+ if (!loopActive) return null;
5763
+ if (dragGuide) {
5764
+ const sdx = window.scrollX - dragGuide.startScroll.x;
5765
+ const sdy = window.scrollY - dragGuide.startScroll.y;
5766
+ const { candidateContainerIds, activeContainerId, activeChildIds, draggingNodeId } = dragGuide;
5767
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref: containerRef, "data-design-mode-ui": true, style: OVERLAY_ROOT_STYLE, children: [
5768
+ [...candidateContainerIds].map((id) => {
5769
+ if (id === activeContainerId || id === draggingNodeId) return null;
5770
+ const rect2 = projectDragRect(dragGuide, id, sdx, sdy);
5771
+ if (!rect2) return null;
5772
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5773
+ "div",
5774
+ {
5775
+ "data-guide-for": id,
5776
+ "data-guide-tier": "candidate",
5777
+ "data-design-mode-ui": true,
5778
+ style: getGuideOutlineStyle(rect2, "candidate", inspectorTheme)
5779
+ },
5780
+ `candidate:${id}`
5781
+ );
5782
+ }),
5783
+ activeContainerId !== null && activeChildIds.map((id) => {
5784
+ if (id === draggingNodeId) return null;
5785
+ const rect2 = projectDragRect(dragGuide, id, sdx, sdy);
5786
+ if (!rect2) return null;
5787
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5788
+ "div",
5789
+ {
5790
+ "data-guide-for": id,
5791
+ "data-guide-tier": "lane-child",
5792
+ "data-design-mode-ui": true,
5793
+ style: getGuideOutlineStyle(rect2, "lane-child", inspectorTheme)
5794
+ },
5795
+ `lane-child:${id}`
5796
+ );
5797
+ }),
5798
+ activeContainerId !== null && activeContainerId !== draggingNodeId && (() => {
5799
+ const rect2 = projectDragRect(dragGuide, activeContainerId, sdx, sdy);
5800
+ if (!rect2) return null;
5801
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5802
+ "div",
5803
+ {
5804
+ "data-guide-for": activeContainerId,
5805
+ "data-guide-tier": "lane",
5806
+ "data-design-mode-ui": true,
5807
+ style: getGuideOutlineStyle(rect2, "lane", inspectorTheme)
5808
+ },
5809
+ `lane:${activeContainerId}`
5810
+ );
5811
+ })()
5812
+ ] });
5813
+ }
5814
+ if (!selected) return null;
5815
+ const rect = selected.element.getBoundingClientRect();
5816
+ const labelTop = Math.max(0, rect.top - 20);
5817
+ const labelLeft = rect.left + 12;
5818
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref: containerRef, "data-design-mode-ui": true, style: OVERLAY_ROOT_STYLE, children: [
5819
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5820
+ "div",
5821
+ {
5822
+ "data-outline-for": selected.nodeId,
5823
+ "data-design-mode-ui": true,
5824
+ style: getSelectedOutlineStyle(rect, inspectorTheme),
5825
+ children: selected.draggable && HANDLES.map((dir) => {
5826
+ const pos = handlePosition(dir);
5827
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5787
5828
  "div",
5788
5829
  {
5789
5830
  "data-design-mode-ui": true,
5790
- "data-drag-handle": el.nodeId,
5791
- style: getLabelStyle(
5792
- labelLeft,
5793
- labelTop,
5794
- isSelected ? inspectorTheme.accent : inspectorTheme.hover
5795
- ),
5796
- children: [
5797
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: LABEL_ICON_STYLE, children: "\u283F" }),
5798
- el.label,
5799
- isSelected && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: LABEL_DIMENSIONS_STYLE, children: [
5800
- Math.round(rect.width),
5801
- "\xD7",
5802
- Math.round(rect.height)
5803
- ] })
5804
- ]
5805
- }
5806
- )
5807
- ] }, el.nodeId);
5808
- })
5809
- }
5810
- );
5831
+ "data-resize-handle": dir,
5832
+ onPointerDown: (e) => {
5833
+ e.stopPropagation();
5834
+ onResizeHandlePointerDown?.(e, selected.nodeId, dir);
5835
+ },
5836
+ style: getResizeHandleStyle(pos),
5837
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5838
+ "span",
5839
+ {
5840
+ "data-design-mode-ui": true,
5841
+ style: getResizeKnobStyle(inspectorTheme.accent)
5842
+ }
5843
+ )
5844
+ },
5845
+ dir
5846
+ );
5847
+ })
5848
+ }
5849
+ ),
5850
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
5851
+ "div",
5852
+ {
5853
+ "data-design-mode-ui": true,
5854
+ "data-drag-handle": selected.nodeId,
5855
+ style: getLabelStyle(labelLeft, labelTop, inspectorTheme.accent),
5856
+ children: [
5857
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: LABEL_ICON_STYLE, children: "\u283F" }),
5858
+ selected.label,
5859
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: LABEL_DIMENSIONS_STYLE, children: [
5860
+ Math.round(rect.width),
5861
+ "\xD7",
5862
+ Math.round(rect.height)
5863
+ ] })
5864
+ ]
5865
+ }
5866
+ )
5867
+ ] });
5811
5868
  }
5812
5869
 
5813
5870
  // src/client/dnd/design-mode/projection.ts
@@ -6093,6 +6150,15 @@ var NON_DRAGGABLE_DESCENDANT_TAGS = /* @__PURE__ */ new Set([
6093
6150
  "source",
6094
6151
  "track"
6095
6152
  ]);
6153
+ var ARCHIE_INSERTED_ATTR = "data-archie-inserted";
6154
+ var INSERT_CHILD_NODE_ID_PREFIX2 = "insert-child:";
6155
+ function sealDescendantNodeId(node) {
6156
+ if (node.hasAttribute("data-dnd-node-id")) return;
6157
+ const marker = node.getAttribute(ARCHIE_INSERTED_ATTR);
6158
+ if (!marker) return;
6159
+ if (marker.includes('"') || marker.includes("'")) return;
6160
+ node.setAttribute("data-dnd-node-id", `${INSERT_CHILD_NODE_ID_PREFIX2}${marker}`);
6161
+ }
6096
6162
  function markInsertedDescendants(root) {
6097
6163
  const descendants = root.querySelectorAll("*");
6098
6164
  for (const node of descendants) {
@@ -6100,6 +6166,7 @@ function markInsertedDescendants(root) {
6100
6166
  if (NON_DRAGGABLE_DESCENDANT_TAGS.has(tag)) continue;
6101
6167
  node.setAttribute("data-dnd-force-allow", "");
6102
6168
  node.setAttribute("data-dnd-moved", "");
6169
+ sealDescendantNodeId(node);
6103
6170
  }
6104
6171
  }
6105
6172
 
@@ -6601,30 +6668,6 @@ function applyDndCommandPlacement(command, direction, resolveNodeElement) {
6601
6668
  return true;
6602
6669
  }
6603
6670
 
6604
- // src/client/dnd/design-mode/design-mode-collision.ts
6605
- function filterDroppables(droppableContainers, blockedContainerIds) {
6606
- if (!blockedContainerIds || blockedContainerIds.size === 0) return droppableContainers;
6607
- return droppableContainers.filter((container) => !blockedContainerIds.has(String(container.id)));
6608
- }
6609
- function getCollisions(detector, args) {
6610
- return detector(args);
6611
- }
6612
- function createDesignModeCollisionDetection(blockedContainerIds) {
6613
- return (args) => {
6614
- const droppableContainers = filterDroppables(args.droppableContainers, blockedContainerIds);
6615
- if (droppableContainers.length === 0) return [];
6616
- const scopedArgs = {
6617
- ...args,
6618
- droppableContainers
6619
- };
6620
- const pointerHits = getCollisions(pointerWithin, scopedArgs);
6621
- if (pointerHits.length > 0) return pointerHits;
6622
- const cornerHits = getCollisions(closestCorners, scopedArgs);
6623
- if (cornerHits.length > 0) return cornerHits;
6624
- return getCollisions(closestCenter, scopedArgs);
6625
- };
6626
- }
6627
-
6628
6671
  // src/client/dnd/design-mode/arrange-session.ts
6629
6672
  function serializeArrangeChange(event, index) {
6630
6673
  return {
@@ -6903,9 +6946,9 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6903
6946
  const lastIntentRef = (0, import_react11.useRef)(null);
6904
6947
  const visualIntentRef = (0, import_react11.useRef)(null);
6905
6948
  const sourceContainerIdRef = (0, import_react11.useRef)(null);
6949
+ const activeLaneContainerIdRef = (0, import_react11.useRef)(null);
6906
6950
  const rowLockedRef = (0, import_react11.useRef)(false);
6907
6951
  const dragSessionAnchorRef = (0, import_react11.useRef)(null);
6908
- const lockRef = (0, import_react11.useRef)({ containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null });
6909
6952
  const targetPlaneRef = (0, import_react11.useRef)("lane");
6910
6953
  const nestCandidateRef = (0, import_react11.useRef)(null);
6911
6954
  const nestTargetRef = (0, import_react11.useRef)(null);
@@ -6915,7 +6958,6 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6915
6958
  const sourceCommitLaneRef = (0, import_react11.useRef)(null);
6916
6959
  const commitLaneCacheRef = (0, import_react11.useRef)(/* @__PURE__ */ new Map());
6917
6960
  const safeZoneRef = (0, import_react11.useRef)(null);
6918
- const blockedContainerIdsRef = (0, import_react11.useRef)(/* @__PURE__ */ new Set());
6919
6961
  const historyRef = (0, import_react11.useRef)(new DndCommandHistory());
6920
6962
  const processedUndoIdRef = (0, import_react11.useRef)(undoRequestId);
6921
6963
  const processedRedoIdRef = (0, import_react11.useRef)(redoRequestId);
@@ -6935,9 +6977,9 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6935
6977
  lastIntentRef,
6936
6978
  visualIntentRef,
6937
6979
  sourceContainerIdRef,
6980
+ activeLaneContainerIdRef,
6938
6981
  rowLockedRef,
6939
6982
  dragSessionAnchorRef,
6940
- lockRef,
6941
6983
  targetPlaneRef,
6942
6984
  nestCandidateRef,
6943
6985
  nestTargetRef,
@@ -6947,7 +6989,6 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6947
6989
  sourceCommitLaneRef,
6948
6990
  commitLaneCacheRef,
6949
6991
  safeZoneRef,
6950
- blockedContainerIdsRef,
6951
6992
  historyRef,
6952
6993
  processedUndoIdRef,
6953
6994
  processedRedoIdRef,
@@ -7215,6 +7256,11 @@ function pointInRect(px, py, r) {
7215
7256
  function pointInExpandedRect(px, py, r, inset) {
7216
7257
  return px >= r.left - inset && px <= r.right + inset && py >= r.top - inset && py <= r.bottom + inset;
7217
7258
  }
7259
+ function isInNestZone(px, py, rect) {
7260
+ const relX = (px - rect.left) / rect.width;
7261
+ const relY = (py - rect.top) / rect.height;
7262
+ return relX > NEST_EDGE_FRACTION && relX < 1 - NEST_EDGE_FRACTION && relY > NEST_EDGE_FRACTION && relY < 1 - NEST_EDGE_FRACTION;
7263
+ }
7218
7264
  function buildSlotQuadtree(slotIndex) {
7219
7265
  const rects = /* @__PURE__ */ new Map();
7220
7266
  for (const [id, slot] of slotIndex) {
@@ -7257,6 +7303,11 @@ var DragIntentEngine = class {
7257
7303
  __publicField(this, "laneAllowedContainers");
7258
7304
  __publicField(this, "blockedContainerIds");
7259
7305
  __publicField(this, "targetPlane", "lane");
7306
+ // Memoized result of getValidDropContainerIds(). Safe to cache because every
7307
+ // input it reads (containerIndex, slotIndex, activeId, laneAllowedContainers,
7308
+ // blockedContainerIds) is fixed in the constructor and never mutated during
7309
+ // a drag.
7310
+ __publicField(this, "validDropContainerIds", null);
7260
7311
  this.projectionTree = projection.projectionTree;
7261
7312
  this.containerIndex = projection.containerIndex;
7262
7313
  this.slotIndex = projection.slotIndex;
@@ -7293,6 +7344,44 @@ var DragIntentEngine = class {
7293
7344
  getContainerChildren(containerId) {
7294
7345
  return this.containerIndex.get(containerId)?.children ?? [];
7295
7346
  }
7347
+ /**
7348
+ * Every container id this engine would accept as a drop target for the
7349
+ * active node, at the current pointer position or any other — i.e. the
7350
+ * full set `resolvePointerContainer` draws from, not a snapshot of one
7351
+ * resolve() call. Intended for the visual layer to draw a guide that
7352
+ * matches exactly what the engine can land on, without re-deriving the
7353
+ * eligibility rule itself.
7354
+ *
7355
+ * Always queries with `laneLock: true`. That is not an approximation of
7356
+ * the "real" default — it IS the real answer for every reachable caller:
7357
+ *
7358
+ * - With a row lock active (`laneAllowedContainers` set), `isLaneContainer`
7359
+ * restricts eligibility to that set, so this returns exactly
7360
+ * `{srcContainerId}` — the ARCH-1071 lock, reproduced faithfully.
7361
+ * - Without a row lock, `laneAllowedContainers` is null and
7362
+ * `isLaneContainer` returns true unconditionally, so `laneLock`'s value
7363
+ * makes no difference — passing `true` costs nothing here.
7364
+ * - The engine's own 'nest' plane does call `isEligibleDropContainer` with
7365
+ * `laneLock: false` (see `peekNestIntent` → `resolvePointerContainer`),
7366
+ * but that plane is unreachable for a locked drag: `computeAllowNest`
7367
+ * (useDragVisualLoop.ts) short-circuits `rowLocked` drags before
7368
+ * `setTargetPlane('nest')` is ever called, so `targetPlane` stays
7369
+ * 'lane' for the drag's whole lifetime. If that guard is ever removed,
7370
+ * this method will under-report nest-only targets for locked drags —
7371
+ * keep the two in sync.
7372
+ *
7373
+ * Cached after the first call: every input (containerIndex, slotIndex,
7374
+ * activeId, laneAllowedContainers, blockedContainerIds) is fixed at
7375
+ * construction and never changes for the life of a drag.
7376
+ */
7377
+ getValidDropContainerIds() {
7378
+ if (this.validDropContainerIds) return this.validDropContainerIds;
7379
+ const ids2 = /* @__PURE__ */ new Set();
7380
+ for (const id of this.containerIndex.keys()) {
7381
+ if (this.isEligibleDropContainer(id, true)) ids2.add(id);
7382
+ }
7383
+ return this.validDropContainerIds = ids2;
7384
+ }
7296
7385
  setTargetPlane(plane) {
7297
7386
  this.targetPlane = plane;
7298
7387
  }
@@ -7311,6 +7400,17 @@ var DragIntentEngine = class {
7311
7400
  isInvalidDropContainer(containerId) {
7312
7401
  return containerId === this.activeId || this.isContainerDescendantOf(containerId, this.activeId);
7313
7402
  }
7403
+ /**
7404
+ * The full "can this container accept the active node this drag" predicate.
7405
+ * SINGLE definition — every caller routes through it so no two parts of the
7406
+ * system can disagree about which containers are droppable.
7407
+ */
7408
+ isEligibleDropContainer(containerId, laneLock) {
7409
+ if (this.isInvalidDropContainer(containerId)) return false;
7410
+ if (this.isBlockedContainer(containerId)) return false;
7411
+ if (laneLock && !this.isLaneContainer(containerId)) return false;
7412
+ return this.hasUsableSlots(containerId);
7413
+ }
7314
7414
  /**
7315
7415
  * Resolve the drag intent from the current pointer position.
7316
7416
  *
@@ -7398,9 +7498,7 @@ var DragIntentEngine = class {
7398
7498
  const childNode = this.getNodeRect(childId);
7399
7499
  if (!childNode) continue;
7400
7500
  if (!pointInRect(px, py, childNode)) continue;
7401
- const relX = (px - childNode.left) / childNode.width;
7402
- const relY = (py - childNode.top) / childNode.height;
7403
- if (relX > NEST_EDGE_FRACTION && relX < 1 - NEST_EDGE_FRACTION && relY > NEST_EDGE_FRACTION && relY < 1 - NEST_EDGE_FRACTION) {
7501
+ if (isInNestZone(px, py, childNode)) {
7404
7502
  return {
7405
7503
  containerId: pointerContainerId,
7406
7504
  index: container.children.indexOf(childId),
@@ -7414,11 +7512,8 @@ var DragIntentEngine = class {
7414
7512
  resolvePointerContainer(px, py, laneLock) {
7415
7513
  const pointerContainers = [];
7416
7514
  for (const [id, node] of this.containerIndex) {
7417
- if (this.isInvalidDropContainer(id)) continue;
7418
- if (this.isBlockedContainer(id)) continue;
7419
- if (laneLock && !this.isLaneContainer(id)) continue;
7420
7515
  if (!pointInRect(px, py, node.rect)) continue;
7421
- if (this.hasUsableSlots(id) === false) continue;
7516
+ if (!this.isEligibleDropContainer(id, laneLock)) continue;
7422
7517
  const r = node.rect;
7423
7518
  const edgeDistX = Math.min(px - r.left, r.right - px);
7424
7519
  const edgeDistY = Math.min(py - r.top, r.bottom - py);
@@ -7435,11 +7530,11 @@ var DragIntentEngine = class {
7435
7530
  let pointerContainerId = pointerContainers[0]?.id ?? null;
7436
7531
  if (!pointerContainerId && this.lastIntent?.mode === "reorder") {
7437
7532
  const lastContainer = this.containerIndex.get(this.lastIntent.containerId);
7438
- if (lastContainer && !this.isInvalidDropContainer(lastContainer.id) && !this.isBlockedContainer(lastContainer.id) && (!laneLock || this.isLaneContainer(lastContainer.id)) && this.hasUsableSlots(lastContainer.id) && pointInExpandedRect(px, py, lastContainer.rect, LAST_CONTAINER_REENTRY_SLOP_PX)) {
7533
+ if (lastContainer && this.isEligibleDropContainer(lastContainer.id, laneLock) && pointInExpandedRect(px, py, lastContainer.rect, LAST_CONTAINER_REENTRY_SLOP_PX)) {
7439
7534
  pointerContainerId = lastContainer.id;
7440
7535
  }
7441
7536
  }
7442
- if (this.originContainerId && this.originContainerId !== pointerContainerId && !this.isInvalidDropContainer(this.originContainerId) && !this.isBlockedContainer(this.originContainerId) && (!laneLock || this.isLaneContainer(this.originContainerId)) && this.hasUsableSlots(this.originContainerId)) {
7537
+ if (this.originContainerId && this.originContainerId !== pointerContainerId && this.isEligibleDropContainer(this.originContainerId, laneLock)) {
7443
7538
  const originNode = this.containerIndex.get(this.originContainerId);
7444
7539
  const currentNode = pointerContainerId ? this.containerIndex.get(pointerContainerId) : null;
7445
7540
  const originIsDeeper = originNode && (!currentNode || originNode.depth >= currentNode.depth);
@@ -7852,7 +7947,8 @@ function useDragVisualLoop({
7852
7947
  refs,
7853
7948
  getCommitLaneSnapshot,
7854
7949
  setInsideRect,
7855
- setDropLine
7950
+ setDropLine,
7951
+ setActiveLaneContainerId
7856
7952
  }) {
7857
7953
  (0, import_react12.useEffect)(() => {
7858
7954
  const engine = refs.engineRef.current;
@@ -7860,6 +7956,11 @@ function useDragVisualLoop({
7860
7956
  if (!activeDrag || !engine || !snapshot) return;
7861
7957
  let rafId;
7862
7958
  const animator = refs.animatorRef.current;
7959
+ const publishActiveLane = (containerId) => {
7960
+ if (refs.activeLaneContainerIdRef.current === containerId) return;
7961
+ refs.activeLaneContainerIdRef.current = containerId;
7962
+ setActiveLaneContainerId(containerId);
7963
+ };
7863
7964
  const setTargetPlane = (nextPlane) => {
7864
7965
  const prevPlane = refs.targetPlaneRef.current;
7865
7966
  if (prevPlane === nextPlane) return;
@@ -7878,6 +7979,7 @@ function useDragVisualLoop({
7878
7979
  const safeZone = refs.safeZoneRef.current;
7879
7980
  if (isPointerBlocked(px, py, safeZone)) {
7880
7981
  setTargetPlane("lane");
7982
+ publishActiveLane(null);
7881
7983
  refs.lastIntentRef.current = null;
7882
7984
  refs.visualIntentRef.current = null;
7883
7985
  refs.nestCandidateRef.current = null;
@@ -7919,6 +8021,7 @@ function useDragVisualLoop({
7919
8021
  let intent = engine.resolve(px, py, window.scrollX, window.scrollY, snapshot.scroll.x, snapshot.scroll.y);
7920
8022
  if (!intent) {
7921
8023
  setTargetPlane("lane");
8024
+ publishActiveLane(null);
7922
8025
  refs.lastIntentRef.current = null;
7923
8026
  refs.visualIntentRef.current = null;
7924
8027
  refs.nestCandidateRef.current = null;
@@ -7944,6 +8047,7 @@ function useDragVisualLoop({
7944
8047
  const targetId = intent.slotId.split("::nest::")[1] ?? "";
7945
8048
  if (!targetId) {
7946
8049
  setTargetPlane("lane");
8050
+ publishActiveLane(null);
7947
8051
  rafId = requestAnimationFrame(tick);
7948
8052
  return;
7949
8053
  }
@@ -7965,12 +8069,14 @@ function useDragVisualLoop({
7965
8069
  };
7966
8070
  refs.visualIntentRef.current = intent;
7967
8071
  refs.lastIntentRef.current = intent;
8072
+ publishActiveLane(intent.containerId);
7968
8073
  } else {
7969
8074
  setTargetPlane("lane");
7970
8075
  refs.nestCandidateRef.current = null;
7971
8076
  refs.nestTargetRef.current = null;
7972
8077
  refs.lastIntentRef.current = intent;
7973
8078
  refs.visualIntentRef.current = intent;
8079
+ publishActiveLane(intent.containerId);
7974
8080
  setInsideRect(null);
7975
8081
  const proj = refs.projectionRef.current;
7976
8082
  const slot = proj.slotIndex.get(intent.slotId);
@@ -8277,7 +8383,8 @@ function useDragLifecycle({
8277
8383
  syncHistoryAvailability,
8278
8384
  resolveNodeElement,
8279
8385
  setInsideRect,
8280
- setDropLine
8386
+ setDropLine,
8387
+ setActiveLaneContainerId
8281
8388
  }) {
8282
8389
  const [activeDrag, setActiveDrag] = (0, import_react13.useState)(null);
8283
8390
  const clearDragState = (0, import_react13.useCallback)(() => {
@@ -8288,13 +8395,12 @@ function useDragLifecycle({
8288
8395
  refs.snapshotRef.current = null;
8289
8396
  refs.projectionRef.current = EMPTY_PROJECTION;
8290
8397
  refs.engineRef.current = null;
8291
- refs.blockedContainerIdsRef.current = /* @__PURE__ */ new Set();
8292
8398
  refs.lastIntentRef.current = null;
8293
8399
  refs.visualIntentRef.current = null;
8294
8400
  refs.sourceContainerIdRef.current = null;
8401
+ refs.activeLaneContainerIdRef.current = null;
8295
8402
  refs.rowLockedRef.current = false;
8296
8403
  refs.dragSessionAnchorRef.current = null;
8297
- refs.lockRef.current = { containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null };
8298
8404
  refs.targetPlaneRef.current = "lane";
8299
8405
  refs.nestCandidateRef.current = null;
8300
8406
  refs.nestTargetRef.current = null;
@@ -8305,6 +8411,7 @@ function useDragLifecycle({
8305
8411
  refs.safeZoneRef.current = null;
8306
8412
  setInsideRect(null);
8307
8413
  setDropLine(null);
8414
+ setActiveLaneContainerId(null);
8308
8415
  setActiveDrag(null);
8309
8416
  resume();
8310
8417
  }, [clearCommitLaneCache, resume]);
@@ -8362,7 +8469,6 @@ function useDragLifecycle({
8362
8469
  const sourceCommitLane = getCommitLaneSnapshot(srcContainerId);
8363
8470
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
8364
8471
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
8365
- refs.blockedContainerIdsRef.current = blockedContainerIds;
8366
8472
  const engineOptions = buildDragIntentEngineOptions(anchorScanned.element, srcContainerId, blockedContainerIds);
8367
8473
  refs.rowLockedRef.current = !!engineOptions.laneAllowedContainers;
8368
8474
  refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, engineOptions);
@@ -8371,7 +8477,6 @@ function useDragLifecycle({
8371
8477
  refs.sourceContainerIdRef.current = srcContainerId;
8372
8478
  refs.sourceCommitLaneRef.current = sourceCommitLane;
8373
8479
  refs.dragSessionAnchorRef.current = anchor;
8374
- refs.lockRef.current = { containerId: srcContainerId, isCrossUnlocked: true, exitSince: null, unlockUntilTs: null };
8375
8480
  refs.dragStartPositionRef.current = startPosition;
8376
8481
  refs.safeZoneRef.current = resolveSafeZoneRect();
8377
8482
  pause();
@@ -8853,7 +8958,7 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
8853
8958
  const candidate = postInsertChildren[i];
8854
8959
  const candidateNodeId = candidate.getAttribute(DND_NODE_ID_ATTR);
8855
8960
  if (!candidateNodeId) continue;
8856
- const isEarlierInsert = candidateNodeId.startsWith("insert:");
8961
+ const isEarlierInsert = candidateNodeId.startsWith("insert:") || candidateNodeId.startsWith("insert-child:");
8857
8962
  if (!isEarlierInsert && !getInspectorRefFromElement(candidate)) continue;
8858
8963
  beforeSiblingEl = candidate;
8859
8964
  beforeSiblingNodeId = candidateNodeId;
@@ -8930,6 +9035,7 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
8930
9035
  // src/client/dnd/design-mode/DesignModeOverlay.tsx
8931
9036
  var import_jsx_runtime4 = require("react/jsx-runtime");
8932
9037
  var DROP_ANIMATION = { duration: DURATION_MS, easing: EASING };
9038
+ var EMPTY_CONTAINER_ID_SET = /* @__PURE__ */ new Set();
8933
9039
  function resizeCursorFromHandle(handle) {
8934
9040
  switch (handle) {
8935
9041
  case "n":
@@ -9217,13 +9323,9 @@ function DesignModeOverlay() {
9217
9323
  const sensors = useSensors(
9218
9324
  useSensor(PointerSensor, { activationConstraint: { distance: 8 } })
9219
9325
  );
9220
- const collisionDetection = (0, import_react16.useCallback)(
9221
- (args) => createDesignModeCollisionDetection(refs.blockedContainerIdsRef.current)(args),
9222
- []
9223
- // eslint-disable-line react-hooks/exhaustive-deps -- refs are stable
9224
- );
9225
9326
  const [insideRect, setInsideRect] = (0, import_react16.useState)(null);
9226
9327
  const [dropLine, setDropLine] = (0, import_react16.useState)(null);
9328
+ const [activeLaneContainerId, setActiveLaneContainerId] = (0, import_react16.useState)(null);
9227
9329
  (0, import_react16.useEffect)(() => {
9228
9330
  if (!enabled) return;
9229
9331
  const fn = (e) => {
@@ -9259,8 +9361,55 @@ function DesignModeOverlay() {
9259
9361
  syncHistoryAvailability,
9260
9362
  resolveNodeElement,
9261
9363
  setInsideRect,
9262
- setDropLine
9364
+ setDropLine,
9365
+ setActiveLaneContainerId
9263
9366
  });
9367
+ const dropCandidateContainerIds = (0, import_react16.useMemo)(
9368
+ () => activeDrag ? refs.engineRef.current?.getValidDropContainerIds() ?? null : null,
9369
+ [activeDrag]
9370
+ // eslint-disable-line react-hooks/exhaustive-deps -- refs are stable
9371
+ );
9372
+ const laneKinshipContainerIds = (0, import_react16.useMemo)(() => {
9373
+ if (!activeDrag || !dropCandidateContainerIds) return null;
9374
+ const engine = refs.engineRef.current;
9375
+ if (!engine) return null;
9376
+ return computeLaneKinshipContainerIds({
9377
+ candidateContainerIds: dropCandidateContainerIds,
9378
+ activeContainerId: activeLaneContainerId,
9379
+ draggingNodeId: activeDrag.nodeId,
9380
+ getParentContainerId: (id) => engine.getNodeParentContainerId(id),
9381
+ getContainerChildren: (id) => engine.getContainerChildren(id)
9382
+ });
9383
+ }, [activeDrag, activeLaneContainerId, dropCandidateContainerIds]);
9384
+ const visibleCandidateContainerIds = (0, import_react16.useMemo)(() => {
9385
+ if (!laneKinshipContainerIds) return null;
9386
+ const rects = refs.snapshotRef.current?.rects;
9387
+ if (!rects) return laneKinshipContainerIds;
9388
+ const vw = window.innerWidth;
9389
+ const vh = window.innerHeight;
9390
+ const visible = /* @__PURE__ */ new Set();
9391
+ for (const id of laneKinshipContainerIds) {
9392
+ const r = rects.get(id);
9393
+ if (!r) continue;
9394
+ if (r.right < 0 || r.bottom < 0 || r.left > vw || r.top > vh) continue;
9395
+ visible.add(id);
9396
+ }
9397
+ return visible;
9398
+ }, [laneKinshipContainerIds]);
9399
+ const dragGuide = (0, import_react16.useMemo)(() => {
9400
+ if (!activeDrag) return null;
9401
+ const snapshot = refs.snapshotRef.current;
9402
+ if (!snapshot) return null;
9403
+ const activeChildIds = activeLaneContainerId ? refs.engineRef.current?.getContainerChildren(activeLaneContainerId) ?? [] : [];
9404
+ return {
9405
+ rects: snapshot.rects,
9406
+ startScroll: snapshot.scroll,
9407
+ candidateContainerIds: visibleCandidateContainerIds ?? EMPTY_CONTAINER_ID_SET,
9408
+ activeContainerId: activeLaneContainerId,
9409
+ activeChildIds,
9410
+ draggingNodeId: activeDrag.nodeId
9411
+ };
9412
+ }, [activeDrag, activeLaneContainerId, visibleCandidateContainerIds]);
9264
9413
  (0, import_react16.useEffect)(() => {
9265
9414
  if (!enabled || activeDrag) return;
9266
9415
  if (undoRequestId === refs.processedUndoIdRef.current) return;
@@ -9323,7 +9472,7 @@ function DesignModeOverlay() {
9323
9472
  safeZoneRef: refs.safeZoneRef,
9324
9473
  autoScrollStateRef: refs.autoScrollStateRef
9325
9474
  });
9326
- useDragVisualLoop({ activeDrag, refs, getCommitLaneSnapshot, setInsideRect, setDropLine });
9475
+ useDragVisualLoop({ activeDrag, refs, getCommitLaneSnapshot, setInsideRect, setDropLine, setActiveLaneContainerId });
9327
9476
  useExternalDragLoop({
9328
9477
  externalDrag,
9329
9478
  refs,
@@ -9343,7 +9492,6 @@ function DesignModeOverlay() {
9343
9492
  DndContext,
9344
9493
  {
9345
9494
  sensors,
9346
- collisionDetection,
9347
9495
  autoScroll: false,
9348
9496
  onDragStart: handleDragStart,
9349
9497
  onDragEnd: handleDragEnd,
@@ -9352,10 +9500,8 @@ function DesignModeOverlay() {
9352
9500
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
9353
9501
  ElementHighlight,
9354
9502
  {
9355
- elements,
9356
- draggingNodeId: activeDrag?.nodeId ?? null,
9357
- activeEl: refs.activeElRef.current,
9358
- arrangeSelectedNodeId,
9503
+ selected: arrangeSelectedNodeId ? nodeMap.get(arrangeSelectedNodeId) ?? null : null,
9504
+ dragGuide,
9359
9505
  externalDragActive: externalDrag !== null,
9360
9506
  onResizeHandlePointerDown: handleResizeHandlePointerDown
9361
9507
  }