@treelocator/runtime 0.4.1 → 0.4.6

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.
Files changed (69) hide show
  1. package/dist/_generated_styles.js +115 -55
  2. package/dist/adapters/getElementInfo.js +3 -17
  3. package/dist/adapters/getParentsPath.js +3 -33
  4. package/dist/adapters/getTree.js +3 -33
  5. package/dist/adapters/jsx/getJSXComponentBoundingBox.js +0 -1
  6. package/dist/adapters/jsx/jsxAdapter.js +0 -3
  7. package/dist/adapters/jsx/runtimeStore.js +0 -12
  8. package/dist/adapters/react/reactAdapter.js +0 -8
  9. package/dist/adapters/resolveAdapter.d.ts +8 -0
  10. package/dist/adapters/resolveAdapter.js +28 -0
  11. package/dist/adapters/vue/vueAdapter.js +0 -14
  12. package/dist/browserApi.d.ts +1 -1
  13. package/dist/browserApi.js +8 -10
  14. package/dist/components/MaybeOutline.d.ts +1 -0
  15. package/dist/components/MaybeOutline.js +38 -29
  16. package/dist/components/Outline.d.ts +1 -0
  17. package/dist/components/Outline.js +20 -16
  18. package/dist/components/Runtime.js +30 -18
  19. package/dist/dejitter/recorder.js +0 -1
  20. package/dist/functions/formatAncestryChain.d.ts +7 -0
  21. package/dist/functions/formatAncestryChain.js +25 -0
  22. package/dist/functions/formatAncestryChain.test.js +133 -1
  23. package/dist/functions/getUsableName.js +0 -21
  24. package/dist/functions/isCombinationModifiersPressed.js +6 -2
  25. package/dist/output.css +10 -40
  26. package/dist/types/types.d.ts +1 -32
  27. package/package.json +1 -1
  28. package/src/_generated_styles.ts +115 -55
  29. package/src/adapters/getElementInfo.tsx +3 -23
  30. package/src/adapters/getParentsPath.tsx +3 -42
  31. package/src/adapters/getTree.tsx +3 -42
  32. package/src/adapters/jsx/getJSXComponentBoundingBox.ts +0 -1
  33. package/src/adapters/jsx/jsxAdapter.ts +0 -2
  34. package/src/adapters/jsx/runtimeStore.ts +0 -11
  35. package/src/adapters/react/reactAdapter.ts +0 -7
  36. package/src/adapters/resolveAdapter.ts +38 -0
  37. package/src/adapters/vue/vueAdapter.ts +0 -14
  38. package/src/browserApi.ts +9 -12
  39. package/src/components/MaybeOutline.tsx +4 -2
  40. package/src/components/Outline.tsx +2 -1
  41. package/src/components/Runtime.tsx +27 -18
  42. package/src/dejitter/recorder.ts +43 -44
  43. package/src/functions/formatAncestryChain.test.ts +74 -0
  44. package/src/functions/formatAncestryChain.ts +28 -0
  45. package/src/functions/getUsableName.ts +0 -21
  46. package/src/functions/isCombinationModifiersPressed.ts +5 -2
  47. package/src/types/types.ts +1 -32
  48. package/src/adapters/react/fiberToSimple.tsx +0 -72
  49. package/src/adapters/react/gatherFiberRoots.tsx +0 -36
  50. package/src/adapters/react/makeFiberId.tsx +0 -19
  51. package/src/adapters/react/searchDevtoolsRenderersForClosestTarget.ts +0 -15
  52. package/src/components/Button.tsx +0 -14
  53. package/src/components/ComponentOutline.tsx +0 -98
  54. package/src/components/SimpleNodeOutline.tsx +0 -27
  55. package/src/components/Tooltip.tsx +0 -28
  56. package/src/functions/cropPath.test.ts +0 -18
  57. package/src/functions/cropPath.ts +0 -12
  58. package/src/functions/evalTemplate.test.ts +0 -12
  59. package/src/functions/evalTemplate.ts +0 -8
  60. package/src/functions/findNames.ts +0 -20
  61. package/src/functions/getBoundingRect.tsx +0 -11
  62. package/src/functions/getComposedBoundingBox.tsx +0 -25
  63. package/src/functions/getIdsOnPathToRoot.tsx +0 -21
  64. package/src/functions/getMultipleElementsBoundingBox.tsx +0 -25
  65. package/src/functions/getPathToParent.tsx +0 -17
  66. package/src/functions/getUsableFileName.test.tsx +0 -24
  67. package/src/functions/getUsableFileName.tsx +0 -19
  68. package/src/functions/transformPath.test.ts +0 -28
  69. package/src/functions/transformPath.ts +0 -7
@@ -1,11 +1,11 @@
1
1
  import { template as _$template } from "solid-js/web";
2
+ import { className as _$className } from "solid-js/web";
2
3
  import { effect as _$effect } from "solid-js/web";
3
4
  import { insert as _$insert } from "solid-js/web";
4
5
  import { setStyleProperty as _$setStyleProperty } from "solid-js/web";
5
6
  import { createComponent as _$createComponent } from "solid-js/web";
6
7
  import { memo as _$memo } from "solid-js/web";
7
- var _tmpl$ = /*#__PURE__*/_$template(`<div><div class="fixed rounded border border-solid border-amber-500"style=z-index:2></div><div class="fixed text-xs font-medium rounded-md"style="z-index:3;box-shadow:0 4px 16px rgba(0, 0, 0, 0.2);font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;letter-spacing:0.01em">`),
8
- _tmpl$2 = /*#__PURE__*/_$template(`<div><div class="fixed rounded border border-solid border-gray-500"style=z-index:2></div><div class="fixed text-xs font-medium rounded-md"style="z-index:3;box-shadow:0 4px 16px rgba(0, 0, 0, 0.2);font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;letter-spacing:0.01em">`);
8
+ var _tmpl$ = /*#__PURE__*/_$template(`<div><div style=z-index:2></div><div class="fixed text-xs font-medium rounded-md"style="z-index:3;box-shadow:0 4px 16px rgba(0, 0, 0, 0.2);font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;letter-spacing:0.01em">`);
9
9
  import { createMemo } from "solid-js";
10
10
  import { getElementInfo } from "../adapters/getElementInfo";
11
11
  import { Outline } from "./Outline";
@@ -37,6 +37,9 @@ export function MaybeOutline(props) {
37
37
  },
38
38
  get targets() {
39
39
  return props.targets;
40
+ },
41
+ get dashed() {
42
+ return props.dashed;
40
43
  }
41
44
  }) : _$memo(() => !!phoenixInfo())() ? (() => {
42
45
  var _el$ = _tmpl$(),
@@ -48,18 +51,20 @@ export function MaybeOutline(props) {
48
51
  _$setStyleProperty(_el$3, "border", "1px solid rgba(255, 255, 255, 0.15)");
49
52
  _$insert(_el$3, () => phoenixInfo().filter(sc => sc.type === "component").map(sc => sc.name.split(".").pop()).join(" > "));
50
53
  _$effect(_p$ => {
51
- var _v$ = box().x + "px",
52
- _v$2 = box().y + "px",
53
- _v$3 = box().width + "px",
54
- _v$4 = box().height + "px",
55
- _v$5 = box().x + 4 + "px",
56
- _v$6 = box().y + 4 + "px";
57
- _v$ !== _p$.e && _$setStyleProperty(_el$2, "left", _p$.e = _v$);
58
- _v$2 !== _p$.t && _$setStyleProperty(_el$2, "top", _p$.t = _v$2);
59
- _v$3 !== _p$.a && _$setStyleProperty(_el$2, "width", _p$.a = _v$3);
60
- _v$4 !== _p$.o && _$setStyleProperty(_el$2, "height", _p$.o = _v$4);
61
- _v$5 !== _p$.i && _$setStyleProperty(_el$3, "left", _p$.i = _v$5);
62
- _v$6 !== _p$.n && _$setStyleProperty(_el$3, "top", _p$.n = _v$6);
54
+ var _v$ = `fixed rounded border ${props.dashed ? "border-dashed" : "border-solid"} border-amber-500`,
55
+ _v$2 = box().x + "px",
56
+ _v$3 = box().y + "px",
57
+ _v$4 = box().width + "px",
58
+ _v$5 = box().height + "px",
59
+ _v$6 = box().x + 4 + "px",
60
+ _v$7 = box().y + 4 + "px";
61
+ _v$ !== _p$.e && _$className(_el$2, _p$.e = _v$);
62
+ _v$2 !== _p$.t && _$setStyleProperty(_el$2, "left", _p$.t = _v$2);
63
+ _v$3 !== _p$.a && _$setStyleProperty(_el$2, "top", _p$.a = _v$3);
64
+ _v$4 !== _p$.o && _$setStyleProperty(_el$2, "width", _p$.o = _v$4);
65
+ _v$5 !== _p$.i && _$setStyleProperty(_el$2, "height", _p$.i = _v$5);
66
+ _v$6 !== _p$.n && _$setStyleProperty(_el$3, "left", _p$.n = _v$6);
67
+ _v$7 !== _p$.s && _$setStyleProperty(_el$3, "top", _p$.s = _v$7);
63
68
  return _p$;
64
69
  }, {
65
70
  e: undefined,
@@ -67,11 +72,12 @@ export function MaybeOutline(props) {
67
72
  a: undefined,
68
73
  o: undefined,
69
74
  i: undefined,
70
- n: undefined
75
+ n: undefined,
76
+ s: undefined
71
77
  });
72
78
  return _el$;
73
79
  })() : (() => {
74
- var _el$4 = _tmpl$2(),
80
+ var _el$4 = _tmpl$(),
75
81
  _el$5 = _el$4.firstChild,
76
82
  _el$6 = _el$5.nextSibling;
77
83
  _$setStyleProperty(_el$6, "padding", "4px 10px");
@@ -88,18 +94,20 @@ export function MaybeOutline(props) {
88
94
  return () => _c$2() ? `.${props.currentElement.getAttribute('class').split(" ")[0]}` : "";
89
95
  })(), null);
90
96
  _$effect(_p$ => {
91
- var _v$7 = box().x + "px",
92
- _v$8 = box().y + "px",
93
- _v$9 = box().width + "px",
94
- _v$0 = box().height + "px",
95
- _v$1 = box().x + 4 + "px",
96
- _v$10 = box().y + 4 + "px";
97
- _v$7 !== _p$.e && _$setStyleProperty(_el$5, "left", _p$.e = _v$7);
98
- _v$8 !== _p$.t && _$setStyleProperty(_el$5, "top", _p$.t = _v$8);
99
- _v$9 !== _p$.a && _$setStyleProperty(_el$5, "width", _p$.a = _v$9);
100
- _v$0 !== _p$.o && _$setStyleProperty(_el$5, "height", _p$.o = _v$0);
101
- _v$1 !== _p$.i && _$setStyleProperty(_el$6, "left", _p$.i = _v$1);
102
- _v$10 !== _p$.n && _$setStyleProperty(_el$6, "top", _p$.n = _v$10);
97
+ var _v$8 = `fixed rounded border ${props.dashed ? "border-dashed" : "border-solid"} border-gray-500`,
98
+ _v$9 = box().x + "px",
99
+ _v$0 = box().y + "px",
100
+ _v$1 = box().width + "px",
101
+ _v$10 = box().height + "px",
102
+ _v$11 = box().x + 4 + "px",
103
+ _v$12 = box().y + 4 + "px";
104
+ _v$8 !== _p$.e && _$className(_el$5, _p$.e = _v$8);
105
+ _v$9 !== _p$.t && _$setStyleProperty(_el$5, "left", _p$.t = _v$9);
106
+ _v$0 !== _p$.a && _$setStyleProperty(_el$5, "top", _p$.a = _v$0);
107
+ _v$1 !== _p$.o && _$setStyleProperty(_el$5, "width", _p$.o = _v$1);
108
+ _v$10 !== _p$.i && _$setStyleProperty(_el$5, "height", _p$.i = _v$10);
109
+ _v$11 !== _p$.n && _$setStyleProperty(_el$6, "left", _p$.n = _v$11);
110
+ _v$12 !== _p$.s && _$setStyleProperty(_el$6, "top", _p$.s = _v$12);
103
111
  return _p$;
104
112
  }, {
105
113
  e: undefined,
@@ -107,7 +115,8 @@ export function MaybeOutline(props) {
107
115
  a: undefined,
108
116
  o: undefined,
109
117
  i: undefined,
110
- n: undefined
118
+ n: undefined,
119
+ s: undefined
111
120
  });
112
121
  return _el$4;
113
122
  })());
@@ -21,5 +21,6 @@ export type AllBoxes = {
21
21
  export declare function Outline(props: {
22
22
  element: FullElementInfo;
23
23
  targets: Targets;
24
+ dashed?: boolean;
24
25
  }): import("solid-js").JSX.Element;
25
26
  export {};
@@ -1,10 +1,11 @@
1
1
  import { template as _$template } from "solid-js/web";
2
2
  import { createComponent as _$createComponent } from "solid-js/web";
3
+ import { className as _$className } from "solid-js/web";
3
4
  import { effect as _$effect } from "solid-js/web";
4
5
  import { insert as _$insert } from "solid-js/web";
5
6
  import { setStyleProperty as _$setStyleProperty } from "solid-js/web";
6
7
  import { memo as _$memo } from "solid-js/web";
7
- var _tmpl$ = /*#__PURE__*/_$template(`<div><div class="fixed rounded border border-solid border-sky-500"style=z-index:2></div><div class="fixed text-xs font-medium rounded-md"style="z-index:3;box-shadow:0 4px 16px rgba(0, 0, 0, 0.2);font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;letter-spacing:0.01em;text-overflow:ellipsis;white-space:nowrap">`);
8
+ var _tmpl$ = /*#__PURE__*/_$template(`<div><div style=z-index:2></div><div class="fixed text-xs font-medium rounded-md"style="z-index:3;box-shadow:0 4px 16px rgba(0, 0, 0, 0.2);font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;letter-spacing:0.01em;text-overflow:ellipsis;white-space:nowrap">`);
8
9
  import { RenderBoxes } from "./RenderBoxes";
9
10
  export function Outline(props) {
10
11
  const box = () => props.element.thisElement.box;
@@ -118,20 +119,22 @@ export function Outline(props) {
118
119
  _$setStyleProperty(_el$3, "overflow", "hidden");
119
120
  _$insert(_el$3, () => props.element.thisElement.label);
120
121
  _$effect(_p$ => {
121
- var _v$ = box().x + "px",
122
- _v$2 = box().y + "px",
123
- _v$3 = box().width + "px",
124
- _v$4 = box().height + "px",
125
- _v$5 = box().x + 4 + "px",
126
- _v$6 = box().y + 4 + "px",
127
- _v$7 = box().width - 8 + "px";
128
- _v$ !== _p$.e && _$setStyleProperty(_el$2, "left", _p$.e = _v$);
129
- _v$2 !== _p$.t && _$setStyleProperty(_el$2, "top", _p$.t = _v$2);
130
- _v$3 !== _p$.a && _$setStyleProperty(_el$2, "width", _p$.a = _v$3);
131
- _v$4 !== _p$.o && _$setStyleProperty(_el$2, "height", _p$.o = _v$4);
132
- _v$5 !== _p$.i && _$setStyleProperty(_el$3, "left", _p$.i = _v$5);
133
- _v$6 !== _p$.n && _$setStyleProperty(_el$3, "top", _p$.n = _v$6);
134
- _v$7 !== _p$.s && _$setStyleProperty(_el$3, "max-width", _p$.s = _v$7);
122
+ var _v$ = `fixed rounded border ${props.dashed ? "border-dashed" : "border-solid"} border-sky-500`,
123
+ _v$2 = box().x + "px",
124
+ _v$3 = box().y + "px",
125
+ _v$4 = box().width + "px",
126
+ _v$5 = box().height + "px",
127
+ _v$6 = box().x + 4 + "px",
128
+ _v$7 = box().y + 4 + "px",
129
+ _v$8 = box().width - 8 + "px";
130
+ _v$ !== _p$.e && _$className(_el$2, _p$.e = _v$);
131
+ _v$2 !== _p$.t && _$setStyleProperty(_el$2, "left", _p$.t = _v$2);
132
+ _v$3 !== _p$.a && _$setStyleProperty(_el$2, "top", _p$.a = _v$3);
133
+ _v$4 !== _p$.o && _$setStyleProperty(_el$2, "width", _p$.o = _v$4);
134
+ _v$5 !== _p$.i && _$setStyleProperty(_el$2, "height", _p$.i = _v$5);
135
+ _v$6 !== _p$.n && _$setStyleProperty(_el$3, "left", _p$.n = _v$6);
136
+ _v$7 !== _p$.s && _$setStyleProperty(_el$3, "top", _p$.s = _v$7);
137
+ _v$8 !== _p$.h && _$setStyleProperty(_el$3, "max-width", _p$.h = _v$8);
135
138
  return _p$;
136
139
  }, {
137
140
  e: undefined,
@@ -140,7 +143,8 @@ export function Outline(props) {
140
143
  o: undefined,
141
144
  i: undefined,
142
145
  n: undefined,
143
- s: undefined
146
+ s: undefined,
147
+ h: undefined
144
148
  });
145
149
  return _el$;
146
150
  })();
@@ -16,15 +16,24 @@ import { isCombinationModifiersPressed } from "../functions/isCombinationModifie
16
16
  import { MaybeOutline } from "./MaybeOutline";
17
17
  import { isLocatorsOwnElement } from "../functions/isLocatorsOwnElement";
18
18
  import { Toast } from "./Toast";
19
- import { collectAncestry, formatAncestryChain } from "../functions/formatAncestryChain";
19
+ import { collectAncestry, formatAncestryChain, truncateAtFirstFile } from "../functions/formatAncestryChain";
20
20
  import { enrichAncestryWithSourceMaps } from "../functions/enrichAncestrySourceMaps";
21
21
  import { createTreeNode } from "../adapters/createTreeNode";
22
22
  import treeIconUrl from "../_generated_tree_icon";
23
23
  import { createDejitterRecorder } from "../dejitter/recorder";
24
24
  import { RecordingOutline } from "./RecordingOutline";
25
25
  import { RecordingResults } from "./RecordingResults";
26
+ const DEJITTER_CONFIG = {
27
+ selector: '[data-treelocator-recording]',
28
+ props: ['opacity', 'transform', 'boundingRect', 'width', 'height'],
29
+ sampleRate: 15,
30
+ maxDuration: 30000,
31
+ idleTimeout: 0,
32
+ mutations: true
33
+ };
26
34
  function Runtime(props) {
27
35
  const [holdingModKey, setHoldingModKey] = createSignal(false);
36
+ const [holdingShift, setHoldingShift] = createSignal(false);
28
37
  const [currentElement, setCurrentElement] = createSignal(null);
29
38
  const [toastMessage, setToastMessage] = createSignal(null);
30
39
  const [locatorActive, setLocatorActive] = createSignal(false);
@@ -86,13 +95,16 @@ function Runtime(props) {
86
95
  }
87
96
  function keyUpListener(e) {
88
97
  setHoldingModKey(isCombinationModifiersPressed(e));
98
+ setHoldingShift(e.shiftKey);
89
99
  }
90
100
  function keyDownListener(e) {
91
101
  setHoldingModKey(isCombinationModifiersPressed(e, true));
102
+ setHoldingShift(e.shiftKey);
92
103
  }
93
104
  function mouseMoveListener(e) {
94
105
  // Update modifier state from mouse events - more reliable than keydown/keyup
95
106
  setHoldingModKey(e.altKey);
107
+ setHoldingShift(e.shiftKey);
96
108
  }
97
109
  function findElementAtPoint(e) {
98
110
  const elementsAtPoint = document.elementsFromPoint(e.clientX, e.clientY);
@@ -134,14 +146,7 @@ function Runtime(props) {
134
146
  element.setAttribute('data-treelocator-recording', 'true');
135
147
  setRecordedElement(element);
136
148
  dejitterInstance = createDejitterRecorder();
137
- dejitterInstance.configure({
138
- selector: '[data-treelocator-recording]',
139
- props: ['opacity', 'transform', 'boundingRect', 'width', 'height'],
140
- sampleRate: 15,
141
- maxDuration: 30000,
142
- idleTimeout: 0,
143
- mutations: true
144
- });
149
+ dejitterInstance.configure(DEJITTER_CONFIG);
145
150
  dejitterInstance.start();
146
151
  startInteractionTracker();
147
152
  setRecordingState('recording');
@@ -251,14 +256,7 @@ function Runtime(props) {
251
256
  element.setAttribute('data-treelocator-recording', 'true');
252
257
  setRecordedElement(element);
253
258
  dejitterInstance = createDejitterRecorder();
254
- dejitterInstance.configure({
255
- selector: '[data-treelocator-recording]',
256
- props: ['opacity', 'transform', 'boundingRect', 'width', 'height'],
257
- sampleRate: 15,
258
- maxDuration: 30000,
259
- idleTimeout: 0,
260
- mutations: true
261
- });
259
+ dejitterInstance.configure(DEJITTER_CONFIG);
262
260
  dejitterInstance.start();
263
261
  setRecordingState('recording');
264
262
  setReplaying(true);
@@ -349,6 +347,9 @@ function Runtime(props) {
349
347
  setRecordedElement(null);
350
348
  setViewingPrevious(false);
351
349
  setRecordingState('idle');
350
+ try {
351
+ localStorage.removeItem(STORAGE_KEY);
352
+ } catch {}
352
353
  }
353
354
  function hasPreviousRecording() {
354
355
  return loadFromStorage().previous !== null;
@@ -408,6 +409,7 @@ function Runtime(props) {
408
409
  }
409
410
  function mouseOverListener(e) {
410
411
  setHoldingModKey(e.altKey);
412
+ setHoldingShift(e.shiftKey);
411
413
 
412
414
  // Don't update hovered element while recording -- highlight is sticky
413
415
  if (recordingState() === 'recording') return;
@@ -456,7 +458,12 @@ function Runtime(props) {
456
458
  // Copy ancestry to clipboard on alt+click
457
459
  const treeNode = createTreeNode(element, props.adapterId);
458
460
  if (treeNode) {
459
- const ancestry = collectAncestry(treeNode);
461
+ let ancestry = collectAncestry(treeNode);
462
+
463
+ // Alt+Shift: keep from bottom up to the first element with a file, discard above
464
+ if (e.shiftKey) {
465
+ ancestry = truncateAtFirstFile(ancestry);
466
+ }
460
467
 
461
468
  // Write immediately with component names (preserves user gesture for clipboard API)
462
469
  const formatted = formatAncestryChain(ancestry);
@@ -513,6 +520,8 @@ function Runtime(props) {
513
520
  root.addEventListener("scroll", scrollListener);
514
521
  }
515
522
  onCleanup(() => {
523
+ stopReplay();
524
+ stopInteractionTracker();
516
525
  for (const root of roots) {
517
526
  root.removeEventListener("keyup", keyUpListener);
518
527
  root.removeEventListener("keydown", keyDownListener);
@@ -540,6 +549,9 @@ function Runtime(props) {
540
549
  },
541
550
  get targets() {
542
551
  return props.targets;
552
+ },
553
+ get dashed() {
554
+ return holdingShift();
543
555
  }
544
556
  }) : null), _$memo(() => _$memo(() => !!(recordingState() === 'recording' && recordedElement()))() ? _$createComponent(RecordingOutline, {
545
557
  get element() {
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  /**
3
2
  * Dejitter — Animation frame recorder & jank detector (vendored)
4
3
  *
@@ -18,4 +18,11 @@ export interface AncestryItem {
18
18
  serverComponents?: ServerComponentInfo[];
19
19
  }
20
20
  export declare function collectAncestry(node: TreeNode): AncestryItem[];
21
+ /**
22
+ * Truncate ancestry to keep only the local context.
23
+ * - If the clicked element has no filePath: keep up to the first ancestor with a file.
24
+ * - If the clicked element has a filePath: keep up to the first ancestor with a DIFFERENT file.
25
+ * The ancestry array is bottom-up: index 0 = clicked element, last = root.
26
+ */
27
+ export declare function truncateAtFirstFile(items: AncestryItem[]): AncestryItem[];
21
28
  export declare function formatAncestryChain(items: AncestryItem[]): string;
@@ -121,6 +121,31 @@ function getInnermostNamedComponent(item) {
121
121
  }
122
122
  return undefined;
123
123
  }
124
+
125
+ /**
126
+ * Truncate ancestry to keep only the local context.
127
+ * - If the clicked element has no filePath: keep up to the first ancestor with a file.
128
+ * - If the clicked element has a filePath: keep up to the first ancestor with a DIFFERENT file.
129
+ * The ancestry array is bottom-up: index 0 = clicked element, last = root.
130
+ */
131
+ export function truncateAtFirstFile(items) {
132
+ if (items.length === 0) return items;
133
+ const clickedFile = items[0]?.filePath;
134
+ if (!clickedFile) {
135
+ // Clicked element has no file: find first ancestor with any file
136
+ const firstWithFile = items.findIndex(item => item.filePath);
137
+ if (firstWithFile === -1) return items;
138
+ return items.slice(0, firstWithFile + 1);
139
+ }
140
+
141
+ // Clicked element has a file: find first ancestor with a different file
142
+ for (let i = 1; i < items.length; i++) {
143
+ if (items[i].filePath && items[i].filePath !== clickedFile) {
144
+ return items.slice(0, i + 1);
145
+ }
146
+ }
147
+ return items;
148
+ }
124
149
  export function formatAncestryChain(items) {
125
150
  if (items.length === 0) {
126
151
  return "";
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { formatAncestryChain } from "./formatAncestryChain";
2
+ import { formatAncestryChain, truncateAtFirstFile } from "./formatAncestryChain";
3
3
  describe("formatAncestryChain", () => {
4
4
  it("uses component name only at component boundaries", () => {
5
5
  const items = [{
@@ -239,4 +239,136 @@ describe("formatAncestryChain", () => {
239
239
  └─ GlassPanel in Sidebar at src/Sidebar.tsx:20`);
240
240
  });
241
241
  });
242
+ describe("truncateAtFirstFile", () => {
243
+ it("keeps from clicked element up to first ancestor with filePath", () => {
244
+ // Bottom-up: clicked element first, root last
245
+ const items = [{
246
+ elementName: "span",
247
+ componentName: "Button"
248
+ }, {
249
+ elementName: "div",
250
+ componentName: "Card"
251
+ }, {
252
+ elementName: "div",
253
+ componentName: "Layout",
254
+ filePath: "src/Layout.tsx",
255
+ line: 10
256
+ }, {
257
+ elementName: "div",
258
+ componentName: "App",
259
+ filePath: "src/App.tsx",
260
+ line: 1
261
+ }];
262
+ const result = truncateAtFirstFile(items);
263
+ expect(result).toEqual([{
264
+ elementName: "span",
265
+ componentName: "Button"
266
+ }, {
267
+ elementName: "div",
268
+ componentName: "Card"
269
+ }, {
270
+ elementName: "div",
271
+ componentName: "Layout",
272
+ filePath: "src/Layout.tsx",
273
+ line: 10
274
+ }]);
275
+ });
276
+ it("when clicked element has filePath, keeps up to first different file", () => {
277
+ const items = [{
278
+ elementName: "button",
279
+ componentName: "Button",
280
+ filePath: "src/Button.tsx",
281
+ line: 5
282
+ }, {
283
+ elementName: "div",
284
+ componentName: "Layout",
285
+ filePath: "src/Layout.tsx",
286
+ line: 10
287
+ }, {
288
+ elementName: "div",
289
+ componentName: "App",
290
+ filePath: "src/App.tsx",
291
+ line: 1
292
+ }];
293
+ const result = truncateAtFirstFile(items);
294
+ expect(result).toEqual([{
295
+ elementName: "button",
296
+ componentName: "Button",
297
+ filePath: "src/Button.tsx",
298
+ line: 5
299
+ }, {
300
+ elementName: "div",
301
+ componentName: "Layout",
302
+ filePath: "src/Layout.tsx",
303
+ line: 10
304
+ }]);
305
+ });
306
+ it("keeps all items in the same file plus first different-file ancestor", () => {
307
+ const items = [{
308
+ elementName: "span",
309
+ componentName: "Label",
310
+ filePath: "src/Button.tsx",
311
+ line: 20
312
+ }, {
313
+ elementName: "button",
314
+ componentName: "Button",
315
+ filePath: "src/Button.tsx",
316
+ line: 5
317
+ }, {
318
+ elementName: "div",
319
+ componentName: "Layout",
320
+ filePath: "src/Layout.tsx",
321
+ line: 10
322
+ }, {
323
+ elementName: "div",
324
+ componentName: "App",
325
+ filePath: "src/App.tsx",
326
+ line: 1
327
+ }];
328
+ const result = truncateAtFirstFile(items);
329
+ expect(result).toEqual([{
330
+ elementName: "span",
331
+ componentName: "Label",
332
+ filePath: "src/Button.tsx",
333
+ line: 20
334
+ }, {
335
+ elementName: "button",
336
+ componentName: "Button",
337
+ filePath: "src/Button.tsx",
338
+ line: 5
339
+ }, {
340
+ elementName: "div",
341
+ componentName: "Layout",
342
+ filePath: "src/Layout.tsx",
343
+ line: 10
344
+ }]);
345
+ });
346
+ it("returns all items when all share the same file", () => {
347
+ const items = [{
348
+ elementName: "span",
349
+ filePath: "src/App.tsx",
350
+ line: 10
351
+ }, {
352
+ elementName: "div",
353
+ filePath: "src/App.tsx",
354
+ line: 5
355
+ }];
356
+ const result = truncateAtFirstFile(items);
357
+ expect(result).toEqual(items);
358
+ });
359
+ it("returns all items when none have a filePath", () => {
360
+ const items = [{
361
+ elementName: "span",
362
+ componentName: "A"
363
+ }, {
364
+ elementName: "div",
365
+ componentName: "B"
366
+ }];
367
+ const result = truncateAtFirstFile(items);
368
+ expect(result).toEqual(items);
369
+ });
370
+ it("returns empty array for empty input", () => {
371
+ expect(truncateAtFirstFile([])).toEqual([]);
372
+ });
373
+ });
242
374
  });
@@ -1,24 +1,3 @@
1
- // function printDebugOwnerTree(fiber: Fiber): string | null {
2
- // let current: Fiber | null = fiber || null;
3
- // let results = [];
4
- // while (current) {
5
- // results.push(getUsableName(current));
6
- // current = current._debugOwner || null;
7
- // }
8
- // console.log('DEBUG OWNER: ', results);
9
- // return null;
10
- // }
11
- // function printReturnTree(fiber: Fiber): string | null {
12
- // let current: Fiber | null = fiber || null;
13
- // let results = [];
14
- // while (current) {
15
- // results.push(getUsableName(current));
16
- // current = current.return || null;
17
- // }
18
- // console.log('RETURN: ', results);
19
- // return null;
20
- // }
21
-
22
1
  export function getUsableName(fiber) {
23
2
  if (!fiber) {
24
3
  return "Not found";
@@ -9,8 +9,12 @@ export function getMouseModifiers() {
9
9
  }
10
10
  export function isCombinationModifiersPressed(e, rightClick = false) {
11
11
  const modifiers = getMouseModifiers();
12
+
13
+ // Only require shift if it's part of the configured modifiers.
14
+ // Shift is used independently for truncation, so pressing it shouldn't
15
+ // disqualify the activation modifier combo.
12
16
  if (rightClick) {
13
- return e.altKey == !!modifiers.alt && e.metaKey == !!modifiers.meta && e.shiftKey == !!modifiers.shift;
17
+ return e.altKey == !!modifiers.alt && e.metaKey == !!modifiers.meta && (!modifiers.shift || e.shiftKey);
14
18
  }
15
- return e.altKey == !!modifiers.alt && e.ctrlKey == !!modifiers.ctrl && e.metaKey == !!modifiers.meta && e.shiftKey == !!modifiers.shift;
19
+ return e.altKey == !!modifiers.alt && e.ctrlKey == !!modifiers.ctrl && e.metaKey == !!modifiers.meta && (!modifiers.shift || e.shiftKey);
16
20
  }
package/dist/output.css CHANGED
@@ -879,10 +879,6 @@ input:where([type='file']):focus {
879
879
  left: 0.25rem;
880
880
  }
881
881
 
882
- .left-1\/2 {
883
- left: 50%;
884
- }
885
-
886
882
  .left-3 {
887
883
  left: 0.75rem;
888
884
  }
@@ -895,10 +891,6 @@ input:where([type='file']):focus {
895
891
  top: 0.25rem;
896
892
  }
897
893
 
898
- .top-1\/2 {
899
- top: 50%;
900
- }
901
-
902
894
  .z-10 {
903
895
  z-index: 10;
904
896
  }
@@ -1078,11 +1070,6 @@ input:where([type='file']):focus {
1078
1070
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1079
1071
  }
1080
1072
 
1081
- .-translate-x-1\/2 {
1082
- --tw-translate-x: -50%;
1083
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1084
- }
1085
-
1086
1073
  .-translate-x-full {
1087
1074
  --tw-translate-x: -100%;
1088
1075
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@@ -1093,11 +1080,6 @@ input:where([type='file']):focus {
1093
1080
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1094
1081
  }
1095
1082
 
1096
- .-translate-y-1\/2 {
1097
- --tw-translate-y: -50%;
1098
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1099
- }
1100
-
1101
1083
  .translate-x-full {
1102
1084
  --tw-translate-x: 100%;
1103
1085
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@@ -1175,6 +1157,12 @@ input:where([type='file']):focus {
1175
1157
  overflow: scroll;
1176
1158
  }
1177
1159
 
1160
+ .truncate {
1161
+ overflow: hidden;
1162
+ text-overflow: ellipsis;
1163
+ white-space: nowrap;
1164
+ }
1165
+
1178
1166
  .text-ellipsis {
1179
1167
  text-overflow: ellipsis;
1180
1168
  }
@@ -1231,6 +1219,10 @@ input:where([type='file']):focus {
1231
1219
  border-style: solid;
1232
1220
  }
1233
1221
 
1222
+ .border-dashed {
1223
+ border-style: dashed;
1224
+ }
1225
+
1234
1226
  .border-amber-500 {
1235
1227
  --tw-border-opacity: 1;
1236
1228
  border-color: rgb(245 158 11 / var(--tw-border-opacity, 1));
@@ -1465,11 +1457,6 @@ input:where([type='file']):focus {
1465
1457
  padding-bottom: 0px;
1466
1458
  }
1467
1459
 
1468
- .py-0\.5 {
1469
- padding-top: 0.125rem;
1470
- padding-bottom: 0.125rem;
1471
- }
1472
-
1473
1460
  .py-1 {
1474
1461
  padding-top: 0.25rem;
1475
1462
  padding-bottom: 0.25rem;
@@ -1803,21 +1790,4 @@ input:where([type='file']):focus {
1803
1790
 
1804
1791
  .ease-out {
1805
1792
  transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
1806
- }
1807
-
1808
- .hover\:bg-white\/30:hover {
1809
- background-color: rgb(255 255 255 / 0.3);
1810
- }
1811
-
1812
- .hover\:text-gray-100:hover {
1813
- --tw-text-opacity: 1;
1814
- color: rgb(243 244 246 / var(--tw-text-opacity, 1));
1815
- }
1816
-
1817
- .group\/tooltip:hover .group-hover\/tooltip\:visible {
1818
- visibility: visible;
1819
- }
1820
-
1821
- .group\/tooltip:hover .group-hover\/tooltip\:opacity-100 {
1822
- opacity: 1;
1823
1793
  }