@archie/devtools 0.0.43 → 0.0.45

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,
@@ -5224,8 +5186,10 @@ function ensureUniqueNodeId(base, used) {
5224
5186
  while (used.has(`${base}::${idx}`)) idx += 1;
5225
5187
  return `${base}::${idx}`;
5226
5188
  }
5189
+ var INSERT_NODE_ID_PREFIX = "insert:";
5227
5190
  function resolveStableNodeId(el, used) {
5228
5191
  const persisted = el.getAttribute(DND_NODE_ID_ATTR);
5192
+ if (persisted && persisted.startsWith(INSERT_NODE_ID_PREFIX)) return persisted;
5229
5193
  if (persisted && !used.has(persisted)) return persisted;
5230
5194
  const projected = projectUsageMeta(el);
5231
5195
  if (projected) return ensureUniqueNodeId(projected.nodeId, used);
@@ -5296,7 +5260,7 @@ function isFlexOrGridParent(el) {
5296
5260
  const d = getComputedStyle(el).display;
5297
5261
  return d === "flex" || d === "grid" || d === "inline-flex" || d === "inline-grid";
5298
5262
  }
5299
- function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(), seenResolvedHosts = /* @__PURE__ */ new WeakSet()) {
5263
+ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(), seenResolvedHosts = /* @__PURE__ */ new WeakSet(), resolvedUnreported = /* @__PURE__ */ new Set()) {
5300
5264
  if (depth > MAX_DEPTH) return [];
5301
5265
  const results = [];
5302
5266
  for (const child of Array.from(root.children)) {
@@ -5321,7 +5285,9 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5321
5285
  const nodeId2 = resolveStableNodeId(el, usedNodeIds);
5322
5286
  usedNodeIds.add(nodeId2);
5323
5287
  el.setAttribute(DND_NODE_ID_ATTR, nodeId2);
5324
- if (!isZeroRect) {
5288
+ if (isZeroRect) {
5289
+ resolvedUnreported.add(el);
5290
+ } else {
5325
5291
  seenResolvedHosts.add(el);
5326
5292
  const surfaceMeta2 = el instanceof HTMLElement ? getDirectEditorMeta(el) ?? void 0 : void 0;
5327
5293
  const identityMeta3 = projectUsageMeta(el) ?? surfaceMeta2;
@@ -5345,7 +5311,7 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5345
5311
  });
5346
5312
  }
5347
5313
  }
5348
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5314
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5349
5315
  continue;
5350
5316
  }
5351
5317
  if (isSvgRoot && isInspectorEligible(el) === null) {
@@ -5354,7 +5320,7 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5354
5320
  const eligibilitySource = isInspectorEligible(el);
5355
5321
  const inferredEligibility = eligibilitySource ?? inferEligibilityFromSortableParent(el) ?? (projectUsageMeta(el) ? "projected-usage" : null);
5356
5322
  if (inferredEligibility === null) {
5357
- const childResults = scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts);
5323
+ const childResults = scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported);
5358
5324
  results.push(...childResults);
5359
5325
  if (isFlexOrGridParent(el) && !seenResolvedHosts.has(el) && childResults.some((r) => r.draggable && r.element.parentElement === el)) {
5360
5326
  const nodeId2 = resolveStableNodeId(el, usedNodeIds);
@@ -5382,13 +5348,13 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5382
5348
  const dragSurface = resolveDragSurface(el);
5383
5349
  const hostEl = dragSurface.host;
5384
5350
  if (!hostEl) {
5385
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5351
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5386
5352
  continue;
5387
5353
  }
5388
5354
  const hostRect = hostEl.getBoundingClientRect();
5389
5355
  const nodeId = resolveStableNodeId(hostEl, usedNodeIds);
5390
5356
  if (seenResolvedHosts.has(hostEl)) {
5391
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5357
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5392
5358
  continue;
5393
5359
  }
5394
5360
  seenResolvedHosts.add(hostEl);
@@ -5418,9 +5384,9 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5418
5384
  identityOrigin: templateFrameLabel(identityMeta2),
5419
5385
  editorMeta: surfaceMeta
5420
5386
  });
5421
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5387
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5422
5388
  } else {
5423
- if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5389
+ if (!isSvgRoot) results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts, resolvedUnreported));
5424
5390
  }
5425
5391
  }
5426
5392
  return results;
@@ -5476,8 +5442,10 @@ function useElementScanner(enabled) {
5476
5442
  if (pausedRef.current) return;
5477
5443
  const root = document.getElementById("root") ?? document.body;
5478
5444
  const usedNodeIds = /* @__PURE__ */ new Set();
5479
- const scanned = scanElements(root, 0, usedNodeIds);
5445
+ const resolvedUnreported = /* @__PURE__ */ new Set();
5446
+ const scanned = scanElements(root, 0, usedNodeIds, void 0, resolvedUnreported);
5480
5447
  const seenEls = new Set(scanned.map((s) => s.element));
5448
+ for (const el of resolvedUnreported) seenEls.add(el);
5481
5449
  for (const el of Array.from(document.querySelectorAll("[data-dnd-moved]"))) {
5482
5450
  if (seenEls.has(el)) continue;
5483
5451
  const entry = buildMovedElementEntry(el, usedNodeIds);
@@ -5541,6 +5509,7 @@ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
5541
5509
  var HANDLES = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
5542
5510
  var HANDLE_HIT_SIZE = 24;
5543
5511
  var HANDLE_KNOB_SIZE = 8;
5512
+ var EMPTY_SCROLL_DELTA = { x: NaN, y: NaN };
5544
5513
  var OVERLAY_ROOT_STYLE = {
5545
5514
  position: "fixed",
5546
5515
  inset: 0,
@@ -5614,17 +5583,39 @@ function handlePosition(dir) {
5614
5583
  };
5615
5584
  return positions[dir];
5616
5585
  }
5617
- function getOutlineStyle(rect, isSelected, inspectorTheme) {
5586
+ function getSelectedOutlineStyle(rect, inspectorTheme) {
5618
5587
  return {
5619
5588
  ...OUTLINE_BASE_STYLE,
5620
5589
  left: rect.left,
5621
5590
  top: rect.top,
5622
5591
  width: rect.width,
5623
5592
  height: rect.height,
5624
- border: isSelected ? `2px solid ${inspectorTheme.accent}` : `1px dashed ${inspectorTheme.shared}`,
5625
- backgroundColor: isSelected ? inspectorTheme.accentSoft : "transparent"
5593
+ border: `2px solid ${inspectorTheme.accent}`,
5594
+ backgroundColor: inspectorTheme.accentSoft
5626
5595
  };
5627
5596
  }
5597
+ function getGuideOutlineStyle(rect, tier, inspectorTheme) {
5598
+ const base = {
5599
+ ...OUTLINE_BASE_STYLE,
5600
+ left: rect.left,
5601
+ top: rect.top,
5602
+ width: rect.width,
5603
+ height: rect.height
5604
+ };
5605
+ switch (tier) {
5606
+ case "candidate":
5607
+ return { ...base, border: `1px solid ${inspectorTheme.accent}`, opacity: 0.2 };
5608
+ case "lane-child":
5609
+ return { ...base, border: `1px dashed ${inspectorTheme.accent}`, opacity: 0.45 };
5610
+ case "lane":
5611
+ return {
5612
+ ...base,
5613
+ border: `1.5px solid ${inspectorTheme.accent}`,
5614
+ backgroundColor: inspectorTheme.accentSoft,
5615
+ opacity: 0.75
5616
+ };
5617
+ }
5618
+ }
5628
5619
  function getResizeHandleStyle(position) {
5629
5620
  return {
5630
5621
  ...RESIZE_HANDLE_BASE_STYLE,
@@ -5647,40 +5638,58 @@ function getLabelStyle(labelLeft, labelTop, labelColor) {
5647
5638
  backgroundColor: labelColor
5648
5639
  };
5649
5640
  }
5641
+ function projectDragRect(dragGuide, nodeId, sdx, sdy) {
5642
+ const r = dragGuide.rects.get(nodeId);
5643
+ if (!r) return null;
5644
+ return { left: r.left - sdx, top: r.top - sdy, width: r.width, height: r.height };
5645
+ }
5650
5646
  function ElementHighlight({
5651
- elements,
5652
- draggingNodeId,
5653
- activeEl,
5654
- arrangeSelectedNodeId,
5647
+ selected,
5648
+ dragGuide,
5655
5649
  externalDragActive,
5656
5650
  onResizeHandlePointerDown
5657
5651
  }) {
5658
5652
  const inspectorTheme = useDesignModeStore((s) => s.inspectorTheme);
5659
- const internalDragInProgress = draggingNodeId !== null;
5660
- const overlayVisible = arrangeSelectedNodeId !== null || internalDragInProgress;
5661
5653
  const containerRef = useRef5(null);
5662
5654
  const rafRef = useRef5(0);
5655
+ const lastScrollDeltaRef = useRef5(EMPTY_SCROLL_DELTA);
5663
5656
  const syncPositions = useCallback5(() => {
5664
5657
  const container = containerRef.current;
5665
5658
  if (!container) return;
5666
- const outlines = container.querySelectorAll("[data-outline-for]");
5667
- for (const outline of outlines) {
5668
- const nodeId = outline.getAttribute("data-outline-for");
5669
- const el = elements.find((e) => e.nodeId === nodeId);
5670
- if (!el) continue;
5671
- const rect = el.element.getBoundingClientRect();
5672
- outline.style.left = `${rect.left}px`;
5673
- outline.style.top = `${rect.top}px`;
5674
- outline.style.width = `${rect.width}px`;
5675
- outline.style.height = `${rect.height}px`;
5676
- const label = outline.nextElementSibling;
5677
- if (label) {
5678
- label.style.left = `${rect.left}px`;
5679
- label.style.top = `${rect.top}px`;
5659
+ if (dragGuide) {
5660
+ const sdx = window.scrollX - dragGuide.startScroll.x;
5661
+ const sdy = window.scrollY - dragGuide.startScroll.y;
5662
+ if (sdx === lastScrollDeltaRef.current.x && sdy === lastScrollDeltaRef.current.y) return;
5663
+ lastScrollDeltaRef.current = { x: sdx, y: sdy };
5664
+ const outlines = container.querySelectorAll("[data-guide-for]");
5665
+ for (const outline2 of outlines) {
5666
+ const nodeId = outline2.getAttribute("data-guide-for");
5667
+ const r = dragGuide.rects.get(nodeId);
5668
+ if (!r) continue;
5669
+ outline2.style.left = `${r.left - sdx}px`;
5670
+ outline2.style.top = `${r.top - sdy}px`;
5680
5671
  }
5672
+ return;
5681
5673
  }
5682
- }, [elements]);
5674
+ if (!selected) return;
5675
+ const rect2 = selected.element.getBoundingClientRect();
5676
+ const outline = container.querySelector("[data-outline-for]");
5677
+ if (outline) {
5678
+ outline.style.left = `${rect2.left}px`;
5679
+ outline.style.top = `${rect2.top}px`;
5680
+ outline.style.width = `${rect2.width}px`;
5681
+ outline.style.height = `${rect2.height}px`;
5682
+ }
5683
+ const label = container.querySelector("[data-drag-handle]");
5684
+ if (label) {
5685
+ label.style.left = `${rect2.left + 12}px`;
5686
+ label.style.top = `${Math.max(0, rect2.top - 20)}px`;
5687
+ }
5688
+ }, [dragGuide, selected]);
5689
+ const loopActive = !externalDragActive && (dragGuide !== null || selected !== null);
5683
5690
  useEffect5(() => {
5691
+ if (!loopActive) return;
5692
+ lastScrollDeltaRef.current = EMPTY_SCROLL_DELTA;
5684
5693
  let running = true;
5685
5694
  const loop = () => {
5686
5695
  if (!running) return;
@@ -5692,81 +5701,113 @@ function ElementHighlight({
5692
5701
  running = false;
5693
5702
  cancelAnimationFrame(rafRef.current);
5694
5703
  };
5695
- }, [syncPositions]);
5696
- if (!overlayVisible) return null;
5697
- if (externalDragActive) return null;
5698
- return /* @__PURE__ */ jsx2(
5699
- "div",
5700
- {
5701
- ref: containerRef,
5702
- "data-design-mode-ui": true,
5703
- style: OVERLAY_ROOT_STYLE,
5704
- children: elements.map((el) => {
5705
- if (el.nodeId === draggingNodeId) return null;
5706
- if (activeEl && activeEl !== el.element && activeEl.contains(el.element)) return null;
5707
- const isSelected = el.nodeId === arrangeSelectedNodeId;
5708
- if (!isSelected && !internalDragInProgress) return null;
5709
- const rect = el.element.getBoundingClientRect();
5710
- const showLabel = isSelected;
5711
- const labelTop = isSelected ? Math.max(0, rect.top - 20) : rect.top;
5712
- const labelLeft = isSelected ? rect.left + 12 : rect.left;
5713
- return /* @__PURE__ */ jsxs("div", { children: [
5714
- /* @__PURE__ */ jsx2(
5715
- "div",
5716
- {
5717
- "data-outline-for": el.nodeId,
5718
- "data-design-mode-ui": true,
5719
- style: getOutlineStyle(rect, isSelected, inspectorTheme),
5720
- children: isSelected && HANDLES.map((dir) => {
5721
- const pos = handlePosition(dir);
5722
- return /* @__PURE__ */ jsx2(
5723
- "div",
5724
- {
5725
- "data-design-mode-ui": true,
5726
- "data-resize-handle": dir,
5727
- onPointerDown: (e) => {
5728
- e.stopPropagation();
5729
- onResizeHandlePointerDown?.(e, el.nodeId, dir);
5730
- },
5731
- style: getResizeHandleStyle(pos),
5732
- children: /* @__PURE__ */ jsx2(
5733
- "span",
5734
- {
5735
- "data-design-mode-ui": true,
5736
- style: getResizeKnobStyle(inspectorTheme.accent)
5737
- }
5738
- )
5739
- },
5740
- dir
5741
- );
5742
- })
5743
- }
5744
- ),
5745
- showLabel && /* @__PURE__ */ jsxs(
5704
+ }, [loopActive, syncPositions]);
5705
+ if (!loopActive) return null;
5706
+ if (dragGuide) {
5707
+ const sdx = window.scrollX - dragGuide.startScroll.x;
5708
+ const sdy = window.scrollY - dragGuide.startScroll.y;
5709
+ const { candidateContainerIds, activeContainerId, activeChildIds, draggingNodeId } = dragGuide;
5710
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, "data-design-mode-ui": true, style: OVERLAY_ROOT_STYLE, children: [
5711
+ [...candidateContainerIds].map((id) => {
5712
+ if (id === activeContainerId || id === draggingNodeId) return null;
5713
+ const rect2 = projectDragRect(dragGuide, id, sdx, sdy);
5714
+ if (!rect2) return null;
5715
+ return /* @__PURE__ */ jsx2(
5716
+ "div",
5717
+ {
5718
+ "data-guide-for": id,
5719
+ "data-guide-tier": "candidate",
5720
+ "data-design-mode-ui": true,
5721
+ style: getGuideOutlineStyle(rect2, "candidate", inspectorTheme)
5722
+ },
5723
+ `candidate:${id}`
5724
+ );
5725
+ }),
5726
+ activeContainerId !== null && activeChildIds.map((id) => {
5727
+ if (id === draggingNodeId) return null;
5728
+ const rect2 = projectDragRect(dragGuide, id, sdx, sdy);
5729
+ if (!rect2) return null;
5730
+ return /* @__PURE__ */ jsx2(
5731
+ "div",
5732
+ {
5733
+ "data-guide-for": id,
5734
+ "data-guide-tier": "lane-child",
5735
+ "data-design-mode-ui": true,
5736
+ style: getGuideOutlineStyle(rect2, "lane-child", inspectorTheme)
5737
+ },
5738
+ `lane-child:${id}`
5739
+ );
5740
+ }),
5741
+ activeContainerId !== null && activeContainerId !== draggingNodeId && (() => {
5742
+ const rect2 = projectDragRect(dragGuide, activeContainerId, sdx, sdy);
5743
+ if (!rect2) return null;
5744
+ return /* @__PURE__ */ jsx2(
5745
+ "div",
5746
+ {
5747
+ "data-guide-for": activeContainerId,
5748
+ "data-guide-tier": "lane",
5749
+ "data-design-mode-ui": true,
5750
+ style: getGuideOutlineStyle(rect2, "lane", inspectorTheme)
5751
+ },
5752
+ `lane:${activeContainerId}`
5753
+ );
5754
+ })()
5755
+ ] });
5756
+ }
5757
+ if (!selected) return null;
5758
+ const rect = selected.element.getBoundingClientRect();
5759
+ const labelTop = Math.max(0, rect.top - 20);
5760
+ const labelLeft = rect.left + 12;
5761
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, "data-design-mode-ui": true, style: OVERLAY_ROOT_STYLE, children: [
5762
+ /* @__PURE__ */ jsx2(
5763
+ "div",
5764
+ {
5765
+ "data-outline-for": selected.nodeId,
5766
+ "data-design-mode-ui": true,
5767
+ style: getSelectedOutlineStyle(rect, inspectorTheme),
5768
+ children: selected.draggable && HANDLES.map((dir) => {
5769
+ const pos = handlePosition(dir);
5770
+ return /* @__PURE__ */ jsx2(
5746
5771
  "div",
5747
5772
  {
5748
5773
  "data-design-mode-ui": true,
5749
- "data-drag-handle": el.nodeId,
5750
- style: getLabelStyle(
5751
- labelLeft,
5752
- labelTop,
5753
- isSelected ? inspectorTheme.accent : inspectorTheme.hover
5754
- ),
5755
- children: [
5756
- /* @__PURE__ */ jsx2("span", { style: LABEL_ICON_STYLE, children: "\u283F" }),
5757
- el.label,
5758
- isSelected && /* @__PURE__ */ jsxs("span", { style: LABEL_DIMENSIONS_STYLE, children: [
5759
- Math.round(rect.width),
5760
- "\xD7",
5761
- Math.round(rect.height)
5762
- ] })
5763
- ]
5764
- }
5765
- )
5766
- ] }, el.nodeId);
5767
- })
5768
- }
5769
- );
5774
+ "data-resize-handle": dir,
5775
+ onPointerDown: (e) => {
5776
+ e.stopPropagation();
5777
+ onResizeHandlePointerDown?.(e, selected.nodeId, dir);
5778
+ },
5779
+ style: getResizeHandleStyle(pos),
5780
+ children: /* @__PURE__ */ jsx2(
5781
+ "span",
5782
+ {
5783
+ "data-design-mode-ui": true,
5784
+ style: getResizeKnobStyle(inspectorTheme.accent)
5785
+ }
5786
+ )
5787
+ },
5788
+ dir
5789
+ );
5790
+ })
5791
+ }
5792
+ ),
5793
+ /* @__PURE__ */ jsxs(
5794
+ "div",
5795
+ {
5796
+ "data-design-mode-ui": true,
5797
+ "data-drag-handle": selected.nodeId,
5798
+ style: getLabelStyle(labelLeft, labelTop, inspectorTheme.accent),
5799
+ children: [
5800
+ /* @__PURE__ */ jsx2("span", { style: LABEL_ICON_STYLE, children: "\u283F" }),
5801
+ selected.label,
5802
+ /* @__PURE__ */ jsxs("span", { style: LABEL_DIMENSIONS_STYLE, children: [
5803
+ Math.round(rect.width),
5804
+ "\xD7",
5805
+ Math.round(rect.height)
5806
+ ] })
5807
+ ]
5808
+ }
5809
+ )
5810
+ ] });
5770
5811
  }
5771
5812
 
5772
5813
  // src/client/dnd/design-mode/projection.ts
@@ -6560,30 +6601,6 @@ function applyDndCommandPlacement(command, direction, resolveNodeElement) {
6560
6601
  return true;
6561
6602
  }
6562
6603
 
6563
- // src/client/dnd/design-mode/design-mode-collision.ts
6564
- function filterDroppables(droppableContainers, blockedContainerIds) {
6565
- if (!blockedContainerIds || blockedContainerIds.size === 0) return droppableContainers;
6566
- return droppableContainers.filter((container) => !blockedContainerIds.has(String(container.id)));
6567
- }
6568
- function getCollisions(detector, args) {
6569
- return detector(args);
6570
- }
6571
- function createDesignModeCollisionDetection(blockedContainerIds) {
6572
- return (args) => {
6573
- const droppableContainers = filterDroppables(args.droppableContainers, blockedContainerIds);
6574
- if (droppableContainers.length === 0) return [];
6575
- const scopedArgs = {
6576
- ...args,
6577
- droppableContainers
6578
- };
6579
- const pointerHits = getCollisions(pointerWithin, scopedArgs);
6580
- if (pointerHits.length > 0) return pointerHits;
6581
- const cornerHits = getCollisions(closestCorners, scopedArgs);
6582
- if (cornerHits.length > 0) return cornerHits;
6583
- return getCollisions(closestCenter, scopedArgs);
6584
- };
6585
- }
6586
-
6587
6604
  // src/client/dnd/design-mode/arrange-session.ts
6588
6605
  function serializeArrangeChange(event, index) {
6589
6606
  return {
@@ -6862,9 +6879,9 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6862
6879
  const lastIntentRef = useRef8(null);
6863
6880
  const visualIntentRef = useRef8(null);
6864
6881
  const sourceContainerIdRef = useRef8(null);
6882
+ const activeLaneContainerIdRef = useRef8(null);
6865
6883
  const rowLockedRef = useRef8(false);
6866
6884
  const dragSessionAnchorRef = useRef8(null);
6867
- const lockRef = useRef8({ containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null });
6868
6885
  const targetPlaneRef = useRef8("lane");
6869
6886
  const nestCandidateRef = useRef8(null);
6870
6887
  const nestTargetRef = useRef8(null);
@@ -6874,7 +6891,6 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6874
6891
  const sourceCommitLaneRef = useRef8(null);
6875
6892
  const commitLaneCacheRef = useRef8(/* @__PURE__ */ new Map());
6876
6893
  const safeZoneRef = useRef8(null);
6877
- const blockedContainerIdsRef = useRef8(/* @__PURE__ */ new Set());
6878
6894
  const historyRef = useRef8(new DndCommandHistory());
6879
6895
  const processedUndoIdRef = useRef8(undoRequestId);
6880
6896
  const processedRedoIdRef = useRef8(redoRequestId);
@@ -6894,9 +6910,9 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6894
6910
  lastIntentRef,
6895
6911
  visualIntentRef,
6896
6912
  sourceContainerIdRef,
6913
+ activeLaneContainerIdRef,
6897
6914
  rowLockedRef,
6898
6915
  dragSessionAnchorRef,
6899
- lockRef,
6900
6916
  targetPlaneRef,
6901
6917
  nestCandidateRef,
6902
6918
  nestTargetRef,
@@ -6906,7 +6922,6 @@ function useOverlayRefs(undoRequestId, redoRequestId) {
6906
6922
  sourceCommitLaneRef,
6907
6923
  commitLaneCacheRef,
6908
6924
  safeZoneRef,
6909
- blockedContainerIdsRef,
6910
6925
  historyRef,
6911
6926
  processedUndoIdRef,
6912
6927
  processedRedoIdRef,
@@ -7174,6 +7189,11 @@ function pointInRect(px, py, r) {
7174
7189
  function pointInExpandedRect(px, py, r, inset) {
7175
7190
  return px >= r.left - inset && px <= r.right + inset && py >= r.top - inset && py <= r.bottom + inset;
7176
7191
  }
7192
+ function isInNestZone(px, py, rect) {
7193
+ const relX = (px - rect.left) / rect.width;
7194
+ const relY = (py - rect.top) / rect.height;
7195
+ return relX > NEST_EDGE_FRACTION && relX < 1 - NEST_EDGE_FRACTION && relY > NEST_EDGE_FRACTION && relY < 1 - NEST_EDGE_FRACTION;
7196
+ }
7177
7197
  function buildSlotQuadtree(slotIndex) {
7178
7198
  const rects = /* @__PURE__ */ new Map();
7179
7199
  for (const [id, slot] of slotIndex) {
@@ -7216,6 +7236,11 @@ var DragIntentEngine = class {
7216
7236
  __publicField(this, "laneAllowedContainers");
7217
7237
  __publicField(this, "blockedContainerIds");
7218
7238
  __publicField(this, "targetPlane", "lane");
7239
+ // Memoized result of getValidDropContainerIds(). Safe to cache because every
7240
+ // input it reads (containerIndex, slotIndex, activeId, laneAllowedContainers,
7241
+ // blockedContainerIds) is fixed in the constructor and never mutated during
7242
+ // a drag.
7243
+ __publicField(this, "validDropContainerIds", null);
7219
7244
  this.projectionTree = projection.projectionTree;
7220
7245
  this.containerIndex = projection.containerIndex;
7221
7246
  this.slotIndex = projection.slotIndex;
@@ -7252,6 +7277,44 @@ var DragIntentEngine = class {
7252
7277
  getContainerChildren(containerId) {
7253
7278
  return this.containerIndex.get(containerId)?.children ?? [];
7254
7279
  }
7280
+ /**
7281
+ * Every container id this engine would accept as a drop target for the
7282
+ * active node, at the current pointer position or any other — i.e. the
7283
+ * full set `resolvePointerContainer` draws from, not a snapshot of one
7284
+ * resolve() call. Intended for the visual layer to draw a guide that
7285
+ * matches exactly what the engine can land on, without re-deriving the
7286
+ * eligibility rule itself.
7287
+ *
7288
+ * Always queries with `laneLock: true`. That is not an approximation of
7289
+ * the "real" default — it IS the real answer for every reachable caller:
7290
+ *
7291
+ * - With a row lock active (`laneAllowedContainers` set), `isLaneContainer`
7292
+ * restricts eligibility to that set, so this returns exactly
7293
+ * `{srcContainerId}` — the ARCH-1071 lock, reproduced faithfully.
7294
+ * - Without a row lock, `laneAllowedContainers` is null and
7295
+ * `isLaneContainer` returns true unconditionally, so `laneLock`'s value
7296
+ * makes no difference — passing `true` costs nothing here.
7297
+ * - The engine's own 'nest' plane does call `isEligibleDropContainer` with
7298
+ * `laneLock: false` (see `peekNestIntent` → `resolvePointerContainer`),
7299
+ * but that plane is unreachable for a locked drag: `computeAllowNest`
7300
+ * (useDragVisualLoop.ts) short-circuits `rowLocked` drags before
7301
+ * `setTargetPlane('nest')` is ever called, so `targetPlane` stays
7302
+ * 'lane' for the drag's whole lifetime. If that guard is ever removed,
7303
+ * this method will under-report nest-only targets for locked drags —
7304
+ * keep the two in sync.
7305
+ *
7306
+ * Cached after the first call: every input (containerIndex, slotIndex,
7307
+ * activeId, laneAllowedContainers, blockedContainerIds) is fixed at
7308
+ * construction and never changes for the life of a drag.
7309
+ */
7310
+ getValidDropContainerIds() {
7311
+ if (this.validDropContainerIds) return this.validDropContainerIds;
7312
+ const ids2 = /* @__PURE__ */ new Set();
7313
+ for (const id of this.containerIndex.keys()) {
7314
+ if (this.isEligibleDropContainer(id, true)) ids2.add(id);
7315
+ }
7316
+ return this.validDropContainerIds = ids2;
7317
+ }
7255
7318
  setTargetPlane(plane) {
7256
7319
  this.targetPlane = plane;
7257
7320
  }
@@ -7270,6 +7333,17 @@ var DragIntentEngine = class {
7270
7333
  isInvalidDropContainer(containerId) {
7271
7334
  return containerId === this.activeId || this.isContainerDescendantOf(containerId, this.activeId);
7272
7335
  }
7336
+ /**
7337
+ * The full "can this container accept the active node this drag" predicate.
7338
+ * SINGLE definition — every caller routes through it so no two parts of the
7339
+ * system can disagree about which containers are droppable.
7340
+ */
7341
+ isEligibleDropContainer(containerId, laneLock) {
7342
+ if (this.isInvalidDropContainer(containerId)) return false;
7343
+ if (this.isBlockedContainer(containerId)) return false;
7344
+ if (laneLock && !this.isLaneContainer(containerId)) return false;
7345
+ return this.hasUsableSlots(containerId);
7346
+ }
7273
7347
  /**
7274
7348
  * Resolve the drag intent from the current pointer position.
7275
7349
  *
@@ -7357,9 +7431,7 @@ var DragIntentEngine = class {
7357
7431
  const childNode = this.getNodeRect(childId);
7358
7432
  if (!childNode) continue;
7359
7433
  if (!pointInRect(px, py, childNode)) continue;
7360
- const relX = (px - childNode.left) / childNode.width;
7361
- const relY = (py - childNode.top) / childNode.height;
7362
- if (relX > NEST_EDGE_FRACTION && relX < 1 - NEST_EDGE_FRACTION && relY > NEST_EDGE_FRACTION && relY < 1 - NEST_EDGE_FRACTION) {
7434
+ if (isInNestZone(px, py, childNode)) {
7363
7435
  return {
7364
7436
  containerId: pointerContainerId,
7365
7437
  index: container.children.indexOf(childId),
@@ -7373,11 +7445,8 @@ var DragIntentEngine = class {
7373
7445
  resolvePointerContainer(px, py, laneLock) {
7374
7446
  const pointerContainers = [];
7375
7447
  for (const [id, node] of this.containerIndex) {
7376
- if (this.isInvalidDropContainer(id)) continue;
7377
- if (this.isBlockedContainer(id)) continue;
7378
- if (laneLock && !this.isLaneContainer(id)) continue;
7379
7448
  if (!pointInRect(px, py, node.rect)) continue;
7380
- if (this.hasUsableSlots(id) === false) continue;
7449
+ if (!this.isEligibleDropContainer(id, laneLock)) continue;
7381
7450
  const r = node.rect;
7382
7451
  const edgeDistX = Math.min(px - r.left, r.right - px);
7383
7452
  const edgeDistY = Math.min(py - r.top, r.bottom - py);
@@ -7394,11 +7463,11 @@ var DragIntentEngine = class {
7394
7463
  let pointerContainerId = pointerContainers[0]?.id ?? null;
7395
7464
  if (!pointerContainerId && this.lastIntent?.mode === "reorder") {
7396
7465
  const lastContainer = this.containerIndex.get(this.lastIntent.containerId);
7397
- 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)) {
7466
+ if (lastContainer && this.isEligibleDropContainer(lastContainer.id, laneLock) && pointInExpandedRect(px, py, lastContainer.rect, LAST_CONTAINER_REENTRY_SLOP_PX)) {
7398
7467
  pointerContainerId = lastContainer.id;
7399
7468
  }
7400
7469
  }
7401
- if (this.originContainerId && this.originContainerId !== pointerContainerId && !this.isInvalidDropContainer(this.originContainerId) && !this.isBlockedContainer(this.originContainerId) && (!laneLock || this.isLaneContainer(this.originContainerId)) && this.hasUsableSlots(this.originContainerId)) {
7470
+ if (this.originContainerId && this.originContainerId !== pointerContainerId && this.isEligibleDropContainer(this.originContainerId, laneLock)) {
7402
7471
  const originNode = this.containerIndex.get(this.originContainerId);
7403
7472
  const currentNode = pointerContainerId ? this.containerIndex.get(pointerContainerId) : null;
7404
7473
  const originIsDeeper = originNode && (!currentNode || originNode.depth >= currentNode.depth);
@@ -7811,7 +7880,8 @@ function useDragVisualLoop({
7811
7880
  refs,
7812
7881
  getCommitLaneSnapshot,
7813
7882
  setInsideRect,
7814
- setDropLine
7883
+ setDropLine,
7884
+ setActiveLaneContainerId
7815
7885
  }) {
7816
7886
  useEffect8(() => {
7817
7887
  const engine = refs.engineRef.current;
@@ -7819,6 +7889,11 @@ function useDragVisualLoop({
7819
7889
  if (!activeDrag || !engine || !snapshot) return;
7820
7890
  let rafId;
7821
7891
  const animator = refs.animatorRef.current;
7892
+ const publishActiveLane = (containerId) => {
7893
+ if (refs.activeLaneContainerIdRef.current === containerId) return;
7894
+ refs.activeLaneContainerIdRef.current = containerId;
7895
+ setActiveLaneContainerId(containerId);
7896
+ };
7822
7897
  const setTargetPlane = (nextPlane) => {
7823
7898
  const prevPlane = refs.targetPlaneRef.current;
7824
7899
  if (prevPlane === nextPlane) return;
@@ -7837,6 +7912,7 @@ function useDragVisualLoop({
7837
7912
  const safeZone = refs.safeZoneRef.current;
7838
7913
  if (isPointerBlocked(px, py, safeZone)) {
7839
7914
  setTargetPlane("lane");
7915
+ publishActiveLane(null);
7840
7916
  refs.lastIntentRef.current = null;
7841
7917
  refs.visualIntentRef.current = null;
7842
7918
  refs.nestCandidateRef.current = null;
@@ -7878,6 +7954,7 @@ function useDragVisualLoop({
7878
7954
  let intent = engine.resolve(px, py, window.scrollX, window.scrollY, snapshot.scroll.x, snapshot.scroll.y);
7879
7955
  if (!intent) {
7880
7956
  setTargetPlane("lane");
7957
+ publishActiveLane(null);
7881
7958
  refs.lastIntentRef.current = null;
7882
7959
  refs.visualIntentRef.current = null;
7883
7960
  refs.nestCandidateRef.current = null;
@@ -7903,6 +7980,7 @@ function useDragVisualLoop({
7903
7980
  const targetId = intent.slotId.split("::nest::")[1] ?? "";
7904
7981
  if (!targetId) {
7905
7982
  setTargetPlane("lane");
7983
+ publishActiveLane(null);
7906
7984
  rafId = requestAnimationFrame(tick);
7907
7985
  return;
7908
7986
  }
@@ -7924,12 +8002,14 @@ function useDragVisualLoop({
7924
8002
  };
7925
8003
  refs.visualIntentRef.current = intent;
7926
8004
  refs.lastIntentRef.current = intent;
8005
+ publishActiveLane(intent.containerId);
7927
8006
  } else {
7928
8007
  setTargetPlane("lane");
7929
8008
  refs.nestCandidateRef.current = null;
7930
8009
  refs.nestTargetRef.current = null;
7931
8010
  refs.lastIntentRef.current = intent;
7932
8011
  refs.visualIntentRef.current = intent;
8012
+ publishActiveLane(intent.containerId);
7933
8013
  setInsideRect(null);
7934
8014
  const proj = refs.projectionRef.current;
7935
8015
  const slot = proj.slotIndex.get(intent.slotId);
@@ -8236,7 +8316,8 @@ function useDragLifecycle({
8236
8316
  syncHistoryAvailability,
8237
8317
  resolveNodeElement,
8238
8318
  setInsideRect,
8239
- setDropLine
8319
+ setDropLine,
8320
+ setActiveLaneContainerId
8240
8321
  }) {
8241
8322
  const [activeDrag, setActiveDrag] = useState5(null);
8242
8323
  const clearDragState = useCallback6(() => {
@@ -8247,13 +8328,12 @@ function useDragLifecycle({
8247
8328
  refs.snapshotRef.current = null;
8248
8329
  refs.projectionRef.current = EMPTY_PROJECTION;
8249
8330
  refs.engineRef.current = null;
8250
- refs.blockedContainerIdsRef.current = /* @__PURE__ */ new Set();
8251
8331
  refs.lastIntentRef.current = null;
8252
8332
  refs.visualIntentRef.current = null;
8253
8333
  refs.sourceContainerIdRef.current = null;
8334
+ refs.activeLaneContainerIdRef.current = null;
8254
8335
  refs.rowLockedRef.current = false;
8255
8336
  refs.dragSessionAnchorRef.current = null;
8256
- refs.lockRef.current = { containerId: null, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null };
8257
8337
  refs.targetPlaneRef.current = "lane";
8258
8338
  refs.nestCandidateRef.current = null;
8259
8339
  refs.nestTargetRef.current = null;
@@ -8264,6 +8344,7 @@ function useDragLifecycle({
8264
8344
  refs.safeZoneRef.current = null;
8265
8345
  setInsideRect(null);
8266
8346
  setDropLine(null);
8347
+ setActiveLaneContainerId(null);
8267
8348
  setActiveDrag(null);
8268
8349
  resume();
8269
8350
  }, [clearCommitLaneCache, resume]);
@@ -8321,7 +8402,6 @@ function useDragLifecycle({
8321
8402
  const sourceCommitLane = getCommitLaneSnapshot(srcContainerId);
8322
8403
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
8323
8404
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
8324
- refs.blockedContainerIdsRef.current = blockedContainerIds;
8325
8405
  const engineOptions = buildDragIntentEngineOptions(anchorScanned.element, srcContainerId, blockedContainerIds);
8326
8406
  refs.rowLockedRef.current = !!engineOptions.laneAllowedContainers;
8327
8407
  refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, engineOptions);
@@ -8330,7 +8410,6 @@ function useDragLifecycle({
8330
8410
  refs.sourceContainerIdRef.current = srcContainerId;
8331
8411
  refs.sourceCommitLaneRef.current = sourceCommitLane;
8332
8412
  refs.dragSessionAnchorRef.current = anchor;
8333
- refs.lockRef.current = { containerId: srcContainerId, isCrossUnlocked: true, exitSince: null, unlockUntilTs: null };
8334
8413
  refs.dragStartPositionRef.current = startPosition;
8335
8414
  refs.safeZoneRef.current = resolveSafeZoneRect();
8336
8415
  pause();
@@ -8484,7 +8563,9 @@ function useResizeSession({
8484
8563
  }) {
8485
8564
  const handleResizeHandlePointerDown = useCallback7((e, nodeId, dir) => {
8486
8565
  if (refs.resizeSessionRef.current) return;
8487
- const selectedEl = refs.nodeMapRef.current.get(nodeId)?.element ?? document.querySelector(`[data-dnd-node-id="${nodeId}"]`);
8566
+ const scanned = refs.nodeMapRef.current.get(nodeId);
8567
+ if (scanned && scanned.draggable === false) return;
8568
+ const selectedEl = scanned?.element ?? document.querySelector(`[data-dnd-node-id="${nodeId}"]`);
8488
8569
  if (!selectedEl) return;
8489
8570
  e.preventDefault();
8490
8571
  e.stopPropagation();
@@ -8887,6 +8968,7 @@ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, s
8887
8968
  // src/client/dnd/design-mode/DesignModeOverlay.tsx
8888
8969
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
8889
8970
  var DROP_ANIMATION = { duration: DURATION_MS, easing: EASING };
8971
+ var EMPTY_CONTAINER_ID_SET = /* @__PURE__ */ new Set();
8890
8972
  function resizeCursorFromHandle(handle) {
8891
8973
  switch (handle) {
8892
8974
  case "n":
@@ -9174,13 +9256,9 @@ function DesignModeOverlay() {
9174
9256
  const sensors = useSensors(
9175
9257
  useSensor(PointerSensor, { activationConstraint: { distance: 8 } })
9176
9258
  );
9177
- const collisionDetection = useCallback8(
9178
- (args) => createDesignModeCollisionDetection(refs.blockedContainerIdsRef.current)(args),
9179
- []
9180
- // eslint-disable-line react-hooks/exhaustive-deps -- refs are stable
9181
- );
9182
9259
  const [insideRect, setInsideRect] = useState6(null);
9183
9260
  const [dropLine, setDropLine] = useState6(null);
9261
+ const [activeLaneContainerId, setActiveLaneContainerId] = useState6(null);
9184
9262
  useEffect11(() => {
9185
9263
  if (!enabled) return;
9186
9264
  const fn = (e) => {
@@ -9216,8 +9294,43 @@ function DesignModeOverlay() {
9216
9294
  syncHistoryAvailability,
9217
9295
  resolveNodeElement,
9218
9296
  setInsideRect,
9219
- setDropLine
9297
+ setDropLine,
9298
+ setActiveLaneContainerId
9220
9299
  });
9300
+ const dropCandidateContainerIds = useMemo6(
9301
+ () => activeDrag ? refs.engineRef.current?.getValidDropContainerIds() ?? null : null,
9302
+ [activeDrag]
9303
+ // eslint-disable-line react-hooks/exhaustive-deps -- refs are stable
9304
+ );
9305
+ const visibleCandidateContainerIds = useMemo6(() => {
9306
+ if (!dropCandidateContainerIds) return null;
9307
+ const rects = refs.snapshotRef.current?.rects;
9308
+ if (!rects) return dropCandidateContainerIds;
9309
+ const vw = window.innerWidth;
9310
+ const vh = window.innerHeight;
9311
+ const visible = /* @__PURE__ */ new Set();
9312
+ for (const id of dropCandidateContainerIds) {
9313
+ const r = rects.get(id);
9314
+ if (!r) continue;
9315
+ if (r.right < 0 || r.bottom < 0 || r.left > vw || r.top > vh) continue;
9316
+ visible.add(id);
9317
+ }
9318
+ return visible;
9319
+ }, [dropCandidateContainerIds]);
9320
+ const dragGuide = useMemo6(() => {
9321
+ if (!activeDrag) return null;
9322
+ const snapshot = refs.snapshotRef.current;
9323
+ if (!snapshot) return null;
9324
+ const activeChildIds = activeLaneContainerId ? refs.engineRef.current?.getContainerChildren(activeLaneContainerId) ?? [] : [];
9325
+ return {
9326
+ rects: snapshot.rects,
9327
+ startScroll: snapshot.scroll,
9328
+ candidateContainerIds: visibleCandidateContainerIds ?? EMPTY_CONTAINER_ID_SET,
9329
+ activeContainerId: activeLaneContainerId,
9330
+ activeChildIds,
9331
+ draggingNodeId: activeDrag.nodeId
9332
+ };
9333
+ }, [activeDrag, activeLaneContainerId, visibleCandidateContainerIds]);
9221
9334
  useEffect11(() => {
9222
9335
  if (!enabled || activeDrag) return;
9223
9336
  if (undoRequestId === refs.processedUndoIdRef.current) return;
@@ -9280,7 +9393,7 @@ function DesignModeOverlay() {
9280
9393
  safeZoneRef: refs.safeZoneRef,
9281
9394
  autoScrollStateRef: refs.autoScrollStateRef
9282
9395
  });
9283
- useDragVisualLoop({ activeDrag, refs, getCommitLaneSnapshot, setInsideRect, setDropLine });
9396
+ useDragVisualLoop({ activeDrag, refs, getCommitLaneSnapshot, setInsideRect, setDropLine, setActiveLaneContainerId });
9284
9397
  useExternalDragLoop({
9285
9398
  externalDrag,
9286
9399
  refs,
@@ -9300,7 +9413,6 @@ function DesignModeOverlay() {
9300
9413
  DndContext,
9301
9414
  {
9302
9415
  sensors,
9303
- collisionDetection,
9304
9416
  autoScroll: false,
9305
9417
  onDragStart: handleDragStart,
9306
9418
  onDragEnd: handleDragEnd,
@@ -9309,10 +9421,8 @@ function DesignModeOverlay() {
9309
9421
  /* @__PURE__ */ jsx4(
9310
9422
  ElementHighlight,
9311
9423
  {
9312
- elements,
9313
- draggingNodeId: activeDrag?.nodeId ?? null,
9314
- activeEl: refs.activeElRef.current,
9315
- arrangeSelectedNodeId,
9424
+ selected: arrangeSelectedNodeId ? nodeMap.get(arrangeSelectedNodeId) ?? null : null,
9425
+ dragGuide,
9316
9426
  externalDragActive: externalDrag !== null,
9317
9427
  onResizeHandlePointerDown: handleResizeHandlePointerDown
9318
9428
  }