@archie/devtools 0.0.18 → 0.0.20

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.
@@ -4279,6 +4279,32 @@ function getEditorMeta(el) {
4279
4279
  }
4280
4280
  return writeFiberAwareCache(metaCache, el, selection, null);
4281
4281
  }
4282
+ function getEditorMetaOwnerFiber(el) {
4283
+ const selection = getFiberSelectionFromElement(el);
4284
+ let fiber = selection.fiber;
4285
+ while (fiber) {
4286
+ if (fiber.memoizedProps?.__editorMeta) return fiber;
4287
+ fiber = fiber.return;
4288
+ }
4289
+ return null;
4290
+ }
4291
+ function getSingleHostElement(owner) {
4292
+ const hosts = [];
4293
+ const stack = owner?.child ? [owner.child] : [];
4294
+ while (stack.length > 0) {
4295
+ const fiber = stack.pop();
4296
+ if (!fiber) continue;
4297
+ if (fiber.tag === 4) return null;
4298
+ if (typeof fiber.type === "string" && fiber.stateNode instanceof HTMLElement) {
4299
+ hosts.push(fiber.stateNode);
4300
+ if (hosts.length > 1) return null;
4301
+ } else if (fiber.child) {
4302
+ stack.push(fiber.child);
4303
+ }
4304
+ if (fiber.sibling) stack.push(fiber.sibling);
4305
+ }
4306
+ return hosts.length === 1 ? hosts[0] : null;
4307
+ }
4282
4308
  function findMetaOwnerElement(el) {
4283
4309
  let current = el;
4284
4310
  while (current) {
@@ -4901,9 +4927,22 @@ function buildNodeSelector(nodeId) {
4901
4927
  var LARGE_NODE_NEST_BLOCK_MIN_WIDTH = 520;
4902
4928
  var LARGE_NODE_NEST_BLOCK_MIN_HEIGHT = 260;
4903
4929
  var LARGE_NODE_NEST_BLOCK_MIN_AREA = 18e4;
4930
+ function resolveGrabFeedbackTarget(target, scannedByNodeId) {
4931
+ if (!(target instanceof Element)) return null;
4932
+ if (target.closest("[data-design-mode-ui]")) return null;
4933
+ const host = target.closest(`[${DND_NODE_ID_ATTR}]`);
4934
+ if (!host) return null;
4935
+ const nodeId = host.getAttribute(DND_NODE_ID_ATTR);
4936
+ const scanned = nodeId ? scannedByNodeId.get(nodeId) : null;
4937
+ if (!scanned || scanned.draggable !== false) return null;
4938
+ return scanned;
4939
+ }
4904
4940
  function isEligibleForDrag(sc) {
4941
+ if (sc.draggable === false) return false;
4905
4942
  const source = sc.eligibilitySource ?? "editor-meta";
4906
- return source === "editor-meta" || source === "force-allow" || source === "inferred-sortable-descendant";
4943
+ return source === "editor-meta" || source === "force-allow" || source === "inferred-sortable-descendant" || // RFC E2.4 P1: single host root of a kit-component usage (MetricCard,
4944
+ // Section, …) carrying the usage's projected meta.
4945
+ source === "projected-usage";
4907
4946
  }
4908
4947
  function isPointerInEdgeZone(px, py, guard) {
4909
4948
  return px <= guard || py <= guard || px >= window.innerWidth - guard || py >= window.innerHeight - guard;
@@ -5025,6 +5064,13 @@ function isInspectorEligible(el) {
5025
5064
  if (el.hasAttribute("data-dnd-force-allow")) return "force-allow";
5026
5065
  return null;
5027
5066
  }
5067
+ function projectUsageMeta(el) {
5068
+ if (getDirectEditorMeta(el)) return null;
5069
+ const owner = getEditorMetaOwnerFiber(el);
5070
+ if (!owner) return null;
5071
+ if (getSingleHostElement(owner) !== el) return null;
5072
+ return owner.memoizedProps?.__editorMeta ?? null;
5073
+ }
5028
5074
  function isRuntimeVisualInternal(el) {
5029
5075
  if (shouldIgnoreAsVirtualRuntime(el)) return true;
5030
5076
  const tag = el.tagName.toLowerCase();
@@ -5056,6 +5102,8 @@ function resolveStableNodeId(el, used) {
5056
5102
  if (persisted && !used.has(persisted)) return persisted;
5057
5103
  const metaId = getEditorMetaNodeId(el);
5058
5104
  if (metaId) return ensureUniqueNodeId(metaId, used);
5105
+ const projected = projectUsageMeta(el);
5106
+ if (projected) return ensureUniqueNodeId(projected.nodeId, used);
5059
5107
  return ensureUniqueNodeId(getRuntimeNodeId(el), used);
5060
5108
  }
5061
5109
  function getUniqueSelector(el) {
@@ -5147,9 +5195,31 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5147
5195
  continue;
5148
5196
  }
5149
5197
  const eligibilitySource = isInspectorEligible(el);
5150
- const inferredEligibility = eligibilitySource ?? inferEligibilityFromSortableParent(el);
5198
+ const inferredEligibility = eligibilitySource ?? inferEligibilityFromSortableParent(el) ?? (projectUsageMeta(el) ? "projected-usage" : null);
5151
5199
  if (inferredEligibility === null) {
5152
- results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5200
+ const childResults = scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts);
5201
+ results.push(...childResults);
5202
+ if (isFlexOrGridParent(el) && !seenResolvedHosts.has(el) && childResults.some((r) => r.draggable && r.element.parentElement === el)) {
5203
+ const nodeId2 = resolveStableNodeId(el, usedNodeIds);
5204
+ seenResolvedHosts.add(el);
5205
+ usedNodeIds.add(nodeId2);
5206
+ el.setAttribute(DND_NODE_ID_ATTR, nodeId2);
5207
+ const selector = getUniqueSelector(el);
5208
+ results.push({
5209
+ nodeId: nodeId2,
5210
+ element: el,
5211
+ selector,
5212
+ sourceMetaNodeId: null,
5213
+ resolvedHostNodeId: nodeId2,
5214
+ resolvedHostSelector: selector,
5215
+ resolutionKind: "direct-host",
5216
+ label: getLabel(el),
5217
+ rect: el.getBoundingClientRect(),
5218
+ containerStrategy: resolveContainerStrategy(el),
5219
+ eligibilitySource: "container-of-eligible",
5220
+ draggable: false
5221
+ });
5222
+ }
5153
5223
  continue;
5154
5224
  }
5155
5225
  const dragSurface = resolveDragSurface(el);
@@ -5172,10 +5242,12 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5172
5242
  const surfaceMeta = hostEl instanceof HTMLElement ? getDirectEditorMeta(hostEl) ?? void 0 : void 0;
5173
5243
  const ownerMeta = dragSurface.metaOwner && dragSurface.metaOwner instanceof HTMLElement ? getEditorMeta(dragSurface.metaOwner) : null;
5174
5244
  const resolvedSelector = getUniqueSelector(hostEl);
5245
+ const draggable = inferredEligibility === "force-allow" || !nodeId.startsWith("runtime:");
5175
5246
  results.push({
5176
5247
  nodeId,
5177
5248
  element: hostEl,
5178
5249
  selector: resolvedSelector,
5250
+ draggable,
5179
5251
  sourceMetaNodeId: ownerMeta?.nodeId ?? surfaceMeta?.nodeId ?? null,
5180
5252
  resolvedHostNodeId: nodeId,
5181
5253
  resolvedHostSelector: resolvedSelector,
@@ -5222,6 +5294,7 @@ function useElementScanner(enabled) {
5222
5294
  nodeId,
5223
5295
  element: el,
5224
5296
  selector,
5297
+ draggable: !nodeId.startsWith("runtime:"),
5225
5298
  sourceMetaNodeId: ownerMeta?.nodeId ?? surfaceMeta?.nodeId ?? null,
5226
5299
  resolvedHostNodeId: nodeId,
5227
5300
  resolvedHostSelector: selector,
@@ -8661,7 +8734,9 @@ function DesignModeOverlay() {
8661
8734
  const draggableElements = (0, import_react17.useMemo)(() => {
8662
8735
  if (elements.length === 0) return [];
8663
8736
  const projection = buildProjection(elements);
8664
- return collectDraggableAnchors(elements, projection, nodeMap);
8737
+ return collectDraggableAnchors(elements, projection, nodeMap).filter(
8738
+ (el) => el.draggable !== false
8739
+ );
8665
8740
  }, [elements, nodeMap]);
8666
8741
  const refs = useOverlayRefs(undoRequestId, redoRequestId);
8667
8742
  refs.elementsRef.current = elements;
@@ -8671,6 +8746,25 @@ function DesignModeOverlay() {
8671
8746
  for (const el of draggableElements) m.set(el.nodeId, el);
8672
8747
  refs.draggableNodeMapRef.current = m;
8673
8748
  }, [draggableElements]);
8749
+ const [grabFeedback, setGrabFeedback] = (0, import_react17.useState)(null);
8750
+ (0, import_react17.useEffect)(() => {
8751
+ if (!enabled) return;
8752
+ const onPointerDown = (e) => {
8753
+ const scanned = resolveGrabFeedbackTarget(
8754
+ e.target,
8755
+ refs.nodeMapRef.current
8756
+ );
8757
+ if (!scanned) return;
8758
+ setGrabFeedback({ x: e.clientX, y: e.clientY, at: Date.now() });
8759
+ };
8760
+ document.addEventListener("pointerdown", onPointerDown, true);
8761
+ return () => document.removeEventListener("pointerdown", onPointerDown, true);
8762
+ }, [enabled]);
8763
+ (0, import_react17.useEffect)(() => {
8764
+ if (!grabFeedback) return;
8765
+ const t = setTimeout(() => setGrabFeedback(null), 1400);
8766
+ return () => clearTimeout(t);
8767
+ }, [grabFeedback]);
8674
8768
  const resolveNodeElement = (0, import_react17.useCallback)((nodeId) => {
8675
8769
  const known = refs.nodeMapRef.current.get(nodeId)?.element;
8676
8770
  if (known) return known;
@@ -8882,6 +8976,29 @@ function DesignModeOverlay() {
8882
8976
  },
8883
8977
  el.nodeId
8884
8978
  )),
8979
+ grabFeedback ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
8980
+ "div",
8981
+ {
8982
+ "data-design-mode-ui": "true",
8983
+ style: {
8984
+ position: "fixed",
8985
+ left: grabFeedback.x,
8986
+ top: grabFeedback.y - 34,
8987
+ transform: "translateX(-50%)",
8988
+ zIndex: 2147483646,
8989
+ pointerEvents: "none",
8990
+ background: "rgba(23, 23, 23, 0.92)",
8991
+ color: "#fff",
8992
+ font: "500 11px/1.4 system-ui, sans-serif",
8993
+ padding: "4px 8px",
8994
+ borderRadius: 6,
8995
+ whiteSpace: "nowrap",
8996
+ boxShadow: "0 2px 8px rgba(0,0,0,0.25)"
8997
+ },
8998
+ children: "This element can't be moved"
8999
+ },
9000
+ grabFeedback.at
9001
+ ) : null,
8885
9002
  insideRect && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
8886
9003
  "div",
8887
9004
  {
@@ -4244,6 +4244,32 @@ function getEditorMeta(el) {
4244
4244
  }
4245
4245
  return writeFiberAwareCache(metaCache, el, selection, null);
4246
4246
  }
4247
+ function getEditorMetaOwnerFiber(el) {
4248
+ const selection = getFiberSelectionFromElement(el);
4249
+ let fiber = selection.fiber;
4250
+ while (fiber) {
4251
+ if (fiber.memoizedProps?.__editorMeta) return fiber;
4252
+ fiber = fiber.return;
4253
+ }
4254
+ return null;
4255
+ }
4256
+ function getSingleHostElement(owner) {
4257
+ const hosts = [];
4258
+ const stack = owner?.child ? [owner.child] : [];
4259
+ while (stack.length > 0) {
4260
+ const fiber = stack.pop();
4261
+ if (!fiber) continue;
4262
+ if (fiber.tag === 4) return null;
4263
+ if (typeof fiber.type === "string" && fiber.stateNode instanceof HTMLElement) {
4264
+ hosts.push(fiber.stateNode);
4265
+ if (hosts.length > 1) return null;
4266
+ } else if (fiber.child) {
4267
+ stack.push(fiber.child);
4268
+ }
4269
+ if (fiber.sibling) stack.push(fiber.sibling);
4270
+ }
4271
+ return hosts.length === 1 ? hosts[0] : null;
4272
+ }
4247
4273
  function findMetaOwnerElement(el) {
4248
4274
  let current = el;
4249
4275
  while (current) {
@@ -4866,9 +4892,22 @@ function buildNodeSelector(nodeId) {
4866
4892
  var LARGE_NODE_NEST_BLOCK_MIN_WIDTH = 520;
4867
4893
  var LARGE_NODE_NEST_BLOCK_MIN_HEIGHT = 260;
4868
4894
  var LARGE_NODE_NEST_BLOCK_MIN_AREA = 18e4;
4895
+ function resolveGrabFeedbackTarget(target, scannedByNodeId) {
4896
+ if (!(target instanceof Element)) return null;
4897
+ if (target.closest("[data-design-mode-ui]")) return null;
4898
+ const host = target.closest(`[${DND_NODE_ID_ATTR}]`);
4899
+ if (!host) return null;
4900
+ const nodeId = host.getAttribute(DND_NODE_ID_ATTR);
4901
+ const scanned = nodeId ? scannedByNodeId.get(nodeId) : null;
4902
+ if (!scanned || scanned.draggable !== false) return null;
4903
+ return scanned;
4904
+ }
4869
4905
  function isEligibleForDrag(sc) {
4906
+ if (sc.draggable === false) return false;
4870
4907
  const source = sc.eligibilitySource ?? "editor-meta";
4871
- return source === "editor-meta" || source === "force-allow" || source === "inferred-sortable-descendant";
4908
+ return source === "editor-meta" || source === "force-allow" || source === "inferred-sortable-descendant" || // RFC E2.4 P1: single host root of a kit-component usage (MetricCard,
4909
+ // Section, …) carrying the usage's projected meta.
4910
+ source === "projected-usage";
4872
4911
  }
4873
4912
  function isPointerInEdgeZone(px, py, guard) {
4874
4913
  return px <= guard || py <= guard || px >= window.innerWidth - guard || py >= window.innerHeight - guard;
@@ -4990,6 +5029,13 @@ function isInspectorEligible(el) {
4990
5029
  if (el.hasAttribute("data-dnd-force-allow")) return "force-allow";
4991
5030
  return null;
4992
5031
  }
5032
+ function projectUsageMeta(el) {
5033
+ if (getDirectEditorMeta(el)) return null;
5034
+ const owner = getEditorMetaOwnerFiber(el);
5035
+ if (!owner) return null;
5036
+ if (getSingleHostElement(owner) !== el) return null;
5037
+ return owner.memoizedProps?.__editorMeta ?? null;
5038
+ }
4993
5039
  function isRuntimeVisualInternal(el) {
4994
5040
  if (shouldIgnoreAsVirtualRuntime(el)) return true;
4995
5041
  const tag = el.tagName.toLowerCase();
@@ -5021,6 +5067,8 @@ function resolveStableNodeId(el, used) {
5021
5067
  if (persisted && !used.has(persisted)) return persisted;
5022
5068
  const metaId = getEditorMetaNodeId(el);
5023
5069
  if (metaId) return ensureUniqueNodeId(metaId, used);
5070
+ const projected = projectUsageMeta(el);
5071
+ if (projected) return ensureUniqueNodeId(projected.nodeId, used);
5024
5072
  return ensureUniqueNodeId(getRuntimeNodeId(el), used);
5025
5073
  }
5026
5074
  function getUniqueSelector(el) {
@@ -5112,9 +5160,31 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5112
5160
  continue;
5113
5161
  }
5114
5162
  const eligibilitySource = isInspectorEligible(el);
5115
- const inferredEligibility = eligibilitySource ?? inferEligibilityFromSortableParent(el);
5163
+ const inferredEligibility = eligibilitySource ?? inferEligibilityFromSortableParent(el) ?? (projectUsageMeta(el) ? "projected-usage" : null);
5116
5164
  if (inferredEligibility === null) {
5117
- results.push(...scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts));
5165
+ const childResults = scanElements(el, depth + 1, usedNodeIds, seenResolvedHosts);
5166
+ results.push(...childResults);
5167
+ if (isFlexOrGridParent(el) && !seenResolvedHosts.has(el) && childResults.some((r) => r.draggable && r.element.parentElement === el)) {
5168
+ const nodeId2 = resolveStableNodeId(el, usedNodeIds);
5169
+ seenResolvedHosts.add(el);
5170
+ usedNodeIds.add(nodeId2);
5171
+ el.setAttribute(DND_NODE_ID_ATTR, nodeId2);
5172
+ const selector = getUniqueSelector(el);
5173
+ results.push({
5174
+ nodeId: nodeId2,
5175
+ element: el,
5176
+ selector,
5177
+ sourceMetaNodeId: null,
5178
+ resolvedHostNodeId: nodeId2,
5179
+ resolvedHostSelector: selector,
5180
+ resolutionKind: "direct-host",
5181
+ label: getLabel(el),
5182
+ rect: el.getBoundingClientRect(),
5183
+ containerStrategy: resolveContainerStrategy(el),
5184
+ eligibilitySource: "container-of-eligible",
5185
+ draggable: false
5186
+ });
5187
+ }
5118
5188
  continue;
5119
5189
  }
5120
5190
  const dragSurface = resolveDragSurface(el);
@@ -5137,10 +5207,12 @@ function scanElements(root, depth = 0, usedNodeIds = /* @__PURE__ */ new Set(),
5137
5207
  const surfaceMeta = hostEl instanceof HTMLElement ? getDirectEditorMeta(hostEl) ?? void 0 : void 0;
5138
5208
  const ownerMeta = dragSurface.metaOwner && dragSurface.metaOwner instanceof HTMLElement ? getEditorMeta(dragSurface.metaOwner) : null;
5139
5209
  const resolvedSelector = getUniqueSelector(hostEl);
5210
+ const draggable = inferredEligibility === "force-allow" || !nodeId.startsWith("runtime:");
5140
5211
  results.push({
5141
5212
  nodeId,
5142
5213
  element: hostEl,
5143
5214
  selector: resolvedSelector,
5215
+ draggable,
5144
5216
  sourceMetaNodeId: ownerMeta?.nodeId ?? surfaceMeta?.nodeId ?? null,
5145
5217
  resolvedHostNodeId: nodeId,
5146
5218
  resolvedHostSelector: resolvedSelector,
@@ -5187,6 +5259,7 @@ function useElementScanner(enabled) {
5187
5259
  nodeId,
5188
5260
  element: el,
5189
5261
  selector,
5262
+ draggable: !nodeId.startsWith("runtime:"),
5190
5263
  sourceMetaNodeId: ownerMeta?.nodeId ?? surfaceMeta?.nodeId ?? null,
5191
5264
  resolvedHostNodeId: nodeId,
5192
5265
  resolvedHostSelector: selector,
@@ -8626,7 +8699,9 @@ function DesignModeOverlay() {
8626
8699
  const draggableElements = useMemo6(() => {
8627
8700
  if (elements.length === 0) return [];
8628
8701
  const projection = buildProjection(elements);
8629
- return collectDraggableAnchors(elements, projection, nodeMap);
8702
+ return collectDraggableAnchors(elements, projection, nodeMap).filter(
8703
+ (el) => el.draggable !== false
8704
+ );
8630
8705
  }, [elements, nodeMap]);
8631
8706
  const refs = useOverlayRefs(undoRequestId, redoRequestId);
8632
8707
  refs.elementsRef.current = elements;
@@ -8636,6 +8711,25 @@ function DesignModeOverlay() {
8636
8711
  for (const el of draggableElements) m.set(el.nodeId, el);
8637
8712
  refs.draggableNodeMapRef.current = m;
8638
8713
  }, [draggableElements]);
8714
+ const [grabFeedback, setGrabFeedback] = useState6(null);
8715
+ useEffect12(() => {
8716
+ if (!enabled) return;
8717
+ const onPointerDown = (e) => {
8718
+ const scanned = resolveGrabFeedbackTarget(
8719
+ e.target,
8720
+ refs.nodeMapRef.current
8721
+ );
8722
+ if (!scanned) return;
8723
+ setGrabFeedback({ x: e.clientX, y: e.clientY, at: Date.now() });
8724
+ };
8725
+ document.addEventListener("pointerdown", onPointerDown, true);
8726
+ return () => document.removeEventListener("pointerdown", onPointerDown, true);
8727
+ }, [enabled]);
8728
+ useEffect12(() => {
8729
+ if (!grabFeedback) return;
8730
+ const t = setTimeout(() => setGrabFeedback(null), 1400);
8731
+ return () => clearTimeout(t);
8732
+ }, [grabFeedback]);
8639
8733
  const resolveNodeElement = useCallback8((nodeId) => {
8640
8734
  const known = refs.nodeMapRef.current.get(nodeId)?.element;
8641
8735
  if (known) return known;
@@ -8847,6 +8941,29 @@ function DesignModeOverlay() {
8847
8941
  },
8848
8942
  el.nodeId
8849
8943
  )),
8944
+ grabFeedback ? /* @__PURE__ */ jsx4(
8945
+ "div",
8946
+ {
8947
+ "data-design-mode-ui": "true",
8948
+ style: {
8949
+ position: "fixed",
8950
+ left: grabFeedback.x,
8951
+ top: grabFeedback.y - 34,
8952
+ transform: "translateX(-50%)",
8953
+ zIndex: 2147483646,
8954
+ pointerEvents: "none",
8955
+ background: "rgba(23, 23, 23, 0.92)",
8956
+ color: "#fff",
8957
+ font: "500 11px/1.4 system-ui, sans-serif",
8958
+ padding: "4px 8px",
8959
+ borderRadius: 6,
8960
+ whiteSpace: "nowrap",
8961
+ boxShadow: "0 2px 8px rgba(0,0,0,0.25)"
8962
+ },
8963
+ children: "This element can't be moved"
8964
+ },
8965
+ grabFeedback.at
8966
+ ) : null,
8850
8967
  insideRect && /* @__PURE__ */ jsx4(
8851
8968
  "div",
8852
8969
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archie/devtools",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "DevTools for Archie generated applications - Route synchronization and editor communication",
5
5
  "repository": {
6
6
  "type": "git",
@@ -73,4 +73,4 @@
73
73
  "react-dom": ">=18.0.0",
74
74
  "react-router-dom": ">=6.4.0"
75
75
  }
76
- }
76
+ }