@bundle-stats/ui 4.22.3 → 4.23.0-beta.2
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.
- package/lib/app/app.js +3 -0
- package/lib/app/app.js.map +1 -1
- package/lib/app/app.module.css +12 -2
- package/lib/chart-colors.json +58 -44
- package/lib/components/delta/delta.js +3 -13
- package/lib/components/delta/delta.js.map +1 -1
- package/lib/components/delta/delta.module.css +13 -12
- package/lib/components/delta/index.js.map +1 -1
- package/lib/components/entry-info/entry-info.js +1 -1
- package/lib/components/entry-info/entry-info.js.map +1 -1
- package/lib/components/entry-info/entry-info.module.css +1 -1
- package/lib/components/index.js +3 -1
- package/lib/components/index.js.map +1 -1
- package/lib/components/insight-icon/insight-icon.module.css +6 -6
- package/lib/components/job-name/job-name.js +1 -2
- package/lib/components/job-name/job-name.js.map +1 -1
- package/lib/components/metric-change-legend/index.js +18 -0
- package/lib/components/metric-change-legend/index.js.map +1 -0
- package/lib/components/metric-change-legend/metric-change-legend.js +91 -0
- package/lib/components/metric-change-legend/metric-change-legend.js.map +1 -0
- package/lib/components/metric-change-legend/metric-change-legend.module.css +80 -0
- package/lib/components/metric-run-info/metric-run-info.js +22 -7
- package/lib/components/metric-run-info/metric-run-info.js.map +1 -1
- package/lib/components/metrics-display-selector/metrics-display-selector.js +7 -9
- package/lib/components/metrics-display-selector/metrics-display-selector.js.map +1 -1
- package/lib/components/metrics-display-selector/metrics-display-selector.module.css +0 -25
- package/lib/components/metrics-table-header/metrics-table-header.js +6 -6
- package/lib/components/metrics-table-header/metrics-table-header.js.map +1 -1
- package/lib/components/metrics-table-header/metrics-table-header.module.css +8 -12
- package/lib/components/metrics-table-options/metrics-table-options.js +2 -3
- package/lib/components/metrics-table-options/metrics-table-options.js.map +1 -1
- package/lib/components/metrics-table-title/metrics-table-title.js +1 -1
- package/lib/components/metrics-table-title/metrics-table-title.js.map +1 -1
- package/lib/components/metrics-treemap/metrics-treemap.constants.js +3 -1
- package/lib/components/metrics-treemap/metrics-treemap.constants.js.map +1 -1
- package/lib/components/metrics-treemap/metrics-treemap.js +68 -57
- package/lib/components/metrics-treemap/metrics-treemap.js.map +1 -1
- package/lib/components/metrics-treemap/metrics-treemap.module.css +9 -16
- package/lib/components/metrics-treemap/metrics-treemap.utils.js +72 -16
- package/lib/components/metrics-treemap/metrics-treemap.utils.js.map +1 -1
- package/lib/components/run-info/run-info.js +26 -14
- package/lib/components/run-info/run-info.js.map +1 -1
- package/lib/components/run-info/run-info.module.css +35 -26
- package/lib/components/summary/summary.js +9 -10
- package/lib/components/summary/summary.js.map +1 -1
- package/lib/components/summary/summary.module.css +60 -10
- package/lib/constants.js +13 -1
- package/lib/constants.js.map +1 -1
- package/lib/css/variables.css +71 -58
- package/lib/ui/button/button.js +2 -2
- package/lib/ui/button/button.js.map +1 -1
- package/lib/ui/dialog/dialog.js +4 -2
- package/lib/ui/dialog/dialog.js.map +1 -1
- package/lib/ui/dialog/dialog.module.css +22 -0
- package/lib/ui/dropdown/dropdown.js +13 -4
- package/lib/ui/dropdown/dropdown.js.map +1 -1
- package/lib/ui/dropdown/dropdown.module.css +51 -6
- package/lib/ui/filters/filters.js +22 -11
- package/lib/ui/filters/filters.js.map +1 -1
- package/lib/ui/filters/filters.module.css +0 -14
- package/lib/ui/hover-card/hover-card.js +7 -1
- package/lib/ui/hover-card/hover-card.js.map +1 -1
- package/lib/ui/hover-card/hover-card.module.css +14 -0
- package/lib/ui/table/table.module.css +0 -1
- package/lib/ui/tooltip/tooltip.js +6 -1
- package/lib/ui/tooltip/tooltip.js.map +1 -1
- package/lib/ui/tooltip/tooltip.module.css +11 -0
- package/lib-esm/app/app.js +4 -1
- package/lib-esm/app/app.js.map +1 -1
- package/lib-esm/app/app.module.css +12 -2
- package/lib-esm/chart-colors.json +58 -44
- package/lib-esm/components/delta/delta.js +3 -13
- package/lib-esm/components/delta/delta.js.map +1 -1
- package/lib-esm/components/delta/delta.module.css +13 -12
- package/lib-esm/components/delta/index.js.map +1 -1
- package/lib-esm/components/entry-info/entry-info.js +1 -1
- package/lib-esm/components/entry-info/entry-info.js.map +1 -1
- package/lib-esm/components/entry-info/entry-info.module.css +1 -1
- package/lib-esm/components/index.js +1 -0
- package/lib-esm/components/index.js.map +1 -1
- package/lib-esm/components/insight-icon/insight-icon.module.css +6 -6
- package/lib-esm/components/job-name/job-name.js +1 -2
- package/lib-esm/components/job-name/job-name.js.map +1 -1
- package/lib-esm/components/metric-change-legend/index.js +2 -0
- package/lib-esm/components/metric-change-legend/index.js.map +1 -0
- package/lib-esm/components/metric-change-legend/metric-change-legend.js +84 -0
- package/lib-esm/components/metric-change-legend/metric-change-legend.js.map +1 -0
- package/lib-esm/components/metric-change-legend/metric-change-legend.module.css +80 -0
- package/lib-esm/components/metric-run-info/metric-run-info.js +22 -7
- package/lib-esm/components/metric-run-info/metric-run-info.js.map +1 -1
- package/lib-esm/components/metrics-display-selector/metrics-display-selector.js +7 -9
- package/lib-esm/components/metrics-display-selector/metrics-display-selector.js.map +1 -1
- package/lib-esm/components/metrics-display-selector/metrics-display-selector.module.css +0 -25
- package/lib-esm/components/metrics-table-header/metrics-table-header.js +6 -6
- package/lib-esm/components/metrics-table-header/metrics-table-header.js.map +1 -1
- package/lib-esm/components/metrics-table-header/metrics-table-header.module.css +8 -12
- package/lib-esm/components/metrics-table-options/metrics-table-options.js +3 -4
- package/lib-esm/components/metrics-table-options/metrics-table-options.js.map +1 -1
- package/lib-esm/components/metrics-table-title/metrics-table-title.js +1 -1
- package/lib-esm/components/metrics-table-title/metrics-table-title.js.map +1 -1
- package/lib-esm/components/metrics-treemap/metrics-treemap.constants.js +2 -0
- package/lib-esm/components/metrics-treemap/metrics-treemap.constants.js.map +1 -1
- package/lib-esm/components/metrics-treemap/metrics-treemap.js +74 -63
- package/lib-esm/components/metrics-treemap/metrics-treemap.js.map +1 -1
- package/lib-esm/components/metrics-treemap/metrics-treemap.module.css +9 -16
- package/lib-esm/components/metrics-treemap/metrics-treemap.utils.js +71 -17
- package/lib-esm/components/metrics-treemap/metrics-treemap.utils.js.map +1 -1
- package/lib-esm/components/run-info/run-info.js +26 -14
- package/lib-esm/components/run-info/run-info.js.map +1 -1
- package/lib-esm/components/run-info/run-info.module.css +35 -26
- package/lib-esm/components/summary/summary.js +10 -11
- package/lib-esm/components/summary/summary.js.map +1 -1
- package/lib-esm/components/summary/summary.module.css +60 -10
- package/lib-esm/constants.js +12 -0
- package/lib-esm/constants.js.map +1 -1
- package/lib-esm/css/variables.css +71 -58
- package/lib-esm/ui/button/button.js +2 -2
- package/lib-esm/ui/button/button.js.map +1 -1
- package/lib-esm/ui/dialog/dialog.js +4 -2
- package/lib-esm/ui/dialog/dialog.js.map +1 -1
- package/lib-esm/ui/dialog/dialog.module.css +22 -0
- package/lib-esm/ui/dropdown/dropdown.js +11 -3
- package/lib-esm/ui/dropdown/dropdown.js.map +1 -1
- package/lib-esm/ui/dropdown/dropdown.module.css +51 -6
- package/lib-esm/ui/filters/filters.js +23 -12
- package/lib-esm/ui/filters/filters.js.map +1 -1
- package/lib-esm/ui/filters/filters.module.css +0 -14
- package/lib-esm/ui/hover-card/hover-card.js +7 -1
- package/lib-esm/ui/hover-card/hover-card.js.map +1 -1
- package/lib-esm/ui/hover-card/hover-card.module.css +14 -0
- package/lib-esm/ui/table/table.module.css +0 -1
- package/lib-esm/ui/tooltip/tooltip.js +6 -1
- package/lib-esm/ui/tooltip/tooltip.js.map +1 -1
- package/lib-esm/ui/tooltip/tooltip.module.css +11 -0
- package/package.json +16 -16
- package/src/app/app.jsx +6 -1
- package/src/app/app.module.css +12 -2
- package/src/chart-colors.json +58 -44
- package/src/components/delta/delta.module.css +13 -12
- package/src/components/delta/delta.tsx +32 -0
- package/src/components/entry-info/entry-info.module.css +1 -1
- package/src/components/entry-info/entry-info.tsx +2 -2
- package/src/components/index.ts +3 -0
- package/src/components/insight-icon/insight-icon.module.css +6 -6
- package/src/components/job-name/job-name.jsx +2 -3
- package/src/components/metric-change-legend/index.ts +1 -0
- package/src/components/metric-change-legend/metric-change-legend.module.css +80 -0
- package/src/components/metric-change-legend/metric-change-legend.stories.tsx +13 -0
- package/src/components/metric-change-legend/metric-change-legend.tsx +148 -0
- package/src/components/metric-run-info/metric-run-info.tsx +32 -17
- package/src/components/metrics-display-selector/metrics-display-selector.module.css +0 -25
- package/src/components/metrics-display-selector/metrics-display-selector.tsx +25 -37
- package/src/components/metrics-table-header/metrics-table-header.module.css +8 -12
- package/src/components/metrics-table-header/metrics-table-header.tsx +5 -6
- package/src/components/metrics-table-options/metrics-table-options.tsx +5 -6
- package/src/components/metrics-table-title/metrics-table-title.jsx +1 -1
- package/src/components/metrics-treemap/metrics-treemap.constants.ts +8 -0
- package/src/components/metrics-treemap/metrics-treemap.module.css +9 -16
- package/src/components/metrics-treemap/metrics-treemap.tsx +160 -135
- package/src/components/metrics-treemap/metrics-treemap.utils.ts +141 -27
- package/src/components/run-info/run-info.module.css +35 -26
- package/src/components/run-info/run-info.stories.tsx +16 -10
- package/src/components/run-info/run-info.tsx +132 -73
- package/src/components/summary/summary.module.css +60 -10
- package/src/components/summary/summary.tsx +47 -50
- package/src/constants.ts +13 -0
- package/src/css/variables.css +71 -58
- package/src/ui/button/button.tsx +2 -1
- package/src/ui/dialog/dialog.module.css +22 -0
- package/src/ui/dialog/dialog.tsx +9 -2
- package/src/ui/dropdown/dropdown.module.css +51 -6
- package/src/ui/dropdown/dropdown.tsx +17 -2
- package/src/ui/filters/filters.module.css +0 -14
- package/src/ui/filters/filters.tsx +111 -47
- package/src/ui/hover-card/hover-card.module.css +14 -0
- package/src/ui/hover-card/hover-card.tsx +7 -1
- package/src/ui/table/table.module.css +0 -1
- package/src/ui/tooltip/tooltip.module.css +11 -0
- package/src/ui/tooltip/tooltip.tsx +6 -1
- package/types/components/delta/delta.d.ts +7 -21
- package/types/components/delta/index.d.ts +1 -1
- package/types/components/index.d.ts +3 -0
- package/types/components/metric-change-legend/index.d.ts +1 -0
- package/types/components/metric-change-legend/metric-change-legend.d.ts +4 -0
- package/types/components/metric-run-info/metric-run-info.d.ts +3 -6
- package/types/components/metrics-treemap/metrics-treemap.constants.d.ts +2 -0
- package/types/components/metrics-treemap/metrics-treemap.utils.d.ts +11 -5
- package/types/components/run-info/run-info.d.ts +16 -7
- package/types/constants.d.ts +12 -0
- package/types/ui/dialog/dialog.d.ts +3 -2
- package/types/ui/dropdown/dropdown.d.ts +4 -1
- package/types/ui/filters/filters.d.ts +2 -1
- package/vitest.config.ts +4 -0
- package/lib/components/metrics-table-options/metrics-table-options.module.css +0 -15
- package/lib-esm/components/metrics-table-options/metrics-table-options.module.css +0 -15
- package/src/components/delta/delta.jsx +0 -22
- package/src/components/metrics-table-options/metrics-table-options.module.css +0 -15
- /package/src/components/delta/{index.js → index.ts} +0 -0
|
@@ -2,17 +2,15 @@ import React, {
|
|
|
2
2
|
type ComponentProps,
|
|
3
3
|
type MouseEventHandler,
|
|
4
4
|
type ReactNode,
|
|
5
|
-
type Ref,
|
|
6
|
-
type RefObject,
|
|
7
|
-
forwardRef,
|
|
8
5
|
useCallback,
|
|
6
|
+
useEffect,
|
|
9
7
|
useMemo,
|
|
10
8
|
useRef,
|
|
11
9
|
} from 'react';
|
|
12
|
-
import {
|
|
10
|
+
import { useMeasure } from 'react-use';
|
|
13
11
|
import cx from 'classnames';
|
|
14
12
|
import { HierarchyRectangularNode, hierarchy, treemap, treemapSquarify } from 'd3';
|
|
15
|
-
import { Tooltip, TooltipArrow
|
|
13
|
+
import { Tooltip, TooltipArrow } from 'ariakit/tooltip';
|
|
16
14
|
import {
|
|
17
15
|
type ReportMetricRow,
|
|
18
16
|
type MetricRunInfo,
|
|
@@ -30,23 +28,43 @@ import {
|
|
|
30
28
|
type TreeLeaf,
|
|
31
29
|
type TreeNode,
|
|
32
30
|
type Tree,
|
|
31
|
+
type TreeTotal,
|
|
33
32
|
SQUARIFY_RATIO,
|
|
34
33
|
PADDING_OUTER,
|
|
35
34
|
PADDING_INNER,
|
|
36
35
|
NESTED_PADDING,
|
|
37
36
|
NESTED_PADDING_TOP,
|
|
38
37
|
NESTED_PADDING_LEFT,
|
|
38
|
+
NODE_ID_SELECTOR,
|
|
39
39
|
TileSizeDisplay,
|
|
40
40
|
} from './metrics-treemap.constants';
|
|
41
41
|
import * as I18N from './metrics-treemap.i18n';
|
|
42
42
|
import css from './metrics-treemap.module.css';
|
|
43
43
|
import {
|
|
44
|
+
getTreemapNodesIndex,
|
|
44
45
|
resolveGroupDeltaType,
|
|
45
46
|
resolveTileSizeDisplay,
|
|
46
47
|
resolveTileGroupSizeDisplay,
|
|
47
|
-
|
|
48
|
+
useTreemapTooltipState,
|
|
48
49
|
} from './metrics-treemap.utils';
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the run info of a tile group from the children total
|
|
53
|
+
*/
|
|
54
|
+
function getGroupRunInfo(total: TreeTotal) {
|
|
55
|
+
const runInfo = getMetricRunInfo(
|
|
56
|
+
METRIC_TYPE_CONFIGS.METRIC_TYPE_FILE_SIZE,
|
|
57
|
+
total.current,
|
|
58
|
+
total.baseline,
|
|
59
|
+
) as MetricRunInfo;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
runInfo,
|
|
63
|
+
baselineDisplayValue: formatFileSize(total.baseline),
|
|
64
|
+
deltaType: resolveGroupDeltaType(runInfo),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
interface TileTooltipContentProps {
|
|
51
69
|
item: ReportMetricRow;
|
|
52
70
|
}
|
|
@@ -64,7 +82,8 @@ const TileTooltipContent = (props: TileTooltipContentProps) => {
|
|
|
64
82
|
</h3>
|
|
65
83
|
<RunInfo
|
|
66
84
|
current={currentRun.displayValue}
|
|
67
|
-
delta={currentRun.
|
|
85
|
+
delta={currentRun.displayDelta}
|
|
86
|
+
deltaPercentage={currentRun.displayDeltaPercentage}
|
|
68
87
|
deltaType={currentRun.deltaType}
|
|
69
88
|
baseline={baselineRun?.displayValue || '0B'}
|
|
70
89
|
/>
|
|
@@ -91,12 +110,12 @@ interface TileContentProps {
|
|
|
91
110
|
runInfo: MetricRunInfo;
|
|
92
111
|
}
|
|
93
112
|
|
|
94
|
-
const TileContent =
|
|
113
|
+
const TileContent = (props: TileContentProps) => {
|
|
95
114
|
const { label, sizeDisplay, item, runInfo } = props;
|
|
96
115
|
|
|
97
116
|
// Render only the container
|
|
98
117
|
if (sizeDisplay === 'minimal') {
|
|
99
|
-
return <div className={css.tileContent}
|
|
118
|
+
return <div className={css.tileContent} />;
|
|
100
119
|
}
|
|
101
120
|
|
|
102
121
|
const resolvedLabel = label || item.label;
|
|
@@ -104,14 +123,14 @@ const TileContent = forwardRef((props: TileContentProps, ref: Ref<HTMLDivElement
|
|
|
104
123
|
// Render only the label
|
|
105
124
|
if (sizeDisplay === 'small') {
|
|
106
125
|
return (
|
|
107
|
-
<div className={css.tileContent}
|
|
126
|
+
<div className={css.tileContent}>
|
|
108
127
|
<p className={css.tileContentLabel}>{resolvedLabel}</p>
|
|
109
128
|
</div>
|
|
110
129
|
);
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
return (
|
|
114
|
-
<div className={css.tileContent}
|
|
133
|
+
<div className={css.tileContent}>
|
|
115
134
|
<p className={css.tileContentLabel}>{label || item.label}</p>
|
|
116
135
|
<p className={css.tileContentValue}>
|
|
117
136
|
<span className={css.tileContentMetric}>{runInfo.displayValue}</span>
|
|
@@ -123,24 +142,6 @@ const TileContent = forwardRef((props: TileContentProps, ref: Ref<HTMLDivElement
|
|
|
123
142
|
</p>
|
|
124
143
|
</div>
|
|
125
144
|
);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const TileContentWithTooltip = (props: TileContentProps & { parentRef: RefObject<Element> }) => {
|
|
129
|
-
const { label, sizeDisplay, item, runInfo, parentRef } = props;
|
|
130
|
-
|
|
131
|
-
const tooltipState = useTooltipStateWithMouseFollow({ parentRef });
|
|
132
|
-
|
|
133
|
-
return (
|
|
134
|
-
<>
|
|
135
|
-
<TooltipAnchor state={tooltipState} className={css.tileContentTooltipAnchor}>
|
|
136
|
-
<TileContent label={label} sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
|
|
137
|
-
</TooltipAnchor>
|
|
138
|
-
<Tooltip state={tooltipState} className={css.tooltip}>
|
|
139
|
-
<TooltipArrow state={tooltipState} size={16} className={css.tileTooltipArrow} />
|
|
140
|
-
<TileTooltipContent item={item} />
|
|
141
|
-
</Tooltip>
|
|
142
|
-
</>
|
|
143
|
-
);
|
|
144
145
|
};
|
|
145
146
|
|
|
146
147
|
interface TileProps {
|
|
@@ -167,9 +168,6 @@ const Tile = (props: TileProps) => {
|
|
|
167
168
|
[item.key, onClick],
|
|
168
169
|
);
|
|
169
170
|
|
|
170
|
-
const contentRef = useRef<HTMLDivElement>(null);
|
|
171
|
-
const hover = useHoverDirty(contentRef);
|
|
172
|
-
|
|
173
171
|
const className = cx(
|
|
174
172
|
css.tile,
|
|
175
173
|
css[`tile-${runInfo.deltaType}`],
|
|
@@ -184,51 +182,57 @@ const Tile = (props: TileProps) => {
|
|
|
184
182
|
onClick={handleOnClick}
|
|
185
183
|
role="button"
|
|
186
184
|
aria-label={I18N.TILE_LABEL}
|
|
187
|
-
|
|
185
|
+
data-treemap-id={data.id}
|
|
188
186
|
style={{ left, top, width, height }}
|
|
189
187
|
className={className}
|
|
190
188
|
>
|
|
191
|
-
{
|
|
192
|
-
<TileContentWithTooltip
|
|
193
|
-
label={label}
|
|
194
|
-
sizeDisplay={sizeDisplay}
|
|
195
|
-
item={item}
|
|
196
|
-
runInfo={runInfo}
|
|
197
|
-
parentRef={contentRef}
|
|
198
|
-
/>
|
|
199
|
-
) : (
|
|
200
|
-
<TileContent label={label} sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
|
|
201
|
-
)}
|
|
189
|
+
<TileContent label={label} sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
|
|
202
190
|
</div>
|
|
203
191
|
);
|
|
204
192
|
};
|
|
205
193
|
|
|
206
|
-
interface
|
|
207
|
-
|
|
208
|
-
runInfo?: MetricRunInfo;
|
|
209
|
-
baselineDisplayValue?: string;
|
|
194
|
+
interface TileGroupTooltipContentProps {
|
|
195
|
+
node: Tree;
|
|
210
196
|
}
|
|
211
197
|
|
|
212
|
-
const
|
|
213
|
-
const {
|
|
198
|
+
const TileGroupTooltipContent = (props: TileGroupTooltipContentProps) => {
|
|
199
|
+
const { node } = props;
|
|
200
|
+
|
|
201
|
+
// by default show the node id (full path) as title
|
|
202
|
+
const title = node.id || node.label;
|
|
203
|
+
const groupRunInfo = node.total && getGroupRunInfo(node.total);
|
|
214
204
|
|
|
215
205
|
return (
|
|
216
|
-
<Stack space="small" className={css.
|
|
206
|
+
<Stack space="small" className={css.tileTooltipContent}>
|
|
217
207
|
<h3 className={css.tileTooltipContentTitle}>
|
|
218
208
|
<FileName as="code" name={title} />
|
|
219
209
|
</h3>
|
|
220
|
-
{
|
|
210
|
+
{groupRunInfo && (
|
|
221
211
|
<RunInfo
|
|
222
|
-
current={runInfo.displayValue}
|
|
223
|
-
baseline={baselineDisplayValue}
|
|
224
|
-
delta={runInfo.displayDeltaPercentage}
|
|
225
|
-
deltaType={runInfo.deltaType}
|
|
212
|
+
current={groupRunInfo.runInfo.displayValue}
|
|
213
|
+
baseline={groupRunInfo.baselineDisplayValue}
|
|
214
|
+
delta={groupRunInfo.runInfo.displayDeltaPercentage}
|
|
215
|
+
deltaType={groupRunInfo.runInfo.deltaType}
|
|
226
216
|
/>
|
|
227
217
|
)}
|
|
228
218
|
</Stack>
|
|
229
219
|
);
|
|
230
220
|
};
|
|
231
221
|
|
|
222
|
+
interface TreemapTooltipContentProps {
|
|
223
|
+
node: TreeNode;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const TreemapTooltipContent = (props: TreemapTooltipContentProps) => {
|
|
227
|
+
const { node } = props;
|
|
228
|
+
|
|
229
|
+
if ('item' in node) {
|
|
230
|
+
return <TileTooltipContent item={node.item} />;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return <TileGroupTooltipContent node={node} />;
|
|
234
|
+
};
|
|
235
|
+
|
|
232
236
|
interface TileGroupTitleContentProps {
|
|
233
237
|
title?: string;
|
|
234
238
|
runInfo?: MetricRunInfo;
|
|
@@ -250,47 +254,19 @@ const TileGroupTitleContent = (props: TileGroupTitleContentProps) => {
|
|
|
250
254
|
);
|
|
251
255
|
};
|
|
252
256
|
|
|
253
|
-
type
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const TileGroupTitleContentWithTooltip = (props: TileGroupTitleContentWithTooltipProps) => {
|
|
259
|
-
const { parentRef, tooltipContent, ...restProps } = props;
|
|
260
|
-
|
|
261
|
-
const tooltipState = useTooltipStateWithMouseFollow({ parentRef });
|
|
262
|
-
|
|
263
|
-
return (
|
|
264
|
-
<>
|
|
265
|
-
<TooltipAnchor state={tooltipState} className={css.tileContentTooltipAnchor}>
|
|
266
|
-
<TileGroupTitleContent {...restProps} />
|
|
267
|
-
</TooltipAnchor>
|
|
268
|
-
<Tooltip state={tooltipState} className={css.tooltip}>
|
|
269
|
-
<TooltipArrow state={tooltipState} size={16} className={css.tileTooltipArrow} />
|
|
270
|
-
<TileGroupTitleTooltipContent {...tooltipContent} />
|
|
271
|
-
</Tooltip>
|
|
272
|
-
</>
|
|
273
|
-
);
|
|
257
|
+
type TileGroupTitleProps = TileGroupTitleContentProps & {
|
|
258
|
+
/**
|
|
259
|
+
* Node id - used to resolve the tooltip content on hover
|
|
260
|
+
*/
|
|
261
|
+
id: string;
|
|
274
262
|
};
|
|
275
263
|
|
|
276
|
-
type TileGroupTitleProps = Omit<TileGroupTitleContentWithTooltipProps, 'parentRef'>;
|
|
277
|
-
|
|
278
264
|
const TileGroupTitle = (props: TileGroupTitleProps) => {
|
|
279
|
-
const {
|
|
280
|
-
const contentRef = useRef<HTMLDivElement>(null);
|
|
281
|
-
const hover = useHoverDirty(contentRef);
|
|
265
|
+
const { id, ...restProps } = props;
|
|
282
266
|
|
|
283
267
|
return (
|
|
284
|
-
<div className={css.tileGroupTitle}
|
|
285
|
-
{
|
|
286
|
-
<TileGroupTitleContentWithTooltip
|
|
287
|
-
parentRef={contentRef}
|
|
288
|
-
tooltipContent={tooltipContent}
|
|
289
|
-
{...restProps}
|
|
290
|
-
/>
|
|
291
|
-
) : (
|
|
292
|
-
<TileGroupTitleContent {...restProps} />
|
|
293
|
-
)}
|
|
268
|
+
<div className={css.tileGroupTitle} data-treemap-id={id}>
|
|
269
|
+
<TileGroupTitleContent {...restProps} />
|
|
294
270
|
</div>
|
|
295
271
|
);
|
|
296
272
|
};
|
|
@@ -370,36 +346,14 @@ const TileGroup = (props: TileGroupProps) => {
|
|
|
370
346
|
);
|
|
371
347
|
|
|
372
348
|
// Prepare data
|
|
373
|
-
const
|
|
374
|
-
if (!total) {
|
|
375
|
-
return [];
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const resolvedRunInfo = getMetricRunInfo(
|
|
379
|
-
METRIC_TYPE_CONFIGS.METRIC_TYPE_FILE_SIZE,
|
|
380
|
-
total.current,
|
|
381
|
-
total.baseline,
|
|
382
|
-
) as MetricRunInfo;
|
|
383
|
-
const resolvedBaselineDisplayValue = formatFileSize(total.baseline);
|
|
384
|
-
const resolvedDeltaType = resolveGroupDeltaType(resolvedRunInfo);
|
|
385
|
-
|
|
386
|
-
return [resolvedRunInfo, resolvedBaselineDisplayValue, resolvedDeltaType];
|
|
387
|
-
}, [total]);
|
|
349
|
+
const groupRunInfo = useMemo(() => (total ? getGroupRunInfo(total) : undefined), [total]);
|
|
388
350
|
|
|
389
351
|
const rootClassName = cx(
|
|
390
352
|
css.tileGroup,
|
|
391
|
-
css[`tileGroup--${deltaType}`],
|
|
353
|
+
css[`tileGroup--${groupRunInfo?.deltaType}`],
|
|
392
354
|
css[`tileGroup--${displaySize}`],
|
|
393
355
|
);
|
|
394
356
|
|
|
395
|
-
// Tooltip data
|
|
396
|
-
// - by default show the node id (full path) as title
|
|
397
|
-
const tooltipContent = {
|
|
398
|
-
title: id || title,
|
|
399
|
-
runInfo,
|
|
400
|
-
baselineDisplayValue,
|
|
401
|
-
} as TileGroupTitleTooltipContentProps;
|
|
402
|
-
|
|
403
357
|
if (title && displaySize === 'minimal') {
|
|
404
358
|
return (
|
|
405
359
|
<div
|
|
@@ -409,7 +363,7 @@ const TileGroup = (props: TileGroupProps) => {
|
|
|
409
363
|
className={rootClassName}
|
|
410
364
|
style={{ left, top, width, height }}
|
|
411
365
|
>
|
|
412
|
-
<TileGroupTitle
|
|
366
|
+
<TileGroupTitle id={id} />
|
|
413
367
|
</div>
|
|
414
368
|
);
|
|
415
369
|
}
|
|
@@ -423,7 +377,7 @@ const TileGroup = (props: TileGroupProps) => {
|
|
|
423
377
|
style={{ left, top, width, height }}
|
|
424
378
|
className={rootClassName}
|
|
425
379
|
>
|
|
426
|
-
<TileGroupTitle
|
|
380
|
+
<TileGroupTitle id={id} title={title} />
|
|
427
381
|
</div>
|
|
428
382
|
);
|
|
429
383
|
}
|
|
@@ -436,7 +390,7 @@ const TileGroup = (props: TileGroupProps) => {
|
|
|
436
390
|
className={cx(rootClassName, css.tileGroupSizeDefault)}
|
|
437
391
|
style={{ left, top, width, height }}
|
|
438
392
|
>
|
|
439
|
-
<TileGroupTitle title={title} runInfo={runInfo}
|
|
393
|
+
<TileGroupTitle id={id} title={title} runInfo={groupRunInfo?.runInfo} />
|
|
440
394
|
{childNodes?.map((childNode) => {
|
|
441
395
|
if ('children' in childNode) {
|
|
442
396
|
const groupData = childNode.data as Tree;
|
|
@@ -542,34 +496,105 @@ export const MetricsTreemap = (props: MetricsTreemapProps & ComponentProps<'div'
|
|
|
542
496
|
const [containerRef, { width, height }] = useMeasure<HTMLDivElement>();
|
|
543
497
|
const rootNode = useMetricsTreemapHierarchy({ treeNodes, width, height, nested });
|
|
544
498
|
|
|
499
|
+
/**
|
|
500
|
+
* A single tooltip is shared by every tile and tile group title:
|
|
501
|
+
* - tiles are tagged with `data-treemap-id`
|
|
502
|
+
* - the pointer is tracked by one delegated handler on the canvas
|
|
503
|
+
* - hiding is deferred so that the tooltip stays up while the pointer
|
|
504
|
+
* crosses the gutter between two tiles
|
|
505
|
+
*/
|
|
506
|
+
const nodesIndex = useMemo(() => getTreemapNodesIndex(treeNodes), [treeNodes]);
|
|
507
|
+
const { tooltipState, setPointer, hoveredNodeId, setHoveredNode, clearHoveredNode } =
|
|
508
|
+
useTreemapTooltipState();
|
|
509
|
+
const canvasRef = useRef<HTMLDivElement>(null);
|
|
510
|
+
|
|
511
|
+
// Resolved on render so that the content cannot go stale when the data changes
|
|
512
|
+
const tooltipNode = hoveredNodeId === null ? null : nodesIndex.get(hoveredNodeId);
|
|
513
|
+
|
|
514
|
+
const handlePointerEvent = useCallback(
|
|
515
|
+
(event: MouseEvent) => {
|
|
516
|
+
// Anchor to the canvas to let ariakit track scrolling/resizing
|
|
517
|
+
tooltipState.anchorRef.current = canvasRef.current;
|
|
518
|
+
|
|
519
|
+
setPointer(event.clientX, event.clientY);
|
|
520
|
+
|
|
521
|
+
const target = (event.target as HTMLElement | null)?.closest<HTMLElement>(NODE_ID_SELECTOR);
|
|
522
|
+
setHoveredNode(target?.dataset.treemapId ?? null);
|
|
523
|
+
},
|
|
524
|
+
[tooltipState, setPointer, setHoveredNode],
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
const hasNodes = Boolean(rootNode.children && rootNode.children.length > 0);
|
|
528
|
+
|
|
529
|
+
useEffect(() => {
|
|
530
|
+
const canvas = canvasRef.current;
|
|
531
|
+
|
|
532
|
+
if (!canvas) {
|
|
533
|
+
return undefined;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* `mouseover` is needed on top of `mousemove`: the hovered element can
|
|
538
|
+
* change without the pointer moving (scrolling, resizing, layout shifts),
|
|
539
|
+
* and the browser only emits boundary events in that case.
|
|
540
|
+
*
|
|
541
|
+
* Native listeners are used instead of the React props because React skips
|
|
542
|
+
* `mouseover` dispatch when `relatedTarget` is inside the same tree.
|
|
543
|
+
*/
|
|
544
|
+
canvas.addEventListener('mousemove', handlePointerEvent);
|
|
545
|
+
canvas.addEventListener('mouseover', handlePointerEvent);
|
|
546
|
+
canvas.addEventListener('mouseleave', clearHoveredNode);
|
|
547
|
+
|
|
548
|
+
return () => {
|
|
549
|
+
canvas.removeEventListener('mousemove', handlePointerEvent);
|
|
550
|
+
canvas.removeEventListener('mouseover', handlePointerEvent);
|
|
551
|
+
canvas.removeEventListener('mouseleave', clearHoveredNode);
|
|
552
|
+
};
|
|
553
|
+
}, [hasNodes, handlePointerEvent, clearHoveredNode]);
|
|
554
|
+
|
|
555
|
+
// Keep the cells out of the tooltip render path
|
|
556
|
+
// (`rootNode` is mutated in place by d3, so the layout size drives the memo)
|
|
557
|
+
const canvasContent = useMemo(
|
|
558
|
+
() => (
|
|
559
|
+
<TileGroup
|
|
560
|
+
title={nested ? rootNode.data.label : undefined}
|
|
561
|
+
total={(rootNode.data as Tree).total}
|
|
562
|
+
id={rootNode.data.id}
|
|
563
|
+
childNodes={rootNode.children}
|
|
564
|
+
onItemClick={onItemClick}
|
|
565
|
+
onGroupClick={onGroupClick}
|
|
566
|
+
left={rootNode.x0}
|
|
567
|
+
top={rootNode.y0}
|
|
568
|
+
absoluteLeft={rootNode.x0}
|
|
569
|
+
absoluteTop={rootNode.y0}
|
|
570
|
+
width={rootNode.x1 - rootNode.x0}
|
|
571
|
+
height={rootNode.y1 - rootNode.y0}
|
|
572
|
+
/>
|
|
573
|
+
),
|
|
574
|
+
[rootNode, width, height, nested, onItemClick, onGroupClick],
|
|
575
|
+
);
|
|
576
|
+
|
|
545
577
|
return (
|
|
546
578
|
<div
|
|
547
579
|
className={cx(css.root, nested && css.nested, className)}
|
|
548
580
|
{...restProps}
|
|
549
581
|
ref={containerRef}
|
|
550
582
|
>
|
|
551
|
-
{
|
|
552
|
-
<div className={css.canvas}>
|
|
553
|
-
|
|
554
|
-
title={nested ? rootNode.data.label : undefined}
|
|
555
|
-
total={(rootNode.data as Tree).total}
|
|
556
|
-
id={rootNode.data.id}
|
|
557
|
-
childNodes={rootNode.children}
|
|
558
|
-
onItemClick={onItemClick}
|
|
559
|
-
onGroupClick={onGroupClick}
|
|
560
|
-
left={rootNode.x0}
|
|
561
|
-
top={rootNode.y0}
|
|
562
|
-
absoluteLeft={rootNode.x0}
|
|
563
|
-
absoluteTop={rootNode.y0}
|
|
564
|
-
width={rootNode.x1 - rootNode.x0}
|
|
565
|
-
height={rootNode.y1 - rootNode.y0}
|
|
566
|
-
/>
|
|
583
|
+
{hasNodes ? (
|
|
584
|
+
<div className={css.canvas} ref={canvasRef}>
|
|
585
|
+
{canvasContent}
|
|
567
586
|
</div>
|
|
568
587
|
) : (
|
|
569
588
|
<div className={css.emptyMessage}>
|
|
570
589
|
<div className={css.emptyMessageWrapper}>{emptyMessage}</div>
|
|
571
590
|
</div>
|
|
572
591
|
)}
|
|
592
|
+
{tooltipNode && (
|
|
593
|
+
<Tooltip state={tooltipState} className={css.tooltip}>
|
|
594
|
+
<TooltipArrow state={tooltipState} size={16} className={css.tileTooltipArrow} />
|
|
595
|
+
<TreemapTooltipContent node={tooltipNode} />
|
|
596
|
+
</Tooltip>
|
|
597
|
+
)}
|
|
573
598
|
</div>
|
|
574
599
|
);
|
|
575
600
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useDebounce, useMouseHovered } from 'react-use';
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
2
|
import { useTooltipState } from 'ariakit/tooltip';
|
|
4
3
|
import {
|
|
5
4
|
DeltaType,
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
|
|
11
10
|
import {
|
|
12
11
|
type TreeLeaf,
|
|
12
|
+
type TreeNode,
|
|
13
13
|
type TreeNodeChildren,
|
|
14
14
|
type Tree,
|
|
15
15
|
type TreeTotal,
|
|
@@ -158,6 +158,28 @@ export function getTreemapNodesGroupedByPath(items: Array<ReportMetricRow>): Tre
|
|
|
158
158
|
};
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Index every tree node by id, to allow resolving the hovered node from the
|
|
163
|
+
* `data-treemap-id` DOM marker without walking the tree on every pointer move
|
|
164
|
+
*/
|
|
165
|
+
export function getTreemapNodesIndex(
|
|
166
|
+
node: Tree,
|
|
167
|
+
index: Map<string, TreeNode> = new Map(),
|
|
168
|
+
): Map<string, TreeNode> {
|
|
169
|
+
index.set(node.id, node);
|
|
170
|
+
|
|
171
|
+
node.children.forEach((childNode) => {
|
|
172
|
+
if ('children' in childNode) {
|
|
173
|
+
getTreemapNodesIndex(childNode, index);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
index.set(childNode.id, childNode);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return index;
|
|
181
|
+
}
|
|
182
|
+
|
|
161
183
|
export function resolveGroupDeltaType(
|
|
162
184
|
metricRunInfo?: MetricRunInfo | MetricRunInfoBaseline,
|
|
163
185
|
): DeltaType.NO_CHANGE | DeltaType.NEGATIVE | DeltaType.POSITIVE {
|
|
@@ -225,50 +247,142 @@ export function resolveTileSizeDisplay(width: number, height: number): TileSizeD
|
|
|
225
247
|
return 'default';
|
|
226
248
|
}
|
|
227
249
|
|
|
228
|
-
interface
|
|
229
|
-
/**
|
|
230
|
-
* React ref to parent div
|
|
231
|
-
*/
|
|
232
|
-
parentRef: RefObject<Element>;
|
|
250
|
+
interface UseTreemapTooltipStateOptions {
|
|
233
251
|
/**
|
|
234
252
|
* Tooltip gutter
|
|
235
253
|
*/
|
|
236
254
|
gutter?: number;
|
|
237
255
|
/**
|
|
238
|
-
*
|
|
256
|
+
* Delay before showing the tooltip
|
|
239
257
|
*/
|
|
240
258
|
timeout?: number;
|
|
259
|
+
/**
|
|
260
|
+
* Delay before hiding the tooltip once the pointer is no longer over a cell.
|
|
261
|
+
*
|
|
262
|
+
* Tiles are separated by a gutter that belongs to no node, so without this
|
|
263
|
+
* grace period moving from one cell to another would close and reopen the
|
|
264
|
+
* tooltip every time the pointer crosses the gap.
|
|
265
|
+
*/
|
|
266
|
+
hideTimeout?: number;
|
|
241
267
|
}
|
|
242
268
|
|
|
243
269
|
/**
|
|
244
|
-
* Ariakit tooltip state
|
|
270
|
+
* Ariakit tooltip state shared by every tile/tile group of a treemap.
|
|
271
|
+
*
|
|
272
|
+
* The tooltip is anchored to a virtual 1x1 rect that follows the pointer, so
|
|
273
|
+
* no `TooltipAnchor` is rendered per cell: the anchor position is stored on a
|
|
274
|
+
* ref and ariakit is asked to reposition at most once per animation frame,
|
|
275
|
+
* which keeps the treemap itself out of the pointer move render path.
|
|
276
|
+
*
|
|
277
|
+
* The hovered node id is owned here as well, so that all the tooltip timing
|
|
278
|
+
* lives in a single place.
|
|
245
279
|
*/
|
|
246
|
-
export function
|
|
247
|
-
const {
|
|
280
|
+
export function useTreemapTooltipState(options: UseTreemapTooltipStateOptions = {}) {
|
|
281
|
+
const { gutter = 16, timeout = 240, hideTimeout = timeout } = options;
|
|
248
282
|
|
|
249
|
-
const
|
|
283
|
+
const pointerRef = useRef<{ x: number; y: number } | null>(null);
|
|
284
|
+
const frameRef = useRef(0);
|
|
285
|
+
const hideTimeoutRef = useRef(0);
|
|
250
286
|
|
|
251
|
-
// Return custom rect based on the
|
|
287
|
+
// Return custom rect based on the pointer position
|
|
252
288
|
const getAnchorRect = useCallback(() => {
|
|
253
|
-
|
|
254
|
-
|
|
289
|
+
const pointer = pointerRef.current;
|
|
290
|
+
|
|
291
|
+
// Skip the custom rect until we have a pointer position
|
|
292
|
+
if (!pointer) {
|
|
255
293
|
return null;
|
|
256
294
|
}
|
|
257
295
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
y: pointer.docY - (document?.defaultView?.scrollY ?? 0),
|
|
261
|
-
w: 1,
|
|
262
|
-
h: 1,
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
return newRect;
|
|
266
|
-
}, [pointer.docX, pointer.docY]);
|
|
296
|
+
return { x: pointer.x, y: pointer.y, w: 1, h: 1 };
|
|
297
|
+
}, []);
|
|
267
298
|
|
|
268
299
|
const tooltipState = useTooltipState({ gutter, getAnchorRect, timeout });
|
|
300
|
+
const { mounted, render, show, hide } = tooltipState;
|
|
301
|
+
|
|
302
|
+
const [hoveredNodeId, setHoveredNodeId] = useState<string | null>(null);
|
|
303
|
+
const hoveredNodeIdRef = useRef<string | null>(null);
|
|
304
|
+
|
|
305
|
+
useEffect(
|
|
306
|
+
() => () => {
|
|
307
|
+
cancelAnimationFrame(frameRef.current);
|
|
308
|
+
window.clearTimeout(hideTimeoutRef.current);
|
|
309
|
+
},
|
|
310
|
+
[],
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const setPointer = useCallback(
|
|
314
|
+
(x: number, y: number) => {
|
|
315
|
+
pointerRef.current = { x, y };
|
|
316
|
+
|
|
317
|
+
// Reposition only while the tooltip is visible, once per frame
|
|
318
|
+
if (!mounted || frameRef.current) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
frameRef.current = requestAnimationFrame(() => {
|
|
323
|
+
frameRef.current = 0;
|
|
324
|
+
render();
|
|
325
|
+
});
|
|
326
|
+
},
|
|
327
|
+
[mounted, render],
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const commitHoveredNode = useCallback(
|
|
331
|
+
(nodeId: string | null) => {
|
|
332
|
+
hoveredNodeIdRef.current = nodeId;
|
|
333
|
+
setHoveredNodeId(nodeId);
|
|
334
|
+
|
|
335
|
+
if (nodeId === null) {
|
|
336
|
+
hide();
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
show();
|
|
341
|
+
},
|
|
342
|
+
[hide, show],
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Set the hovered node. Showing another node is applied right away, while
|
|
347
|
+
* hiding is deferred so that the tooltip survives the gap between two cells.
|
|
348
|
+
*/
|
|
349
|
+
const setHoveredNode = useCallback(
|
|
350
|
+
(nodeId: string | null) => {
|
|
351
|
+
window.clearTimeout(hideTimeoutRef.current);
|
|
352
|
+
hideTimeoutRef.current = 0;
|
|
353
|
+
|
|
354
|
+
// Guard against re-triggering `show`, which would restart the show timeout
|
|
355
|
+
if (nodeId === hoveredNodeIdRef.current) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (nodeId !== null || !hideTimeout) {
|
|
360
|
+
commitHoveredNode(nodeId);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
hideTimeoutRef.current = window.setTimeout(() => {
|
|
365
|
+
hideTimeoutRef.current = 0;
|
|
366
|
+
commitHoveredNode(null);
|
|
367
|
+
}, hideTimeout);
|
|
368
|
+
},
|
|
369
|
+
[commitHoveredNode, hideTimeout],
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Hide the tooltip right away, without the grace period - the pointer left
|
|
374
|
+
* the treemap, there is no next cell to move to.
|
|
375
|
+
*/
|
|
376
|
+
const clearHoveredNode = useCallback(() => {
|
|
377
|
+
window.clearTimeout(hideTimeoutRef.current);
|
|
378
|
+
hideTimeoutRef.current = 0;
|
|
379
|
+
|
|
380
|
+
if (hoveredNodeIdRef.current === null) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
269
383
|
|
|
270
|
-
|
|
271
|
-
|
|
384
|
+
commitHoveredNode(null);
|
|
385
|
+
}, [commitHoveredNode]);
|
|
272
386
|
|
|
273
|
-
return tooltipState;
|
|
387
|
+
return { tooltipState, setPointer, hoveredNodeId, setHoveredNode, clearHoveredNode };
|
|
274
388
|
}
|