@archie/devtools 0.1.3 → 0.1.5

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.
@@ -7,6 +7,15 @@
7
7
  var _rawDocumentQuerySelectorAll = Document.prototype.querySelectorAll
8
8
  var _documentQuerySelectorsPatched = false
9
9
 
10
+ // Design-mode (arrange) overlay elements — HitArea, resize handles,
11
+ // drop-line, etc. — carry this attribute. Single source of truth so every
12
+ // check below (isInspectorElement, inspectorClickHandler, the dblclick
13
+ // descend guard) can never desync from a rename.
14
+ var DESIGN_MODE_UI_SELECTOR = "[data-design-mode-ui]"
15
+ // The grab HitArea specifically (DesignModeOverlay.tsx's `HitArea`) — the
16
+ // ONLY design-mode-ui element the dblclick descend guard below exempts.
17
+ var DESIGN_MODE_HIT_AREA_SELECTOR = '[data-design-mode-ui="hit-area"]'
18
+
10
19
  function runInspector() {
11
20
  var INSPECTOR_PROBLEMATIC_SELECTOR_PATTERNS = [
12
21
  /\/\d+/,
@@ -987,6 +996,176 @@
987
996
  return canonicalTarget
988
997
  }
989
998
 
999
+ // ── "Visual unit" climb (ARCH — hit-testing bounces to a full-bleed
1000
+ // child) ─────────────────────────────────────────────────────────────
1001
+ // A click on a card's padding hits the CARD's only element child (a
1002
+ // `flex flex-col p-6` div that fills 100% of the card's box), never the
1003
+ // card itself — the child is topmost at that point AND is the first
1004
+ // ancestor-or-self with its own source signature, so the walk above
1005
+ // returns it immediately. That makes the card's own border/shadow/
1006
+ // padding permanently unreachable by click, and a drag started there
1007
+ // moves only the inner div, leaving an empty bordered shell behind.
1008
+ //
1009
+ // The fix is a SEPARATE pass, applied only where a user interaction
1010
+ // commits a SELECTION (inspectorClickHandler, and the synthetic
1011
+ // mousedown Escape/double-click dispatch below) — never inside
1012
+ // `resolveExactInspectTarget`/`getElementFromPoint` themselves. Those
1013
+ // two stay leaf-exact on purpose: hover highlighting and shared/
1014
+ // mirrored-instance signature matching (`resolveSharedTargets`,
1015
+ // `findElementByInspectorSource`, APPLY_STYLE_CHANGE's mutation
1016
+ // resolution) key off the PRECISE host a signature names, and two
1017
+ // nested hosts with their own distinct metas (a real, if less common,
1018
+ // shape than the wrapper case below) must stay distinguishable there.
1019
+ //
1020
+ // Here, climb through "single-child wrapper" ancestors — each one
1021
+ // instrumented (its own direct __editorMeta), in the SAME file, not
1022
+ // kit-origin/chrome, not the screen root, whose ONLY element child is
1023
+ // the node below and whose box visually contains it — and return the
1024
+ // OUTERMOST one. A leaf clicked directly (the `<h3>` among two
1025
+ // siblings) never enters the climb: its parent has more than one
1026
+ // element child, so the loop breaks on the first iteration.
1027
+ var STRUCTURAL_COMPONENT_NAME_RE = /(?:Page|Provider|RootLayout)$/
1028
+
1029
+ function isStructuralComponentNameForUnit(name) {
1030
+ return !!name && STRUCTURAL_COMPONENT_NAME_RE.test(name)
1031
+ }
1032
+
1033
+ // Vanilla twin of isHighLevelStructuralElement (structural-policy.ts) —
1034
+ // the "screen root" a visual-unit climb must never swallow.
1035
+ function isScreenRootWrapperElement(el) {
1036
+ if (!el || el.nodeType !== 1) return true
1037
+ if (el.hasAttribute && el.hasAttribute("data-dnd-structural")) return true
1038
+ var tag = String(el.tagName || "").toLowerCase()
1039
+ if (tag === "html" || tag === "body") return true
1040
+ if (el.id === "root") return true
1041
+ var parent = el.parentElement
1042
+ if (
1043
+ tag === "main" &&
1044
+ (parent === document.body || (parent && parent.id === "root"))
1045
+ ) {
1046
+ return true
1047
+ }
1048
+ return false
1049
+ }
1050
+
1051
+ function getSoleElementChild(el) {
1052
+ var only = null
1053
+ var kids = el.children
1054
+ for (var i = 0; i < kids.length; i++) {
1055
+ if (only) return null
1056
+ only = kids[i]
1057
+ }
1058
+ return only
1059
+ }
1060
+
1061
+ // "kit-internal" (vendor template, ARCH-883) or "chrome" (app shell) —
1062
+ // neither is a legitimate visual-unit wrapper: climbing into one would
1063
+ // select vendor/shell scaffolding the user never asked to move.
1064
+ function isKitOrChromeMeta(meta) {
1065
+ return (
1066
+ !!meta &&
1067
+ (isTemplateFrame(meta) ||
1068
+ meta.isKitOrigin === true ||
1069
+ meta.isChrome === true)
1070
+ )
1071
+ }
1072
+
1073
+ // The file this element's OWN identity is written in — usage
1074
+ // projection first (same precedence as getExactSourceSignatureFromElement),
1075
+ // then direct/owner fiber meta, then the legacy source-ref attribute.
1076
+ function getOwnSourceFileForElement(element) {
1077
+ var usageMeta = projectUsageMetaFromElement(element)
1078
+ if (usageMeta && usageMeta.file) return usageMeta.file
1079
+
1080
+ var directMeta = getDirectEditorMetaFromElement(element)
1081
+ if (directMeta && directMeta.file) return directMeta.file
1082
+
1083
+ var ownerMeta = getOwnerEditorMetaFromElement(element)
1084
+ if (ownerMeta && ownerMeta.file) return ownerMeta.file
1085
+
1086
+ if (
1087
+ element &&
1088
+ typeof element.getAttribute === "function" &&
1089
+ element.hasAttribute(ARCHIE_SOURCE_REF_ATTR)
1090
+ ) {
1091
+ var raw = element.getAttribute(ARCHIE_SOURCE_REF_ATTR) || ""
1092
+ var file = raw.split("::")[0]
1093
+ if (file) return file
1094
+ }
1095
+
1096
+ return ""
1097
+ }
1098
+
1099
+ // Loose "fits inside" check (1px tolerance for subpixel rounding) — the
1100
+ // wrapper's box must visually contain the child's, not merely equal it
1101
+ // (padding/border on the wrapper is expected and fine).
1102
+ function rectContainsForUnit(outer, inner) {
1103
+ var eps = 1
1104
+ return (
1105
+ inner.left >= outer.left - eps &&
1106
+ inner.top >= outer.top - eps &&
1107
+ inner.right <= outer.right + eps &&
1108
+ inner.bottom <= outer.bottom + eps
1109
+ )
1110
+ }
1111
+
1112
+ // The identity a wrapper ancestor climbs AS. A plain instrumented
1113
+ // wrapper climbs as its own direct meta. A kit-rendered wrapper (a
1114
+ // `Kit.Card` host div) carries kit-TEMPLATE direct meta instead, so it
1115
+ // falls back to the projected USAGE one level up in the screen file —
1116
+ // but that usage can itself be kit/chrome, so it's re-checked below.
1117
+ function getVisualUnitMetaForElement(el) {
1118
+ var directMeta = getDirectEditorMetaFromElement(el)
1119
+ if (!directMeta) return null
1120
+ if (!isKitOrChromeMeta(directMeta)) return directMeta
1121
+
1122
+ // The projected USAGE can itself be kit/chrome (e.g. a Kit.Card host
1123
+ // used inside a chrome file) — re-check it so the climb never enters
1124
+ // chrome scaffolding through a kit-rendered wrapper.
1125
+ var usageMeta = projectUsageMetaFromElement(el)
1126
+ return usageMeta && !isKitOrChromeMeta(usageMeta) ? usageMeta : null
1127
+ }
1128
+
1129
+ function climbVisualUnitChain(leaf) {
1130
+ if (!leaf) return leaf
1131
+ var currentFile = getOwnSourceFileForElement(leaf)
1132
+ if (!currentFile) return leaf
1133
+
1134
+ var current = leaf
1135
+ while (true) {
1136
+ var parent = current.parentElement
1137
+ if (!parent) break
1138
+ if (parent === document.body || parent === document.documentElement) break
1139
+ if (isInspectorElement(parent)) break
1140
+ if (isScreenRootWrapperElement(parent)) break
1141
+ if (getSoleElementChild(parent) !== current) break
1142
+
1143
+ var parentMeta = getVisualUnitMetaForElement(parent)
1144
+ if (!parentMeta) break
1145
+ if (!parentMeta.file || parentMeta.file !== currentFile) break
1146
+ if (isStructuralComponentNameForUnit(parentMeta.componentName)) break
1147
+
1148
+ try {
1149
+ if (
1150
+ !rectContainsForUnit(
1151
+ parent.getBoundingClientRect(),
1152
+ current.getBoundingClientRect()
1153
+ )
1154
+ ) {
1155
+ break
1156
+ }
1157
+ } catch (rectError) {
1158
+ // Containment couldn't be verified — never climb into an
1159
+ // unverified parent; stop here (current stays the last
1160
+ // confirmed-contained unit), same as a failed containment check.
1161
+ break
1162
+ }
1163
+
1164
+ current = parent
1165
+ }
1166
+ return current
1167
+ }
1168
+
990
1169
  function resolveExactInspectTarget(element) {
991
1170
  var normalizedTarget = normalizeInspectableTarget(element)
992
1171
  if (
@@ -1031,6 +1210,119 @@
1031
1210
  return resolveCoarseInspectTarget(normalizedTarget)
1032
1211
  }
1033
1212
 
1213
+ // Nearest real DOM ancestor above `el` that carries its own source
1214
+ // signature — the "select parent" step of visual-unit navigation
1215
+ // (Escape). Climbed to its own outer unit so Escape always lands on a
1216
+ // full unit boundary, the same guarantee a click gives.
1217
+ function findParentUnitElement(el) {
1218
+ var current = el ? el.parentElement : null
1219
+ while (
1220
+ current &&
1221
+ current !== document.body &&
1222
+ current !== document.documentElement
1223
+ ) {
1224
+ if (!isInspectorElement(current) && getExactSourceSignatureFromElement(current)) {
1225
+ return climbVisualUnitChain(current)
1226
+ }
1227
+ current = current.parentElement
1228
+ }
1229
+ return null
1230
+ }
1231
+
1232
+ // The point-hit stack at (x, y), topmost first. Prefers elementsFromPoint
1233
+ // (so a topmost overlay, e.g. the design-mode grab overlay, doesn't hide
1234
+ // the real element beneath it) and falls back to the single-element
1235
+ // elementFromPoint where the multi-element API isn't available.
1236
+ function collectPointHitList(x, y) {
1237
+ try {
1238
+ if (typeof document.elementsFromPoint === "function") {
1239
+ return document.elementsFromPoint(x, y) || []
1240
+ }
1241
+ } catch (_listError) {
1242
+ // fall through to the single-element lookup below
1243
+ }
1244
+ try {
1245
+ var single = document.elementFromPoint(x, y)
1246
+ return single ? [single] : []
1247
+ } catch (_pointError) {
1248
+ return []
1249
+ }
1250
+ }
1251
+
1252
+ // The direct child of `container` that sits on the ancestor line to
1253
+ // (x, y) — the "select child toward the point" step (double click).
1254
+ // Once a unit is selected, the full-coverage design-mode grab overlay
1255
+ // ([data-design-mode-ui]) sits topmost at that point, so a plain
1256
+ // elementFromPoint would just return the overlay itself — this walks
1257
+ // the hit stack and skips inspector/overlay elements to find the real
1258
+ // element underneath, exactly like getElementFromPoint does for hover.
1259
+ function findImmediateChildTowardPoint(container, x, y) {
1260
+ var list = collectPointHitList(x, y)
1261
+ var atPoint = null
1262
+ for (var i = 0; i < list.length; i++) {
1263
+ if (!isInspectorElement(list[i])) {
1264
+ atPoint = list[i]
1265
+ break
1266
+ }
1267
+ }
1268
+ if (!atPoint) return null
1269
+ if (!container.contains(atPoint) || atPoint === container) return null
1270
+
1271
+ var current = atPoint
1272
+ while (current && current.parentElement !== container) {
1273
+ current = current.parentElement
1274
+ }
1275
+ if (!current || current.parentElement !== container) return null
1276
+ return current
1277
+ }
1278
+
1279
+ // Consumed once by inspectorClickHandler, immediately below: a
1280
+ // double-click DESCEND already picked the exact inner element the user
1281
+ // asked for (one level down from the current unit, toward the click) —
1282
+ // re-running the climb on that synthetic mousedown would immediately
1283
+ // walk it straight back up to the same outer unit it just left. The
1284
+ // ASCEND direction (Escape) never sets this: findParentUnitElement
1285
+ // already returns a fully-climbed outer unit, so climbing it again is
1286
+ // a no-op anyway.
1287
+ var suppressUnitClimbOnce = false
1288
+
1289
+ // Reuses the exact pattern DesignModeOverlay's HitArea bypass already
1290
+ // relies on (see hit-area-click-routing.test.tsx): a synthetic
1291
+ // `mousedown` at the element's own center runs inspectorClickHandler's
1292
+ // full selection flow (ELEMENT_SELECTED, badges, design-mode bridge)
1293
+ // instead of a local, partial store flip.
1294
+ function dispatchSyntheticSelect(el, suppressClimb) {
1295
+ if (!el) return
1296
+ var rect
1297
+ try {
1298
+ rect = el.getBoundingClientRect()
1299
+ } catch (rectError) {
1300
+ // Never got to dispatch — the flag must never be set for a
1301
+ // dispatch that didn't happen.
1302
+ return
1303
+ }
1304
+ var cx = rect.left + rect.width / 2
1305
+ var cy = rect.top + rect.height / 2
1306
+
1307
+ // Set the flag as late as possible (right before the dispatch that
1308
+ // consumes it) and guarantee it never stays stuck `true` — whether
1309
+ // dispatchEvent runs inspectorClickHandler to completion (which
1310
+ // consumes it itself) or throws before/along the way.
1311
+ if (suppressClimb) suppressUnitClimbOnce = true
1312
+ try {
1313
+ el.dispatchEvent(
1314
+ new MouseEvent("mousedown", {
1315
+ bubbles: true,
1316
+ cancelable: true,
1317
+ clientX: cx,
1318
+ clientY: cy,
1319
+ })
1320
+ )
1321
+ } finally {
1322
+ suppressUnitClimbOnce = false
1323
+ }
1324
+ }
1325
+
1034
1326
  function resolveSharedGroupSignature(element) {
1035
1327
  var inspectTarget = resolveExactInspectTarget(element)
1036
1328
  if (!inspectTarget) return ""
@@ -1941,7 +2233,7 @@
1941
2233
  // drop-line, etc. These belong to the unified visual editor and must
1942
2234
  // pass mousedown/click events through to React (dnd-kit) instead of
1943
2235
  // being treated as inspect targets.
1944
- (element.closest && element.closest("[data-design-mode-ui]"))
2236
+ (element.closest && element.closest(DESIGN_MODE_UI_SELECTOR))
1945
2237
  )
1946
2238
  }
1947
2239
 
@@ -5522,6 +5814,11 @@
5522
5814
  // Selection handler: MUST run on mousedown before blockEvent. We register at load time (before
5523
5815
  // START_INSPECTION), so we run first in capture phase; we stopImmediatePropagation so blockEvent never runs.
5524
5816
  const inspectorClickHandler = function (e) {
5817
+ // Consumed exactly once per mousedown, regardless of any early
5818
+ // return below — see suppressUnitClimbOnce's docstring above.
5819
+ var climbSuppressed = suppressUnitClimbOnce
5820
+ suppressUnitClimbOnce = false
5821
+
5525
5822
  if (!isInspecting) return
5526
5823
 
5527
5824
  // If the event originated on a design-mode (arrange) overlay element —
@@ -5533,7 +5830,7 @@
5533
5830
  if (
5534
5831
  e.target &&
5535
5832
  typeof e.target.closest === "function" &&
5536
- e.target.closest("[data-design-mode-ui]")
5833
+ e.target.closest(DESIGN_MODE_UI_SELECTOR)
5537
5834
  ) {
5538
5835
  return
5539
5836
  }
@@ -5554,6 +5851,12 @@
5554
5851
  return
5555
5852
  }
5556
5853
 
5854
+ // "Visual unit" climb: a click on a full-bleed single-child wrapper
5855
+ // (a card's padding div) selects the OUTERMOST wrapper of that 1:1
5856
+ // chain (the card itself), not the innermost hit. See
5857
+ // climbVisualUnitChain's docstring above.
5858
+ target = climbSuppressed ? target : climbVisualUnitChain(target)
5859
+
5557
5860
  // Block native behavior
5558
5861
  e.preventDefault()
5559
5862
  e.stopPropagation()
@@ -5954,6 +6257,44 @@
5954
6257
  // stopImmediatePropagation.
5955
6258
  document.addEventListener("mousedown", inspectorClickHandler, true)
5956
6259
 
6260
+ // Visual-unit navigation, "descend" step: double click steps ONE level
6261
+ // down from the current selection, toward wherever the double click
6262
+ // landed — the reverse of the climb in resolveExactInspectTarget. Runs
6263
+ // in capture, BEFORE setupEventBlockers' own "dblclick" blocker (both
6264
+ // are registered here, at load time, ahead of any START_INSPECTION),
6265
+ // so stopImmediatePropagation below reliably wins the race.
6266
+ document.addEventListener(
6267
+ "dblclick",
6268
+ function (e) {
6269
+ if (!isInspecting || !isElementSelected || !selectedElement) return
6270
+ // Only the grab HitArea is exempted — it covers the whole selected
6271
+ // unit, so it (not the real element underneath) is what `e.target`
6272
+ // names for a double click inside it. The 8px resize band is NOT a
6273
+ // separate element — it's just the outer edge of that same HitArea
6274
+ // div (detected by pointer position against its rect), so it's
6275
+ // covered by this same exemption and a dblclick there DOES
6276
+ // descend. Every OTHER design-mode-ui element (toolbar, output
6277
+ // modal, …) still bails here, same as any other inspector UI.
6278
+ if (
6279
+ isInspectorElement(e.target) &&
6280
+ !(e.target && e.target.closest && e.target.closest(DESIGN_MODE_HIT_AREA_SELECTOR))
6281
+ ) {
6282
+ return
6283
+ }
6284
+ var child = findImmediateChildTowardPoint(
6285
+ selectedElement,
6286
+ e.clientX,
6287
+ e.clientY
6288
+ )
6289
+ if (!child) return
6290
+ e.preventDefault()
6291
+ e.stopPropagation()
6292
+ e.stopImmediatePropagation()
6293
+ dispatchSyntheticSelect(child, true)
6294
+ },
6295
+ true
6296
+ )
6297
+
5957
6298
  // Block clicks when element is selected but not inspecting
5958
6299
  document.addEventListener(
5959
6300
  "click",
@@ -5971,30 +6312,59 @@
5971
6312
  true
5972
6313
  )
5973
6314
 
5974
- document.addEventListener("keydown", function (e) {
5975
- if (e.key === "Escape" && (isInspecting || isElementSelected)) {
5976
- isInspecting = false
5977
- isElementSelected = false
5978
- removeEventBlockers()
5979
- if (document.body) {
5980
- document.body.style.cursor = ""
5981
- document.body.style.userSelect = ""
6315
+ // Capture phase, matching setupEventBlockers' own capture-phase
6316
+ // "keydown" blocker: a bubble-phase listener registered here would
6317
+ // never run once isInspecting is true — that blocker's
6318
+ // stopImmediatePropagation wins the target's capture-listeners-first
6319
+ // ordering regardless of add order (also matches
6320
+ // onContextMenuDismissKey's own capture registration above).
6321
+ document.addEventListener(
6322
+ "keydown",
6323
+ function (e) {
6324
+ if (e.key !== "Escape") return
6325
+
6326
+ // Visual-unit navigation, "ascend" step: Escape steps ONE level up
6327
+ // from the current selection (to the nearest ancestor with its own
6328
+ // identity, climbed to ITS outer unit) instead of exiting selection
6329
+ // entirely. Falls through to the exit-all behavior below only once
6330
+ // there is no further ancestor to select — e.g. nothing is
6331
+ // selected, or the selection is already the outermost reachable
6332
+ // unit.
6333
+ if (isInspecting && isElementSelected && selectedElement) {
6334
+ var parentUnit = findParentUnitElement(selectedElement)
6335
+ if (parentUnit) {
6336
+ e.preventDefault()
6337
+ e.stopImmediatePropagation()
6338
+ dispatchSyntheticSelect(parentUnit)
6339
+ return
6340
+ }
5982
6341
  }
5983
- removeHighlight()
5984
- removeSelectedBadge()
5985
- selectedElements.forEach(function (el) {
5986
- if (el) removeInspectorStyles(el)
5987
- })
5988
- selectedElement = null
5989
- selectedElements = []
5990
- window._inspectorSelectedElement = null
5991
- if (window.__ARCHIE_INSPECTOR__)
5992
- window.__ARCHIE_INSPECTOR__.selectedElement = null
5993
- updateSharedHighlights()
5994
- publishSelectionToDesignMode()
5995
- postMessageToParent({ type: "INSPECTION_CANCELLED" })
5996
- }
5997
- })
6342
+
6343
+ if (isInspecting || isElementSelected) {
6344
+ isInspecting = false
6345
+ isElementSelected = false
6346
+ removeEventBlockers()
6347
+ if (document.body) {
6348
+ document.body.style.cursor = ""
6349
+ document.body.style.userSelect = ""
6350
+ }
6351
+ removeHighlight()
6352
+ removeSelectedBadge()
6353
+ selectedElements.forEach(function (el) {
6354
+ if (el) removeInspectorStyles(el)
6355
+ })
6356
+ selectedElement = null
6357
+ selectedElements = []
6358
+ window._inspectorSelectedElement = null
6359
+ if (window.__ARCHIE_INSPECTOR__)
6360
+ window.__ARCHIE_INSPECTOR__.selectedElement = null
6361
+ updateSharedHighlights()
6362
+ publishSelectionToDesignMode()
6363
+ postMessageToParent({ type: "INSPECTION_CANCELLED" })
6364
+ }
6365
+ },
6366
+ true
6367
+ )
5998
6368
  })()
5999
6369
  }
6000
6370
  if (typeof window !== "undefined") {