@parca/profile 0.19.151 → 0.19.152

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.
@@ -21,6 +21,7 @@ import {pointer} from 'd3-selection';
21
21
  interface GraphTooltipProps {
22
22
  children: React.ReactNode;
23
23
  contextElement: Element | null;
24
+ frozen?: boolean;
24
25
  }
25
26
 
26
27
  function createPositionedVirtualElement(contextElement: Element, x = 0, y = 0): VirtualElement {
@@ -40,7 +41,11 @@ function createPositionedVirtualElement(contextElement: Element, x = 0, y = 0):
40
41
  };
41
42
  }
42
43
 
43
- const GraphTooltip = ({children, contextElement}: GraphTooltipProps): React.JSX.Element => {
44
+ const GraphTooltip = ({
45
+ children,
46
+ contextElement,
47
+ frozen = false,
48
+ }: GraphTooltipProps): React.JSX.Element => {
44
49
  'use no memo';
45
50
  const [isPositioned, setIsPositioned] = useState(false);
46
51
 
@@ -60,10 +65,16 @@ const GraphTooltip = ({children, contextElement}: GraphTooltipProps): React.JSX.
60
65
  whileElementsMounted: undefined,
61
66
  });
62
67
 
68
+ // Read the latest `frozen` value from inside the mousemove handler without
69
+ // re-binding the listener every time it toggles.
70
+ const frozenRef = React.useRef(frozen);
71
+ frozenRef.current = frozen;
72
+
63
73
  useEffect(() => {
64
74
  if (contextElement === null) return;
65
75
 
66
76
  const onMouseMove: EventListenerOrEventListenerObject = (e: Event) => {
77
+ if (frozenRef.current) return; // Hold position while ⇧ Shift is held.
67
78
  const rel = pointer(e);
68
79
  const tooltipX = rel[0];
69
80
  const tooltipY = rel[1];
@@ -85,6 +96,7 @@ const GraphTooltip = ({children, contextElement}: GraphTooltipProps): React.JSX.
85
96
  style={{
86
97
  ...floatingStyles,
87
98
  visibility: !isPositioned ? 'hidden' : 'visible',
99
+ pointerEvents: frozen ? 'auto' : 'none',
88
100
  }}
89
101
  className="z-50 w-max"
90
102
  >
@@ -0,0 +1,43 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ // Open a URL in a new browser tab — robust to held modifier keys.
15
+ //
16
+ // Why this exists: the tooltip's "freeze" gesture requires the user to be
17
+ // holding ⇧ Shift. Browsers route Shift+click on a target="_blank" link to
18
+ // "new window" via real-keyboard state at navigation time, so both plain
19
+ // <a target="_blank"> clicks and `window.open` fall through to a window
20
+ // instead of a tab.
21
+ //
22
+ // Trick: dispatch a synthetic MouseEvent on a detached anchor with the
23
+ // platform's "new-tab" modifier set explicitly (Cmd & Ctrl)
24
+ // and Shift cleared. The browser reads modifier flags off the event for its
25
+ // routing decision, so this consistently opens in a new tab.
26
+ export function openInNewTab(url: string): void {
27
+ const a = document.createElement('a');
28
+ a.href = url;
29
+ a.target = '_blank';
30
+ a.rel = 'noopener noreferrer';
31
+
32
+ a.dispatchEvent(
33
+ new MouseEvent('click', {
34
+ bubbles: false,
35
+ cancelable: true,
36
+ view: window,
37
+ button: 0,
38
+ shiftKey: false,
39
+ metaKey: true,
40
+ ctrlKey: true,
41
+ })
42
+ );
43
+ }
@@ -11,7 +11,7 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React, {memo, useEffect, useState} from 'react';
14
+ import React, {memo, useEffect, useRef, useState} from 'react';
15
15
 
16
16
  import GraphTooltipArrow from '../../GraphTooltipArrow';
17
17
  import GraphTooltipArrowContent from '../../GraphTooltipArrow/Content';
@@ -28,13 +28,45 @@ export const MemoizedTooltip = memo(function MemoizedTooltip({
28
28
  dockedMetainfo,
29
29
  }: MemoizedTooltipProps): React.JSX.Element | null {
30
30
  const [tooltipRow, setTooltipRow] = useState<number | null>(null);
31
+ const [shiftHeld, setShiftHeld] = useState(false);
32
+ const shiftHeldRef = useRef(false);
33
+
34
+ useEffect(() => {
35
+ shiftHeldRef.current = shiftHeld;
36
+ }, [shiftHeld]);
37
+
31
38
  const {table, total, totalUnfiltered, profileType, unit, compareAbsolute, tooltipId} =
32
39
  useTooltipContext();
33
40
 
41
+ useEffect(() => {
42
+ const onKeyDown = (e: KeyboardEvent): void => {
43
+ if (e.key === 'Shift') setShiftHeld(true);
44
+ };
45
+ const onKeyUp = (e: KeyboardEvent): void => {
46
+ if (e.key === 'Shift') setShiftHeld(false);
47
+ };
48
+ // If the user opens the attribution link, releases Shift in the new tab,
49
+ // and comes back — our window never saw the keyup, so the frozen state
50
+ // would stick. Clear it whenever focus leaves the tab.
51
+ const onBlurOrHide = (): void => setShiftHeld(false);
52
+ window.addEventListener('keydown', onKeyDown);
53
+ window.addEventListener('keyup', onKeyUp);
54
+ window.addEventListener('blur', onBlurOrHide);
55
+ document.addEventListener('visibilitychange', onBlurOrHide);
56
+ return () => {
57
+ window.removeEventListener('keydown', onKeyDown);
58
+ window.removeEventListener('keyup', onKeyUp);
59
+ window.removeEventListener('blur', onBlurOrHide);
60
+ document.removeEventListener('visibilitychange', onBlurOrHide);
61
+ };
62
+ }, []);
63
+
34
64
  // This component subscribes to tooltip updates through a callback
35
65
  // passed to the TooltipProvider, avoiding the need to lift state
36
66
  useEffect(() => {
37
67
  const handleTooltipUpdate = (event: CustomEvent<{row: number | null}>): void => {
68
+ // While Shift is held, lock the tooltip to its original frame.
69
+ if (shiftHeldRef.current) return;
38
70
  setTooltipRow(event.detail.row);
39
71
  };
40
72
 
@@ -82,7 +114,7 @@ export const MemoizedTooltip = memo(function MemoizedTooltip({
82
114
  return null;
83
115
  }
84
116
  return (
85
- <GraphTooltipArrow contextElement={contextElement}>
117
+ <GraphTooltipArrow contextElement={contextElement} frozen={shiftHeld}>
86
118
  <GraphTooltipArrowContent
87
119
  table={table}
88
120
  row={tooltipRow}
@@ -92,6 +124,7 @@ export const MemoizedTooltip = memo(function MemoizedTooltip({
92
124
  profileType={profileType}
93
125
  unit={unit}
94
126
  compareAbsolute={compareAbsolute}
127
+ frozen={shiftHeld}
95
128
  />
96
129
  </GraphTooltipArrow>
97
130
  );