@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.
@@ -581,44 +581,6 @@ function getFirstCollision(collisions, property) {
581
581
  const [firstCollision] = collisions;
582
582
  return property ? firstCollision[property] : firstCollision;
583
583
  }
584
- function centerOfRectangle(rect, left, top) {
585
- if (left === void 0) {
586
- left = rect.left;
587
- }
588
- if (top === void 0) {
589
- top = rect.top;
590
- }
591
- return {
592
- x: left + rect.width * 0.5,
593
- y: top + rect.height * 0.5
594
- };
595
- }
596
- var closestCenter = (_ref) => {
597
- let {
598
- collisionRect,
599
- droppableRects,
600
- droppableContainers
601
- } = _ref;
602
- const centerRect = centerOfRectangle(collisionRect, collisionRect.left, collisionRect.top);
603
- const collisions = [];
604
- for (const droppableContainer of droppableContainers) {
605
- const {
606
- id
607
- } = droppableContainer;
608
- const rect = droppableRects.get(id);
609
- if (rect) {
610
- const distBetween = distanceBetween(centerOfRectangle(rect), centerRect);
611
- collisions.push({
612
- id,
613
- data: {
614
- droppableContainer,
615
- value: distBetween
616
- }
617
- });
618
- }
619
- }
620
- return collisions.sort(sortCollisionsAsc);
621
- };
622
584
  var closestCorners = (_ref) => {
623
585
  let {
624
586
  collisionRect,
@@ -5225,9 +5187,12 @@ function ensureUniqueNodeId(base, used) {
5225
5187
  return `${base}::${idx}`;
5226
5188
  }
5227
5189
  var INSERT_NODE_ID_PREFIX = "insert:";
5190
+ var INSERT_CHILD_NODE_ID_PREFIX = "insert-child:";
5228
5191
  function resolveStableNodeId(el, used) {
5229
5192
  const persisted = el.getAttribute(DND_NODE_ID_ATTR);
5230
- if (persisted && persisted.startsWith(INSERT_NODE_ID_PREFIX)) return persisted;
5193
+ if (persisted && (persisted.startsWith(INSERT_NODE_ID_PREFIX) || persisted.startsWith(INSERT_CHILD_NODE_ID_PREFIX))) {
5194
+ return persisted;
5195
+ }
5231
5196
  if (persisted && !used.has(persisted)) return persisted;
5232
5197
  const projected = projectUsageMeta(el);
5233
5198
  if (projected) return ensureUniqueNodeId(projected.nodeId, used);
@@ -5544,9 +5509,29 @@ function useElementScanner(enabled) {
5544
5509
  // src/client/dnd/design-mode/ElementHighlight.tsx
5545
5510
  import { useRef as useRef5, useEffect as useEffect5, useCallback as useCallback5 } from "react";
5546
5511
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
5512
+ function computeLaneKinshipContainerIds({
5513
+ candidateContainerIds,
5514
+ activeContainerId,
5515
+ draggingNodeId,
5516
+ getParentContainerId,
5517
+ getContainerChildren: getContainerChildren2
5518
+ }) {
5519
+ const anchorId = activeContainerId ?? getParentContainerId(draggingNodeId);
5520
+ if (!anchorId) return /* @__PURE__ */ new Set();
5521
+ const parentId = getParentContainerId(anchorId);
5522
+ const kin = /* @__PURE__ */ new Set();
5523
+ if (!parentId) return kin;
5524
+ if (candidateContainerIds.has(parentId)) kin.add(parentId);
5525
+ for (const siblingId of getContainerChildren2(parentId)) {
5526
+ if (siblingId === anchorId) continue;
5527
+ if (candidateContainerIds.has(siblingId)) kin.add(siblingId);
5528
+ }
5529
+ return kin;
5530
+ }
5547
5531
  var HANDLES = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
5548
5532
  var HANDLE_HIT_SIZE = 24;
5549
5533
  var HANDLE_KNOB_SIZE = 8;
5534
+ var EMPTY_SCROLL_DELTA = { x: NaN, y: NaN };
5550
5535
  var OVERLAY_ROOT_STYLE = {
5551
5536
  position: "fixed",
5552
5537
  inset: 0,
@@ -5620,16 +5605,38 @@ function handlePosition(dir) {
5620
5605
  };
5621
5606
  return positions[dir];
5622
5607
  }
5623
- function getOutlineStyle(rect, isSelected, inspectorTheme) {
5608
+ function getSelectedOutlineStyle(rect, inspectorTheme) {
5624
5609
  return {
5625
5610
  ...OUTLINE_BASE_STYLE,
5626
5611
  left: rect.left,
5627
5612
  top: rect.top,
5628
5613
  width: rect.width,
5629
5614
  height: rect.height,
5630
- border: isSelected ? `2px solid ${inspectorTheme.accent}` : `1px dashed ${inspectorTheme.shared}`,
5631
- backgroundColor: isSelected ? inspectorTheme.accentSoft : "transparent"
5615
+ border: `2px solid ${inspectorTheme.accent}`,
5616
+ backgroundColor: inspectorTheme.accentSoft
5617
+ };
5618
+ }
5619
+ function getGuideOutlineStyle(rect, tier, inspectorTheme) {
5620
+ const base = {
5621
+ ...OUTLINE_BASE_STYLE,
5622
+ left: rect.left,
5623
+ top: rect.top,
5624
+ width: rect.width,
5625
+ height: rect.height
5632
5626
  };
5627
+ switch (tier) {
5628
+ case "candidate":
5629
+ return { ...base, border: `1px solid ${inspectorTheme.accent}`, opacity: 0.2 };
5630
+ case "lane-child":
5631
+ return { ...base, border: `1px dashed ${inspectorTheme.accent}`, opacity: 0.45 };
5632
+ case "lane":
5633
+ return {
5634
+ ...base,
5635
+ border: `1.5px solid ${inspectorTheme.accent}`,
5636
+ backgroundColor: inspectorTheme.accentSoft,
5637
+ opacity: 0.75
5638
+ };
5639
+ }
5633
5640
  }
5634
5641
  function getResizeHandleStyle(position) {
5635
5642
  return {
@@ -5653,40 +5660,58 @@ function getLabelStyle(labelLeft, labelTop, labelColor) {
5653
5660
  backgroundColor: labelColor
5654
5661
  };
5655
5662
  }
5663
+ function projectDragRect(dragGuide, nodeId, sdx, sdy) {
5664
+ const r = dragGuide.rects.get(nodeId);
5665
+ if (!r) return null;
5666
+ return { left: r.left - sdx, top: r.top - sdy, width: r.width, height: r.height };
5667
+ }
5656
5668
  function ElementHighlight({
5657
- elements,
5658
- draggingNodeId,
5659
- activeEl,
5660
- arrangeSelectedNodeId,
5669
+ selected,
5670
+ dragGuide,
5661
5671
  externalDragActive,
5662
5672
  onResizeHandlePointerDown
5663
5673
  }) {
5664
5674
  const inspectorTheme = useDesignModeStore((s) => s.inspectorTheme);
5665
- const internalDragInProgress = draggingNodeId !== null;
5666
- const overlayVisible = arrangeSelectedNodeId !== null || internalDragInProgress;
5667
5675
  const containerRef = useRef5(null);
5668
5676
  const rafRef = useRef5(0);
5677
+ const lastScrollDeltaRef = useRef5(EMPTY_SCROLL_DELTA);
5669
5678
  const syncPositions = useCallback5(() => {
5670
5679
  const container = containerRef.current;
5671
5680
  if (!container) return;
5672
- const outlines = container.querySelectorAll("[data-outline-for]");
5673
- for (const outline of outlines) {
5674
- const nodeId = outline.getAttribute("data-outline-for");
5675
- const el = elements.find((e) => e.nodeId === nodeId);
5676
- if (!el) continue;
5677
- const rect = el.element.getBoundingClientRect();
5678
- outline.style.left = `${rect.left}px`;
5679
- outline.style.top = `${rect.top}px`;
5680
- outline.style.width = `${rect.width}px`;
5681
- outline.style.height = `${rect.height}px`;
5682
- const label = outline.nextElementSibling;
5683
- if (label) {
5684
- label.style.left = `${rect.left}px`;
5685
- label.style.top = `${rect.top}px`;
5681
+ if (dragGuide) {
5682
+ const sdx = window.scrollX - dragGuide.startScroll.x;
5683
+ const sdy = window.scrollY - dragGuide.startScroll.y;
5684
+ if (sdx === lastScrollDeltaRef.current.x && sdy === lastScrollDeltaRef.current.y) return;
5685
+ lastScrollDeltaRef.current = { x: sdx, y: sdy };
5686
+ const outlines = container.querySelectorAll("[data-guide-for]");
5687
+ for (const outline2 of outlines) {
5688
+ const nodeId = outline2.getAttribute("data-guide-for");
5689
+ const r = dragGuide.rects.get(nodeId);
5690
+ if (!r) continue;
5691
+ outline2.style.left = `${r.left - sdx}px`;
5692
+ outline2.style.top = `${r.top - sdy}px`;
5686
5693
  }
5694
+ return;
5687
5695
  }
5688
- }, [elements]);
5696
+ if (!selected) return;
5697
+ const rect2 = selected.element.getBoundingClientRect();
5698
+ const outline = container.querySelector("[data-outline-for]");
5699
+ if (outline) {
5700
+ outline.style.left = `${rect2.left}px`;
5701
+ outline.style.top = `${rect2.top}px`;
5702
+ outline.style.width = `${rect2.width}px`;
5703
+ outline.style.height = `${rect2.height}px`;
5704
+ }
5705
+ const label = container.querySelector("[data-drag-handle]");
5706
+ if (label) {
5707
+ label.style.left = `${rect2.left + 12}px`;
5708
+ label.style.top = `${Math.max(0, rect2.top - 20)}px`;
5709
+ }
5710
+ }, [dragGuide, selected]);
5711
+ const loopActive = !externalDragActive && (dragGuide !== null || selected !== null);
5689
5712
  useEffect5(() => {
5713
+ if (!loopActive) return;
5714
+ lastScrollDeltaRef.current = EMPTY_SCROLL_DELTA;
5690
5715
  let running = true;
5691
5716
  const loop = () => {
5692
5717
  if (!running) return;
@@ -5698,81 +5723,113 @@ function ElementHighlight({
5698
5723
  running = false;
5699
5724
  cancelAnimationFrame(rafRef.current);
5700
5725
  };
5701
- }, [syncPositions]);
5702
- if (!overlayVisible) return null;
5703
- if (externalDragActive) return null;
5704
- return /* @__PURE__ */ jsx2(
5705
- "div",
5706
- {
5707
- ref: containerRef,
5708
- "data-design-mode-ui": true,
5709
- style: OVERLAY_ROOT_STYLE,
5710
- children: elements.map((el) => {
5711
- if (el.nodeId === draggingNodeId) return null;
5712
- if (activeEl && activeEl !== el.element && activeEl.contains(el.element)) return null;
5713
- const isSelected = el.nodeId === arrangeSelectedNodeId;
5714
- if (!isSelected && !internalDragInProgress) return null;
5715
- const rect = el.element.getBoundingClientRect();
5716
- const showLabel = isSelected;
5717
- const labelTop = isSelected ? Math.max(0, rect.top - 20) : rect.top;
5718
- const labelLeft = isSelected ? rect.left + 12 : rect.left;
5719
- return /* @__PURE__ */ jsxs("div", { children: [
5720
- /* @__PURE__ */ jsx2(
5721
- "div",
5722
- {
5723
- "data-outline-for": el.nodeId,
5724
- "data-design-mode-ui": true,
5725
- style: getOutlineStyle(rect, isSelected, inspectorTheme),
5726
- children: isSelected && el.draggable && HANDLES.map((dir) => {
5727
- const pos = handlePosition(dir);
5728
- return /* @__PURE__ */ jsx2(
5729
- "div",
5730
- {
5731
- "data-design-mode-ui": true,
5732
- "data-resize-handle": dir,
5733
- onPointerDown: (e) => {
5734
- e.stopPropagation();
5735
- onResizeHandlePointerDown?.(e, el.nodeId, dir);
5736
- },
5737
- style: getResizeHandleStyle(pos),
5738
- children: /* @__PURE__ */ jsx2(
5739
- "span",
5740
- {
5741
- "data-design-mode-ui": true,
5742
- style: getResizeKnobStyle(inspectorTheme.accent)
5743
- }
5744
- )
5745
- },
5746
- dir
5747
- );
5748
- })
5749
- }
5750
- ),
5751
- showLabel && /* @__PURE__ */ jsxs(
5726
+ }, [loopActive, syncPositions]);
5727
+ if (!loopActive) return null;
5728
+ if (dragGuide) {
5729
+ const sdx = window.scrollX - dragGuide.startScroll.x;
5730
+ const sdy = window.scrollY - dragGuide.startScroll.y;
5731
+ const { candidateContainerIds, activeContainerId, activeChildIds, draggingNodeId } = dragGuide;
5732
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, "data-design-mode-ui": true, style: OVERLAY_ROOT_STYLE, children: [
5733
+ [...candidateContainerIds].map((id) => {
5734
+ if (id === activeContainerId || id === draggingNodeId) return null;
5735
+ const rect2 = projectDragRect(dragGuide, id, sdx, sdy);
5736
+ if (!rect2) return null;
5737
+ return /* @__PURE__ */ jsx2(
5738
+ "div",
5739
+ {
5740
+ "data-guide-for": id,
5741
+ "data-guide-tier": "candidate",
5742
+ "data-design-mode-ui": true,
5743
+ style: getGuideOutlineStyle(rect2, "candidate", inspectorTheme)
5744
+ },
5745
+ `candidate:${id}`
5746
+ );
5747
+ }),
5748
+ activeContainerId !== null && activeChildIds.map((id) => {
5749
+ if (id === draggingNodeId) return null;
5750
+ const rect2 = projectDragRect(dragGuide, id, sdx, sdy);
5751
+ if (!rect2) return null;
5752
+ return /* @__PURE__ */ jsx2(
5753
+ "div",
5754
+ {
5755
+ "data-guide-for": id,
5756
+ "data-guide-tier": "lane-child",
5757
+ "data-design-mode-ui": true,
5758
+ style: getGuideOutlineStyle(rect2, "lane-child", inspectorTheme)
5759
+ },
5760
+ `lane-child:${id}`
5761
+ );
5762
+ }),
5763
+ activeContainerId !== null && activeContainerId !== draggingNodeId && (() => {
5764
+ const rect2 = projectDragRect(dragGuide, activeContainerId, sdx, sdy);
5765
+ if (!rect2) return null;
5766
+ return /* @__PURE__ */ jsx2(
5767
+ "div",
5768
+ {
5769
+ "data-guide-for": activeContainerId,
5770
+ "data-guide-tier": "lane",
5771
+ "data-design-mode-ui": true,
5772
+ style: getGuideOutlineStyle(rect2, "lane", inspectorTheme)
5773
+ },
5774
+ `lane:${activeContainerId}`
5775
+ );
5776
+ })()
5777
+ ] });
5778
+ }
5779
+ if (!selected) return null;
5780
+ const rect = selected.element.getBoundingClientRect();
5781
+ const labelTop = Math.max(0, rect.top - 20);
5782
+ const labelLeft = rect.left + 12;
5783
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, "data-design-mode-ui": true, style: OVERLAY_ROOT_STYLE, children: [
5784
+ /* @__PURE__ */ jsx2(
5785
+ "div",
5786
+ {
5787
+ "data-outline-for": selected.nodeId,
5788
+ "data-design-mode-ui": true,
5789
+ style: getSelectedOutlineStyle(rect, inspectorTheme),
5790
+ children: selected.draggable && HANDLES.map((dir) => {
5791
+ const pos = handlePosition(dir);
5792
+ return /* @__PURE__ */ jsx2(
5752
5793
  "div",
5753
5794
  {
5754
5795
  "data-design-mode-ui": true,
5755
- "data-drag-handle": el.nodeId,
5756
- style: getLabelStyle(
5757
- labelLeft,
5758
- labelTop,
5759
- isSelected ? inspectorTheme.accent : inspectorTheme.hover
5760
- ),
5761
- children: [
5762
- /* @__PURE__ */ jsx2("span", { style: LABEL_ICON_STYLE, children: "\u283F" }),
5763
- el.label,
5764
- isSelected && /* @__PURE__ */ jsxs("span", { style: LABEL_DIMENSIONS_STYLE, children: [
5765
- Math.round(rect.width),
5766
- "\xD7",
5767
- Math.round(rect.height)
5768
- ] })
5769
- ]
5770
- }
5771
- )
5772
- ] }, el.nodeId);
5773
- })
5774
- }
5775
- );
5796
+ "data-resize-handle": dir,
5797
+ onPointerDown: (e) => {
5798
+ e.stopPropagation();
5799
+ onResizeHandlePointerDown?.(e, selected.nodeId, dir);
5800
+ },
5801
+ style: getResizeHandleStyle(pos),
5802
+ children: /* @__PURE__ */ jsx2(
5803
+ "span",
5804
+ {
5805
+ "data-design-mode-ui": true,
5806
+ style: getResizeKnobStyle(inspectorTheme.accent)
5807
+ }
5808
+ )
5809
+ },
5810
+ dir
5811
+ );
5812
+ })
5813
+ }
5814
+ ),
5815
+ /* @__PURE__ */ jsxs(
5816
+ "div",
5817
+ {
5818
+ "data-design-mode-ui": true,
5819
+ "data-drag-handle": selected.nodeId,
5820
+ style: getLabelStyle(labelLeft, labelTop, inspectorTheme.accent),
5821
+ children: [
5822
+ /* @__PURE__ */ jsx2("span", { style: LABEL_ICON_STYLE, children: "\u283F" }),
5823
+ selected.label,
5824
+ /* @__PURE__ */ jsxs("span", { style: LABEL_DIMENSIONS_STYLE, children: [
5825
+ Math.round(rect.width),
5826
+ "\xD7",
5827
+ Math.round(rect.height)
5828
+ ] })
5829
+ ]
5830
+ }
5831
+ )
5832
+ ] });
5776
5833
  }
5777
5834
 
5778
5835
  // src/client/dnd/design-mode/projection.ts
@@ -6058,6 +6115,15 @@ var NON_DRAGGABLE_DESCENDANT_TAGS = /* @__PURE__ */ new Set([
6058
6115
  "source",
6059
6116
  "track"
6060
6117
  ]);
6118
+ var ARCHIE_INSERTED_ATTR = "data-archie-inserted";
6119
+ var INSERT_CHILD_NODE_ID_PREFIX2 = "insert-child:";
6120
+ function sealDescendantNodeId(node) {
6121
+ if (node.hasAttribute("data-dnd-node-id")) return;
6122
+ const marker = node.getAttribute(ARCHIE_INSERTED_ATTR);
6123
+ if (!marker) return;
6124
+ if (marker.includes('"') || marker.includes("'")) return;
6125
+ node.setAttribute("data-dnd-node-id", `${INSERT_CHILD_NODE_ID_PREFIX2}${marker}`);
6126
+ }
6061
6127
  function markInsertedDescendants(root) {
6062
6128
  const descendants = root.querySelectorAll("*");
6063
6129
  for (const node of descendants) {
@@ -6065,6 +6131,7 @@ function markInsertedDescendants(root) {
6065
6131
  if (NON_DRAGGABLE_DESCENDANT_TAGS.has(tag)) continue;
6066
6132
  node.setAttribute("data-dnd-force-allow", "");
6067
6133
  node.setAttribute("data-dnd-moved", "");
6134
+ sealDescendantNodeId(node);
6068
6135
  }
6069
6136
  }
6070
6137
 
@@ -6566,30 +6633,6 @@ function applyDndCommandPlacement(command, direction, resolveNodeElement) {
6566
6633
  return true;
6567
6634
  }
6568
6635
 
6569
- // src/client/dnd/design-mode/design-mode-collision.ts
6570
- function filterDroppables(droppableContainers, blockedContainerIds) {
6571
- if (!blockedContainerIds || blockedContainerIds.size === 0) return droppableContainers;
6572
- return droppableContainers.filter((container) => !blockedContainerIds.has(String(container.id)));
6573
- }
6574
- function getCollisions(detector, args) {
6575
- return detector(args);
6576
- }
6577
- function createDesignModeCollisionDetection(blockedContainerIds) {
6578
- return (args) => {
6579
- const droppableContainers = filterDroppables(args.droppableContainers, blockedContainerIds);
6580
- if (droppableContainers.length === 0) return [];
6581
- const scopedArgs = {
6582
- ...args,
6583
- droppableContainers
6584
- };
6585
- const pointerHits = getCollisions(pointerWithin, scopedArgs);
6586
- if (pointerHits.length > 0) return pointerHits;
6587
- const cornerHits = getCollisions(closestCorners, scopedArgs);
6588
- if (cornerHits.length > 0) return cornerHits;
6589
- return getCollisions(closestCenter, scopedArgs);
6590
- };
6591
- }
6592
-
6593
6636
  // src/client/dnd/design-mode/arrange-session.ts
6594
6637
  function serializeArrangeChange(event, index) {
6595
6638
  return {
@@ -6868,9 +6911,9 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6868
6911
  const lastIntentRef = useRef8(null);
6869
6912
  const visualIntentRef = useRef8(null);
6870
6913
  const sourceContainerIdRef = useRef8(null);
6914
+ const activeLaneContainerIdRef = useRef8(null);
6871
6915
  const rowLockedRef = useRef8(false);
6872
6916
  const dragSessionAnchorRef = useRef8(null);
6873
- const lockRef = useRef8({ containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null });
6874
6917
  const targetPlaneRef = useRef8("lane");
6875
6918
  const nestCandidateRef = useRef8(null);
6876
6919
  const nestTargetRef = useRef8(null);
@@ -6880,7 +6923,6 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6880
6923
  const sourceCommitLaneRef = useRef8(null);
6881
6924
  const commitLaneCacheRef = useRef8(/* @__PURE__ */ new Map());
6882
6925
  const safeZoneRef = useRef8(null);
6883
- const blockedContainerIdsRef = useRef8(/* @__PURE__ */ new Set());
6884
6926
  const historyRef = useRef8(new DndCommandHistory());
6885
6927
  const processedUndoIdRef = useRef8(undoRequestId);
6886
6928
  const processedRedoIdRef = useRef8(redoRequestId);
@@ -6900,9 +6942,9 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6900
6942
  lastIntentRef,
6901
6943
  visualIntentRef,
6902
6944
  sourceContainerIdRef,
6945
+ activeLaneContainerIdRef,
6903
6946
  rowLockedRef,
6904
6947
  dragSessionAnchorRef,
6905
- lockRef,
6906
6948
  targetPlaneRef,
6907
6949
  nestCandidateRef,
6908
6950
  nestTargetRef,
@@ -6912,7 +6954,6 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6912
6954
  sourceCommitLaneRef,
6913
6955
  commitLaneCacheRef,
6914
6956
  safeZoneRef,
6915
- blockedContainerIdsRef,
6916
6957
  historyRef,
6917
6958
  processedUndoIdRef,
6918
6959
  processedRedoIdRef,
@@ -7180,6 +7221,11 @@ function pointInRect(px, py, r) {
7180
7221
  function pointInExpandedRect(px, py, r, inset) {
7181
7222
  return px >= r.left - inset && px <= r.right + inset && py >= r.top - inset && py <= r.bottom + inset;
7182
7223
  }
7224
+ function isInNestZone(px, py, rect) {
7225
+ const relX = (px - rect.left) / rect.width;
7226
+ const relY = (py - rect.top) / rect.height;
7227
+ return relX > NEST_EDGE_FRACTION && relX < 1 - NEST_EDGE_FRACTION && relY > NEST_EDGE_FRACTION && relY < 1 - NEST_EDGE_FRACTION;
7228
+ }
7183
7229
  function buildSlotQuadtree(slotIndex) {
7184
7230
  const rects = /* @__PURE__ */ new Map();
7185
7231
  for (const [id, slot] of slotIndex) {
@@ -7222,6 +7268,11 @@ var DragIntentEngine = class {
7222
7268
  __publicField(this, "laneAllowedContainers");
7223
7269
  __publicField(this, "blockedContainerIds");
7224
7270
  __publicField(this, "targetPlane", "lane");
7271
+ // Memoized result of getValidDropContainerIds(). Safe to cache because every
7272
+ // input it reads (containerIndex, slotIndex, activeId, laneAllowedContainers,
7273
+ // blockedContainerIds) is fixed in the constructor and never mutated during
7274
+ // a drag.
7275
+ __publicField(this, "validDropContainerIds", null);
7225
7276
  this.projectionTree = projection.projectionTree;
7226
7277
  this.containerIndex = projection.containerIndex;
7227
7278
  this.slotIndex = projection.slotIndex;
@@ -7258,6 +7309,44 @@ var DragIntentEngine = class {
7258
7309
  getContainerChildren(containerId) {
7259
7310
  return this.containerIndex.get(containerId)?.children ?? [];
7260
7311
  }
7312
+ /**
7313
+ * Every container id this engine would accept as a drop target for the
7314
+ * active node, at the current pointer position or any other — i.e. the
7315
+ * full set `resolvePointerContainer` draws from, not a snapshot of one
7316
+ * resolve() call. Intended for the visual layer to draw a guide that
7317
+ * matches exactly what the engine can land on, without re-deriving the
7318
+ * eligibility rule itself.
7319
+ *
7320
+ * Always queries with `laneLock: true`. That is not an approximation of
7321
+ * the "real" default — it IS the real answer for every reachable caller:
7322
+ *
7323
+ * - With a row lock active (`laneAllowedContainers` set), `isLaneContainer`
7324
+ * restricts eligibility to that set, so this returns exactly
7325
+ * `{srcContainerId}` — the ARCH-1071 lock, reproduced faithfully.
7326
+ * - Without a row lock, `laneAllowedContainers` is null and
7327
+ * `isLaneContainer` returns true unconditionally, so `laneLock`'s value
7328
+ * makes no difference — passing `true` costs nothing here.
7329
+ * - The engine's own 'nest' plane does call `isEligibleDropContainer` with
7330
+ * `laneLock: false` (see `peekNestIntent` → `resolvePointerContainer`),
7331
+ * but that plane is unreachable for a locked drag: `computeAllowNest`
7332
+ * (useDragVisualLoop.ts) short-circuits `rowLocked` drags before
7333
+ * `setTargetPlane('nest')` is ever called, so `targetPlane` stays
7334
+ * 'lane' for the drag's whole lifetime. If that guard is ever removed,
7335
+ * this method will under-report nest-only targets for locked drags —
7336
+ * keep the two in sync.
7337
+ *
7338
+ * Cached after the first call: every input (containerIndex, slotIndex,
7339
+ * activeId, laneAllowedContainers, blockedContainerIds) is fixed at
7340
+ * construction and never changes for the life of a drag.
7341
+ */
7342
+ getValidDropContainerIds() {
7343
+ if (this.validDropContainerIds) return this.validDropContainerIds;
7344
+ const ids2 = /* @__PURE__ */ new Set();
7345
+ for (const id of this.containerIndex.keys()) {
7346
+ if (this.isEligibleDropContainer(id, true)) ids2.add(id);
7347
+ }
7348
+ return this.validDropContainerIds = ids2;
7349
+ }
7261
7350
  setTargetPlane(plane) {
7262
7351
  this.targetPlane = plane;
7263
7352
  }
@@ -7276,6 +7365,17 @@ var DragIntentEngine = class {
7276
7365
  isInvalidDropContainer(containerId) {
7277
7366
  return containerId === this.activeId || this.isContainerDescendantOf(containerId, this.activeId);
7278
7367
  }
7368
+ /**
7369
+ * The full "can this container accept the active node this drag" predicate.
7370
+ * SINGLE definition — every caller routes through it so no two parts of the
7371
+ * system can disagree about which containers are droppable.
7372
+ */
7373
+ isEligibleDropContainer(containerId, laneLock) {
7374
+ if (this.isInvalidDropContainer(containerId)) return false;
7375
+ if (this.isBlockedContainer(containerId)) return false;
7376
+ if (laneLock && !this.isLaneContainer(containerId)) return false;
7377
+ return this.hasUsableSlots(containerId);
7378
+ }
7279
7379
  /**
7280
7380
  * Resolve the drag intent from the current pointer position.
7281
7381
  *
@@ -7363,9 +7463,7 @@ var DragIntentEngine = class {
7363
7463
  const childNode = this.getNodeRect(childId);
7364
7464
  if (!childNode) continue;
7365
7465
  if (!pointInRect(px, py, childNode)) continue;
7366
- const relX = (px - childNode.left) / childNode.width;
7367
- const relY = (py - childNode.top) / childNode.height;
7368
- if (relX > NEST_EDGE_FRACTION && relX < 1 - NEST_EDGE_FRACTION && relY > NEST_EDGE_FRACTION && relY < 1 - NEST_EDGE_FRACTION) {
7466
+ if (isInNestZone(px, py, childNode)) {
7369
7467
  return {
7370
7468
  containerId: pointerContainerId,
7371
7469
  index: container.children.indexOf(childId),
@@ -7379,11 +7477,8 @@ var DragIntentEngine = class {
7379
7477
  resolvePointerContainer(px, py, laneLock) {
7380
7478
  const pointerContainers = [];
7381
7479
  for (const [id, node] of this.containerIndex) {
7382
- if (this.isInvalidDropContainer(id)) continue;
7383
- if (this.isBlockedContainer(id)) continue;
7384
- if (laneLock && !this.isLaneContainer(id)) continue;
7385
7480
  if (!pointInRect(px, py, node.rect)) continue;
7386
- if (this.hasUsableSlots(id) === false) continue;
7481
+ if (!this.isEligibleDropContainer(id, laneLock)) continue;
7387
7482
  const r = node.rect;
7388
7483
  const edgeDistX = Math.min(px - r.left, r.right - px);
7389
7484
  const edgeDistY = Math.min(py - r.top, r.bottom - py);
@@ -7400,11 +7495,11 @@ var DragIntentEngine = class {
7400
7495
  let pointerContainerId = pointerContainers[0]?.id ?? null;
7401
7496
  if (!pointerContainerId && this.lastIntent?.mode === "reorder") {
7402
7497
  const lastContainer = this.containerIndex.get(this.lastIntent.containerId);
7403
- 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)) {
7498
+ if (lastContainer && this.isEligibleDropContainer(lastContainer.id, laneLock) && pointInExpandedRect(px, py, lastContainer.rect, LAST_CONTAINER_REENTRY_SLOP_PX)) {
7404
7499
  pointerContainerId = lastContainer.id;
7405
7500
  }
7406
7501
  }
7407
- if (this.originContainerId && this.originContainerId !== pointerContainerId && !this.isInvalidDropContainer(this.originContainerId) && !this.isBlockedContainer(this.originContainerId) && (!laneLock || this.isLaneContainer(this.originContainerId)) && this.hasUsableSlots(this.originContainerId)) {
7502
+ if (this.originContainerId && this.originContainerId !== pointerContainerId && this.isEligibleDropContainer(this.originContainerId, laneLock)) {
7408
7503
  const originNode = this.containerIndex.get(this.originContainerId);
7409
7504
  const currentNode = pointerContainerId ? this.containerIndex.get(pointerContainerId) : null;
7410
7505
  const originIsDeeper = originNode && (!currentNode || originNode.depth >= currentNode.depth);
@@ -7817,7 +7912,8 @@ function useDragVisualLoop({
7817
7912
  refs,
7818
7913
  getCommitLaneSnapshot,
7819
7914
  setInsideRect,
7820
- setDropLine
7915
+ setDropLine,
7916
+ setActiveLaneContainerId
7821
7917
  }) {
7822
7918
  useEffect8(() => {
7823
7919
  const engine = refs.engineRef.current;
@@ -7825,6 +7921,11 @@ function useDragVisualLoop({
7825
7921
  if (!activeDrag || !engine || !snapshot) return;
7826
7922
  let rafId;
7827
7923
  const animator = refs.animatorRef.current;
7924
+ const publishActiveLane = (containerId) => {
7925
+ if (refs.activeLaneContainerIdRef.current === containerId) return;
7926
+ refs.activeLaneContainerIdRef.current = containerId;
7927
+ setActiveLaneContainerId(containerId);
7928
+ };
7828
7929
  const setTargetPlane = (nextPlane) => {
7829
7930
  const prevPlane = refs.targetPlaneRef.current;
7830
7931
  if (prevPlane === nextPlane) return;
@@ -7843,6 +7944,7 @@ function useDragVisualLoop({
7843
7944
  const safeZone = refs.safeZoneRef.current;
7844
7945
  if (isPointerBlocked(px, py, safeZone)) {
7845
7946
  setTargetPlane("lane");
7947
+ publishActiveLane(null);
7846
7948
  refs.lastIntentRef.current = null;
7847
7949
  refs.visualIntentRef.current = null;
7848
7950
  refs.nestCandidateRef.current = null;
@@ -7884,6 +7986,7 @@ function useDragVisualLoop({
7884
7986
  let intent = engine.resolve(px, py, window.scrollX, window.scrollY, snapshot.scroll.x, snapshot.scroll.y);
7885
7987
  if (!intent) {
7886
7988
  setTargetPlane("lane");
7989
+ publishActiveLane(null);
7887
7990
  refs.lastIntentRef.current = null;
7888
7991
  refs.visualIntentRef.current = null;
7889
7992
  refs.nestCandidateRef.current = null;
@@ -7909,6 +8012,7 @@ function useDragVisualLoop({
7909
8012
  const targetId = intent.slotId.split("::nest::")[1] ?? "";
7910
8013
  if (!targetId) {
7911
8014
  setTargetPlane("lane");
8015
+ publishActiveLane(null);
7912
8016
  rafId = requestAnimationFrame(tick);
7913
8017
  return;
7914
8018
  }
@@ -7930,12 +8034,14 @@ function useDragVisualLoop({
7930
8034
  };
7931
8035
  refs.visualIntentRef.current = intent;
7932
8036
  refs.lastIntentRef.current = intent;
8037
+ publishActiveLane(intent.containerId);
7933
8038
  } else {
7934
8039
  setTargetPlane("lane");
7935
8040
  refs.nestCandidateRef.current = null;
7936
8041
  refs.nestTargetRef.current = null;
7937
8042
  refs.lastIntentRef.current = intent;
7938
8043
  refs.visualIntentRef.current = intent;
8044
+ publishActiveLane(intent.containerId);
7939
8045
  setInsideRect(null);
7940
8046
  const proj = refs.projectionRef.current;
7941
8047
  const slot = proj.slotIndex.get(intent.slotId);
@@ -8242,7 +8348,8 @@ function useDragLifecycle({
8242
8348
  syncHistoryAvailability,
8243
8349
  resolveNodeElement,
8244
8350
  setInsideRect,
8245
- setDropLine
8351
+ setDropLine,
8352
+ setActiveLaneContainerId
8246
8353
  }) {
8247
8354
  const [activeDrag, setActiveDrag] = useState5(null);
8248
8355
  const clearDragState = useCallback6(() => {
@@ -8253,13 +8360,12 @@ function useDragLifecycle({
8253
8360
  refs.snapshotRef.current = null;
8254
8361
  refs.projectionRef.current = EMPTY_PROJECTION;
8255
8362
  refs.engineRef.current = null;
8256
- refs.blockedContainerIdsRef.current = /* @__PURE__ */ new Set();
8257
8363
  refs.lastIntentRef.current = null;
8258
8364
  refs.visualIntentRef.current = null;
8259
8365
  refs.sourceContainerIdRef.current = null;
8366
+ refs.activeLaneContainerIdRef.current = null;
8260
8367
  refs.rowLockedRef.current = false;
8261
8368
  refs.dragSessionAnchorRef.current = null;
8262
- refs.lockRef.current = { containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null };
8263
8369
  refs.targetPlaneRef.current = "lane";
8264
8370
  refs.nestCandidateRef.current = null;
8265
8371
  refs.nestTargetRef.current = null;
@@ -8270,6 +8376,7 @@ function useDragLifecycle({
8270
8376
  refs.safeZoneRef.current = null;
8271
8377
  setInsideRect(null);
8272
8378
  setDropLine(null);
8379
+ setActiveLaneContainerId(null);
8273
8380
  setActiveDrag(null);
8274
8381
  resume();
8275
8382
  }, [clearCommitLaneCache, resume]);
@@ -8327,7 +8434,6 @@ function useDragLifecycle({
8327
8434
  const sourceCommitLane = getCommitLaneSnapshot(srcContainerId);
8328
8435
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
8329
8436
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
8330
- refs.blockedContainerIdsRef.current = blockedContainerIds;
8331
8437
  const engineOptions = buildDragIntentEngineOptions(anchorScanned.element, srcContainerId, blockedContainerIds);
8332
8438
  refs.rowLockedRef.current = !!engineOptions.laneAllowedContainers;
8333
8439
  refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, engineOptions);
@@ -8336,7 +8442,6 @@ function useDragLifecycle({
8336
8442
  refs.sourceContainerIdRef.current = srcContainerId;
8337
8443
  refs.sourceCommitLaneRef.current = sourceCommitLane;
8338
8444
  refs.dragSessionAnchorRef.current = anchor;
8339
- refs.lockRef.current = { containerId: srcContainerId, isCrossUnlocked: true, exitSince: null, unlockUntilTs: null };
8340
8445
  refs.dragStartPositionRef.current = startPosition;
8341
8446
  refs.safeZoneRef.current = resolveSafeZoneRect();
8342
8447
  pause();
@@ -8818,7 +8923,7 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
8818
8923
  const candidate = postInsertChildren[i];
8819
8924
  const candidateNodeId = candidate.getAttribute(DND_NODE_ID_ATTR);
8820
8925
  if (!candidateNodeId) continue;
8821
- const isEarlierInsert = candidateNodeId.startsWith("insert:");
8926
+ const isEarlierInsert = candidateNodeId.startsWith("insert:") || candidateNodeId.startsWith("insert-child:");
8822
8927
  if (!isEarlierInsert && !getInspectorRefFromElement(candidate)) continue;
8823
8928
  beforeSiblingEl = candidate;
8824
8929
  beforeSiblingNodeId = candidateNodeId;
@@ -8895,6 +9000,7 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
8895
9000
  // src/client/dnd/design-mode/DesignModeOverlay.tsx
8896
9001
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
8897
9002
  var DROP_ANIMATION = { duration: DURATION_MS, easing: EASING };
9003
+ var EMPTY_CONTAINER_ID_SET = /* @__PURE__ */ new Set();
8898
9004
  function resizeCursorFromHandle(handle) {
8899
9005
  switch (handle) {
8900
9006
  case "n":
@@ -9182,13 +9288,9 @@ function DesignModeOverlay() {
9182
9288
  const sensors = useSensors(
9183
9289
  useSensor(PointerSensor, { activationConstraint: { distance: 8 } })
9184
9290
  );
9185
- const collisionDetection = useCallback8(
9186
- (args) => createDesignModeCollisionDetection(refs.blockedContainerIdsRef.current)(args),
9187
- []
9188
- // eslint-disable-line react-hooks/exhaustive-deps -- refs are stable
9189
- );
9190
9291
  const [insideRect, setInsideRect] = useState6(null);
9191
9292
  const [dropLine, setDropLine] = useState6(null);
9293
+ const [activeLaneContainerId, setActiveLaneContainerId] = useState6(null);
9192
9294
  useEffect11(() => {
9193
9295
  if (!enabled) return;
9194
9296
  const fn = (e) => {
@@ -9224,8 +9326,55 @@ function DesignModeOverlay() {
9224
9326
  syncHistoryAvailability,
9225
9327
  resolveNodeElement,
9226
9328
  setInsideRect,
9227
- setDropLine
9329
+ setDropLine,
9330
+ setActiveLaneContainerId
9228
9331
  });
9332
+ const dropCandidateContainerIds = useMemo6(
9333
+ () => activeDrag ? refs.engineRef.current?.getValidDropContainerIds() ?? null : null,
9334
+ [activeDrag]
9335
+ // eslint-disable-line react-hooks/exhaustive-deps -- refs are stable
9336
+ );
9337
+ const laneKinshipContainerIds = useMemo6(() => {
9338
+ if (!activeDrag || !dropCandidateContainerIds) return null;
9339
+ const engine = refs.engineRef.current;
9340
+ if (!engine) return null;
9341
+ return computeLaneKinshipContainerIds({
9342
+ candidateContainerIds: dropCandidateContainerIds,
9343
+ activeContainerId: activeLaneContainerId,
9344
+ draggingNodeId: activeDrag.nodeId,
9345
+ getParentContainerId: (id) => engine.getNodeParentContainerId(id),
9346
+ getContainerChildren: (id) => engine.getContainerChildren(id)
9347
+ });
9348
+ }, [activeDrag, activeLaneContainerId, dropCandidateContainerIds]);
9349
+ const visibleCandidateContainerIds = useMemo6(() => {
9350
+ if (!laneKinshipContainerIds) return null;
9351
+ const rects = refs.snapshotRef.current?.rects;
9352
+ if (!rects) return laneKinshipContainerIds;
9353
+ const vw = window.innerWidth;
9354
+ const vh = window.innerHeight;
9355
+ const visible = /* @__PURE__ */ new Set();
9356
+ for (const id of laneKinshipContainerIds) {
9357
+ const r = rects.get(id);
9358
+ if (!r) continue;
9359
+ if (r.right < 0 || r.bottom < 0 || r.left > vw || r.top > vh) continue;
9360
+ visible.add(id);
9361
+ }
9362
+ return visible;
9363
+ }, [laneKinshipContainerIds]);
9364
+ const dragGuide = useMemo6(() => {
9365
+ if (!activeDrag) return null;
9366
+ const snapshot = refs.snapshotRef.current;
9367
+ if (!snapshot) return null;
9368
+ const activeChildIds = activeLaneContainerId ? refs.engineRef.current?.getContainerChildren(activeLaneContainerId) ?? [] : [];
9369
+ return {
9370
+ rects: snapshot.rects,
9371
+ startScroll: snapshot.scroll,
9372
+ candidateContainerIds: visibleCandidateContainerIds ?? EMPTY_CONTAINER_ID_SET,
9373
+ activeContainerId: activeLaneContainerId,
9374
+ activeChildIds,
9375
+ draggingNodeId: activeDrag.nodeId
9376
+ };
9377
+ }, [activeDrag, activeLaneContainerId, visibleCandidateContainerIds]);
9229
9378
  useEffect11(() => {
9230
9379
  if (!enabled || activeDrag) return;
9231
9380
  if (undoRequestId === refs.processedUndoIdRef.current) return;
@@ -9288,7 +9437,7 @@ function DesignModeOverlay() {
9288
9437
  safeZoneRef: refs.safeZoneRef,
9289
9438
  autoScrollStateRef: refs.autoScrollStateRef
9290
9439
  });
9291
- useDragVisualLoop({ activeDrag, refs, getCommitLaneSnapshot, setInsideRect, setDropLine });
9440
+ useDragVisualLoop({ activeDrag, refs, getCommitLaneSnapshot, setInsideRect, setDropLine, setActiveLaneContainerId });
9292
9441
  useExternalDragLoop({
9293
9442
  externalDrag,
9294
9443
  refs,
@@ -9308,7 +9457,6 @@ function DesignModeOverlay() {
9308
9457
  DndContext,
9309
9458
  {
9310
9459
  sensors,
9311
- collisionDetection,
9312
9460
  autoScroll: false,
9313
9461
  onDragStart: handleDragStart,
9314
9462
  onDragEnd: handleDragEnd,
@@ -9317,10 +9465,8 @@ function DesignModeOverlay() {
9317
9465
  /* @__PURE__ */ jsx4(
9318
9466
  ElementHighlight,
9319
9467
  {
9320
- elements,
9321
- draggingNodeId: activeDrag?.nodeId ?? null,
9322
- activeEl: refs.activeElRef.current,
9323
- arrangeSelectedNodeId,
9468
+ selected: arrangeSelectedNodeId ? nodeMap.get(arrangeSelectedNodeId) ?? null : null,
9469
+ dragGuide,
9324
9470
  externalDragActive: externalDrag !== null,
9325
9471
  onResizeHandlePointerDown: handleResizeHandlePointerDown
9326
9472
  }