@automattic/charts 0.56.2 → 0.56.4
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/CHANGELOG.md +14 -0
- package/dist/charts/bar-chart/index.cjs +5 -5
- package/dist/charts/bar-chart/index.css +12 -0
- package/dist/charts/bar-chart/index.css.map +1 -1
- package/dist/charts/bar-chart/index.js +4 -4
- package/dist/charts/bar-list-chart/index.cjs +6 -6
- package/dist/charts/bar-list-chart/index.css +12 -0
- package/dist/charts/bar-list-chart/index.css.map +1 -1
- package/dist/charts/bar-list-chart/index.js +5 -5
- package/dist/charts/conversion-funnel-chart/index.cjs +5 -3
- package/dist/charts/conversion-funnel-chart/index.cjs.map +1 -1
- package/dist/charts/conversion-funnel-chart/index.css +14 -1
- package/dist/charts/conversion-funnel-chart/index.css.map +1 -1
- package/dist/charts/conversion-funnel-chart/index.d.cts +2 -0
- package/dist/charts/conversion-funnel-chart/index.d.ts +2 -0
- package/dist/charts/conversion-funnel-chart/index.js +4 -2
- package/dist/charts/geo-chart/index.cjs +4 -4
- package/dist/charts/geo-chart/index.css +12 -0
- package/dist/charts/geo-chart/index.css.map +1 -1
- package/dist/charts/geo-chart/index.js +3 -3
- package/dist/charts/leaderboard-chart/index.cjs +5 -5
- package/dist/charts/leaderboard-chart/index.css +12 -0
- package/dist/charts/leaderboard-chart/index.css.map +1 -1
- package/dist/charts/leaderboard-chart/index.js +4 -4
- package/dist/charts/line-chart/index.cjs +5 -5
- package/dist/charts/line-chart/index.css +12 -0
- package/dist/charts/line-chart/index.css.map +1 -1
- package/dist/charts/line-chart/index.js +4 -4
- package/dist/charts/pie-chart/index.cjs +7 -7
- package/dist/charts/pie-chart/index.css +12 -0
- package/dist/charts/pie-chart/index.css.map +1 -1
- package/dist/charts/pie-chart/index.js +6 -6
- package/dist/charts/pie-semi-circle-chart/index.cjs +7 -7
- package/dist/charts/pie-semi-circle-chart/index.css +12 -0
- package/dist/charts/pie-semi-circle-chart/index.css.map +1 -1
- package/dist/charts/pie-semi-circle-chart/index.js +6 -6
- package/dist/charts/sparkline/index.cjs +6 -6
- package/dist/charts/sparkline/index.css +12 -0
- package/dist/charts/sparkline/index.css.map +1 -1
- package/dist/charts/sparkline/index.js +5 -5
- package/dist/{chunk-OTZT3MC2.cjs → chunk-2A34OA5O.cjs} +19 -20
- package/dist/chunk-2A34OA5O.cjs.map +1 -0
- package/dist/chunk-4YYROZDJ.cjs +375 -0
- package/dist/chunk-4YYROZDJ.cjs.map +1 -0
- package/dist/{chunk-YYQ4IK5V.cjs → chunk-5N77S5N3.cjs} +103 -80
- package/dist/chunk-5N77S5N3.cjs.map +1 -0
- package/dist/chunk-66BXSWMW.cjs +1065 -0
- package/dist/chunk-66BXSWMW.cjs.map +1 -0
- package/dist/{chunk-CEZGL6YP.js → chunk-6CCZL2JJ.js} +15 -7
- package/dist/chunk-6CCZL2JJ.js.map +1 -0
- package/dist/{chunk-NW3RUYK2.cjs → chunk-7QDEU3KN.cjs} +15 -22
- package/dist/chunk-7QDEU3KN.cjs.map +1 -0
- package/dist/{chunk-H34CJSR6.js → chunk-AWNCAKZY.js} +367 -358
- package/dist/chunk-AWNCAKZY.js.map +1 -0
- package/dist/{chunk-5XI443YP.js → chunk-BPYKWMI7.js} +72 -64
- package/dist/chunk-BPYKWMI7.js.map +1 -0
- package/dist/{chunk-7UJPVCMB.cjs → chunk-CERFRCXD.cjs} +265 -262
- package/dist/chunk-CERFRCXD.cjs.map +1 -0
- package/dist/chunk-CMHPXSCI.js +351 -0
- package/dist/chunk-CMHPXSCI.js.map +1 -0
- package/dist/chunk-EBDUXL5K.js +421 -0
- package/dist/chunk-EBDUXL5K.js.map +1 -0
- package/dist/{chunk-2VPPTJS2.js → chunk-FZYJM5PN.js} +256 -253
- package/dist/chunk-FZYJM5PN.js.map +1 -0
- package/dist/chunk-GBDFC74U.cjs +165 -0
- package/dist/chunk-GBDFC74U.cjs.map +1 -0
- package/dist/{chunk-ODF5O5PV.cjs → chunk-HNEG3EFJ.cjs} +154 -170
- package/dist/chunk-HNEG3EFJ.cjs.map +1 -0
- package/dist/{chunk-SRXJLAKG.cjs → chunk-I2276W3I.cjs} +28 -37
- package/dist/chunk-I2276W3I.cjs.map +1 -0
- package/dist/chunk-KKPZ4MVF.js +375 -0
- package/dist/chunk-KKPZ4MVF.js.map +1 -0
- package/dist/chunk-KMYJJTSR.cjs +421 -0
- package/dist/chunk-KMYJJTSR.cjs.map +1 -0
- package/dist/{chunk-A3AEEGKR.js → chunk-KXRWNFQJ.js} +20 -21
- package/dist/chunk-KXRWNFQJ.js.map +1 -0
- package/dist/{chunk-TVV7ZI7C.cjs → chunk-LSV7F26B.cjs} +362 -353
- package/dist/chunk-LSV7F26B.cjs.map +1 -0
- package/dist/{chunk-T4J6TI55.js → chunk-M7PRGJFE.js} +102 -79
- package/dist/chunk-M7PRGJFE.js.map +1 -0
- package/dist/{chunk-TNRKEBTA.js → chunk-PGJAZN2H.js} +148 -164
- package/dist/{chunk-TNRKEBTA.js.map → chunk-PGJAZN2H.js.map} +1 -1
- package/dist/chunk-R23BFDIW.js +1065 -0
- package/dist/chunk-R23BFDIW.js.map +1 -0
- package/dist/{chunk-HIWNB5PK.cjs → chunk-RCY6XLGU.cjs} +13 -5
- package/dist/chunk-RCY6XLGU.cjs.map +1 -0
- package/dist/chunk-RSYD434G.cjs +351 -0
- package/dist/chunk-RSYD434G.cjs.map +1 -0
- package/dist/{chunk-C33AQZEC.js → chunk-TYIH5LMV.js} +16 -23
- package/dist/chunk-TYIH5LMV.js.map +1 -0
- package/dist/chunk-WMWAUOQ4.js +165 -0
- package/dist/chunk-WMWAUOQ4.js.map +1 -0
- package/dist/chunk-XWYZIFZW.js +66 -0
- package/dist/chunk-XWYZIFZW.js.map +1 -0
- package/dist/{chunk-7HROSZRS.cjs → chunk-Y3NNQMAX.cjs} +70 -62
- package/dist/chunk-Y3NNQMAX.cjs.map +1 -0
- package/dist/chunk-ZXEFMKVP.cjs +120 -0
- package/dist/chunk-ZXEFMKVP.cjs.map +1 -0
- package/dist/chunk-ZY4FXLMM.js +120 -0
- package/dist/chunk-ZY4FXLMM.js.map +1 -0
- package/dist/components/legend/index.cjs +2 -2
- package/dist/components/legend/index.css +12 -0
- package/dist/components/legend/index.css.map +1 -1
- package/dist/components/legend/index.js +1 -1
- package/dist/components/tooltip/index.cjs +2 -2
- package/dist/components/tooltip/index.js +1 -1
- package/dist/components/trend-indicator/index.cjs +2 -2
- package/dist/components/trend-indicator/index.js +1 -1
- package/dist/hooks/index.cjs +4 -2
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.css +12 -0
- package/dist/hooks/index.css.map +1 -1
- package/dist/hooks/index.d.cts +28 -2
- package/dist/hooks/index.d.ts +28 -2
- package/dist/hooks/index.js +3 -1
- package/dist/index.cjs +18 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +14 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -17
- package/dist/providers/index.cjs +2 -2
- package/dist/providers/index.css +12 -0
- package/dist/providers/index.css.map +1 -1
- package/dist/providers/index.d.cts +1 -1
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +1 -1
- package/dist/{themes-DQs9rbN5.d.cts → themes-BDVaIfBz.d.cts} +9 -0
- package/dist/{themes-CRV5fVzJ.d.ts → themes-mcS8QNkQ.d.ts} +9 -0
- package/package.json +7 -4
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +2 -1
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +16 -6
- package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +34 -0
- package/src/charts/conversion-funnel-chart/types.ts +2 -0
- package/src/charts/pie-chart/pie-chart.tsx +2 -3
- package/src/hooks/index.ts +1 -0
- package/src/hooks/test/use-tooltip-portal-relocator.test.ts +216 -0
- package/src/hooks/use-tooltip-portal-relocator.module.scss +10 -0
- package/src/hooks/use-tooltip-portal-relocator.ts +177 -0
- package/src/providers/chart-context/global-charts-provider.tsx +18 -1
- package/tsup.config.ts +11 -0
- package/dist/chunk-2VPPTJS2.js.map +0 -1
- package/dist/chunk-5XI443YP.js.map +0 -1
- package/dist/chunk-7HROSZRS.cjs.map +0 -1
- package/dist/chunk-7UJPVCMB.cjs.map +0 -1
- package/dist/chunk-A3AEEGKR.js.map +0 -1
- package/dist/chunk-C33AQZEC.js.map +0 -1
- package/dist/chunk-CEZGL6YP.js.map +0 -1
- package/dist/chunk-COOC2TVQ.js +0 -167
- package/dist/chunk-COOC2TVQ.js.map +0 -1
- package/dist/chunk-EJHLLXBV.js +0 -362
- package/dist/chunk-EJHLLXBV.js.map +0 -1
- package/dist/chunk-FWMJ2FR2.js +0 -121
- package/dist/chunk-FWMJ2FR2.js.map +0 -1
- package/dist/chunk-GRYNIPWH.cjs +0 -385
- package/dist/chunk-GRYNIPWH.cjs.map +0 -1
- package/dist/chunk-H34CJSR6.js.map +0 -1
- package/dist/chunk-HIWNB5PK.cjs.map +0 -1
- package/dist/chunk-IZWC33YN.cjs +0 -357
- package/dist/chunk-IZWC33YN.cjs.map +0 -1
- package/dist/chunk-KOF32DBL.cjs +0 -1058
- package/dist/chunk-KOF32DBL.cjs.map +0 -1
- package/dist/chunk-LHWRZMF7.cjs +0 -362
- package/dist/chunk-LHWRZMF7.cjs.map +0 -1
- package/dist/chunk-MFRS2PEY.cjs +0 -121
- package/dist/chunk-MFRS2PEY.cjs.map +0 -1
- package/dist/chunk-MMDLXS6O.js +0 -75
- package/dist/chunk-MMDLXS6O.js.map +0 -1
- package/dist/chunk-NW3RUYK2.cjs.map +0 -1
- package/dist/chunk-ODF5O5PV.cjs.map +0 -1
- package/dist/chunk-OTZT3MC2.cjs.map +0 -1
- package/dist/chunk-SBRMWDWM.js +0 -357
- package/dist/chunk-SBRMWDWM.js.map +0 -1
- package/dist/chunk-SRXJLAKG.cjs.map +0 -1
- package/dist/chunk-T4J6TI55.js.map +0 -1
- package/dist/chunk-TVV7ZI7C.cjs.map +0 -1
- package/dist/chunk-XVMXWV3C.cjs +0 -167
- package/dist/chunk-XVMXWV3C.cjs.map +0 -1
- package/dist/chunk-YYQ4IK5V.cjs.map +0 -1
- package/dist/chunk-ZDNCF642.js +0 -1058
- package/dist/chunk-ZDNCF642.js.map +0 -1
- package/dist/chunk-ZWBUEHKF.js +0 -385
- package/dist/chunk-ZWBUEHKF.js.map +0 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/* eslint-disable testing-library/no-node-access */
|
|
2
|
+
import { renderHook } from '@testing-library/react';
|
|
3
|
+
import { useTooltipPortalRelocator } from '../use-tooltip-portal-relocator';
|
|
4
|
+
|
|
5
|
+
// In the production build, CSS module class names are hashed (e.g. "a8ccharts-abc123").
|
|
6
|
+
// In jest, the SCSS module import is stubbed to a filename string, so
|
|
7
|
+
// styles.relocatedPortal resolves to undefined and classList.add() is a no-op.
|
|
8
|
+
// We mock the module to return a proper class map so we can assert on class names.
|
|
9
|
+
jest.mock( '../use-tooltip-portal-relocator.module.scss', () => ( {
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: { relocatedPortal: 'relocatedPortal' },
|
|
12
|
+
} ) );
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a mock visx tooltip portal node for testing.
|
|
16
|
+
* @return {HTMLDivElement} A div mimicking a visx tooltip portal.
|
|
17
|
+
*/
|
|
18
|
+
function createVisxPortalNode(): HTMLDivElement {
|
|
19
|
+
const portal = document.createElement( 'div' );
|
|
20
|
+
const child = document.createElement( 'div' );
|
|
21
|
+
child.className = 'visx-tooltip';
|
|
22
|
+
portal.appendChild( child );
|
|
23
|
+
return portal;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Sets up a container, ref, and renders the hook.
|
|
28
|
+
* Optionally appends a visx portal node to document.body before rendering.
|
|
29
|
+
* @param options - Setup options.
|
|
30
|
+
* @param options.withPortal - If true, creates and appends a visx portal before rendering.
|
|
31
|
+
* @return Setup result with container, ref, unmount, and optionally the portal node.
|
|
32
|
+
*/
|
|
33
|
+
function setupHook( { withPortal = false } = {} ) {
|
|
34
|
+
const container = document.createElement( 'div' );
|
|
35
|
+
document.body.appendChild( container );
|
|
36
|
+
|
|
37
|
+
let portal: HTMLDivElement | undefined;
|
|
38
|
+
if ( withPortal ) {
|
|
39
|
+
portal = createVisxPortalNode();
|
|
40
|
+
document.body.appendChild( portal );
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ref = { current: container };
|
|
44
|
+
const { unmount } = renderHook( () => useTooltipPortalRelocator( ref ) );
|
|
45
|
+
|
|
46
|
+
return { container, ref, unmount, portal };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe( 'useTooltipPortalRelocator', () => {
|
|
50
|
+
let nativeRemoveChild: typeof document.body.removeChild;
|
|
51
|
+
|
|
52
|
+
beforeAll( () => {
|
|
53
|
+
nativeRemoveChild = document.body.removeChild;
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
afterEach( () => {
|
|
57
|
+
// Restore native removeChild and clear all body children to prevent
|
|
58
|
+
// leaked portals from interfering with subsequent tests.
|
|
59
|
+
document.body.removeChild = nativeRemoveChild;
|
|
60
|
+
while ( document.body.firstChild ) {
|
|
61
|
+
nativeRemoveChild.call( document.body, document.body.firstChild );
|
|
62
|
+
}
|
|
63
|
+
} );
|
|
64
|
+
|
|
65
|
+
test( 'does nothing when containerRef is undefined', () => {
|
|
66
|
+
const { unmount } = renderHook( () => useTooltipPortalRelocator( undefined ) );
|
|
67
|
+
const portal = createVisxPortalNode();
|
|
68
|
+
document.body.appendChild( portal );
|
|
69
|
+
expect( portal.parentNode ).toBe( document.body );
|
|
70
|
+
unmount();
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
test( 'does nothing when containerRef.current is null', () => {
|
|
74
|
+
const nullRef = { current: null };
|
|
75
|
+
const { unmount } = renderHook( () => useTooltipPortalRelocator( nullRef ) );
|
|
76
|
+
const portal = createVisxPortalNode();
|
|
77
|
+
document.body.appendChild( portal );
|
|
78
|
+
expect( portal.parentNode ).toBe( document.body );
|
|
79
|
+
unmount();
|
|
80
|
+
} );
|
|
81
|
+
|
|
82
|
+
test( 'relocates existing visx portal nodes into the container', () => {
|
|
83
|
+
const { container, unmount, portal } = setupHook( { withPortal: true } );
|
|
84
|
+
expect( portal!.parentNode ).toBe( container );
|
|
85
|
+
unmount();
|
|
86
|
+
} );
|
|
87
|
+
|
|
88
|
+
test( 'applies relocated-portal class to relocated portals', () => {
|
|
89
|
+
const { unmount, portal } = setupHook( { withPortal: true } );
|
|
90
|
+
expect( portal! ).toHaveClass( 'relocatedPortal' );
|
|
91
|
+
unmount();
|
|
92
|
+
} );
|
|
93
|
+
|
|
94
|
+
test( 'does not relocate newly added non-visx nodes', async () => {
|
|
95
|
+
const { unmount } = setupHook();
|
|
96
|
+
|
|
97
|
+
const regularDiv = document.createElement( 'div' );
|
|
98
|
+
regularDiv.id = 'some-id';
|
|
99
|
+
document.body.appendChild( regularDiv );
|
|
100
|
+
|
|
101
|
+
// MutationObserver is async — wait for microtask
|
|
102
|
+
await new Promise( resolve => setTimeout( resolve, 0 ) );
|
|
103
|
+
|
|
104
|
+
expect( regularDiv.parentNode ).toBe( document.body );
|
|
105
|
+
unmount();
|
|
106
|
+
} );
|
|
107
|
+
|
|
108
|
+
test( 'observes and relocates newly added portal nodes', async () => {
|
|
109
|
+
const { container, unmount } = setupHook();
|
|
110
|
+
|
|
111
|
+
const portal = createVisxPortalNode();
|
|
112
|
+
document.body.appendChild( portal );
|
|
113
|
+
|
|
114
|
+
// MutationObserver is async — wait for microtask
|
|
115
|
+
await new Promise( resolve => setTimeout( resolve, 0 ) );
|
|
116
|
+
|
|
117
|
+
expect( portal.parentNode ).toBe( container );
|
|
118
|
+
unmount();
|
|
119
|
+
} );
|
|
120
|
+
|
|
121
|
+
test( 'patched removeChild handles relocated nodes without throwing', () => {
|
|
122
|
+
const { unmount, portal } = setupHook( { withPortal: true } );
|
|
123
|
+
|
|
124
|
+
// Portal is now in container, but visx will call document.body.removeChild(portal)
|
|
125
|
+
expect( () => document.body.removeChild( portal! ) ).not.toThrow();
|
|
126
|
+
expect( portal!.parentNode ).toBeNull();
|
|
127
|
+
unmount();
|
|
128
|
+
} );
|
|
129
|
+
|
|
130
|
+
test( 'patched removeChild delegates to original for non-relocated nodes', () => {
|
|
131
|
+
const { unmount } = setupHook();
|
|
132
|
+
|
|
133
|
+
const regularDiv = document.createElement( 'div' );
|
|
134
|
+
document.body.appendChild( regularDiv );
|
|
135
|
+
expect( () => document.body.removeChild( regularDiv ) ).not.toThrow();
|
|
136
|
+
expect( regularDiv.parentNode ).toBeNull();
|
|
137
|
+
unmount();
|
|
138
|
+
} );
|
|
139
|
+
|
|
140
|
+
test( 'cleanup restores removeChild when it has not been wrapped by others', () => {
|
|
141
|
+
const originalRemoveChild = document.body.removeChild;
|
|
142
|
+
const { unmount } = setupHook();
|
|
143
|
+
|
|
144
|
+
// removeChild should be patched
|
|
145
|
+
expect( document.body.removeChild ).not.toBe( originalRemoveChild );
|
|
146
|
+
|
|
147
|
+
unmount();
|
|
148
|
+
|
|
149
|
+
// Should be restored
|
|
150
|
+
expect( document.body.removeChild ).toBe( originalRemoveChild );
|
|
151
|
+
} );
|
|
152
|
+
|
|
153
|
+
test( 'cleanup leaves removeChild when another wrapper was installed after ours', () => {
|
|
154
|
+
const { unmount } = setupHook();
|
|
155
|
+
|
|
156
|
+
// Simulate another library wrapping removeChild after our patch
|
|
157
|
+
const ourPatch = document.body.removeChild;
|
|
158
|
+
const thirdPartyWrapper = function < T extends Node >( child: T ): T {
|
|
159
|
+
return ourPatch.call( document.body, child );
|
|
160
|
+
};
|
|
161
|
+
document.body.removeChild = thirdPartyWrapper;
|
|
162
|
+
|
|
163
|
+
unmount();
|
|
164
|
+
|
|
165
|
+
// Should NOT restore — third party wrapper is still in place
|
|
166
|
+
expect( document.body.removeChild ).toBe( thirdPartyWrapper );
|
|
167
|
+
} );
|
|
168
|
+
|
|
169
|
+
test( 'cleanup moves relocated nodes back to document.body', () => {
|
|
170
|
+
const { container, unmount, portal } = setupHook( { withPortal: true } );
|
|
171
|
+
|
|
172
|
+
expect( portal!.parentNode ).toBe( container );
|
|
173
|
+
|
|
174
|
+
unmount();
|
|
175
|
+
|
|
176
|
+
// Node should be moved back to body on cleanup
|
|
177
|
+
expect( portal!.parentNode ).toBe( document.body );
|
|
178
|
+
} );
|
|
179
|
+
|
|
180
|
+
test( 'cleanup removes relocated-portal class from nodes', () => {
|
|
181
|
+
const { unmount, portal } = setupHook( { withPortal: true } );
|
|
182
|
+
|
|
183
|
+
expect( portal! ).toHaveClass( 'relocatedPortal' );
|
|
184
|
+
|
|
185
|
+
unmount();
|
|
186
|
+
|
|
187
|
+
expect( portal! ).not.toHaveClass( 'relocatedPortal' );
|
|
188
|
+
} );
|
|
189
|
+
|
|
190
|
+
test( 'ref-counting allows multiple instances to share the patch', () => {
|
|
191
|
+
const container1 = document.createElement( 'div' );
|
|
192
|
+
const container2 = document.createElement( 'div' );
|
|
193
|
+
document.body.appendChild( container1 );
|
|
194
|
+
document.body.appendChild( container2 );
|
|
195
|
+
|
|
196
|
+
const ref1 = { current: container1 };
|
|
197
|
+
const ref2 = { current: container2 };
|
|
198
|
+
|
|
199
|
+
const originalRemoveChild = document.body.removeChild;
|
|
200
|
+
|
|
201
|
+
const { unmount: unmountFirst } = renderHook( () => useTooltipPortalRelocator( ref1 ) );
|
|
202
|
+
const patchedFn = document.body.removeChild;
|
|
203
|
+
const { unmount: unmountSecond } = renderHook( () => useTooltipPortalRelocator( ref2 ) );
|
|
204
|
+
|
|
205
|
+
// Both should share the same patched removeChild
|
|
206
|
+
expect( document.body.removeChild ).toBe( patchedFn );
|
|
207
|
+
|
|
208
|
+
// Unmounting the first should keep the patch (ref count > 0)
|
|
209
|
+
unmountFirst();
|
|
210
|
+
expect( document.body.removeChild ).toBe( patchedFn );
|
|
211
|
+
|
|
212
|
+
// Unmounting the second should restore the original
|
|
213
|
+
unmountSecond();
|
|
214
|
+
expect( document.body.removeChild ).toBe( originalRemoveChild );
|
|
215
|
+
} );
|
|
216
|
+
} );
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import styles from './use-tooltip-portal-relocator.module.scss';
|
|
3
|
+
import type { RefObject } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detects whether a DOM node is a visx chart tooltip portal.
|
|
7
|
+
*
|
|
8
|
+
* visx renders tooltips via `ReactDOM.createPortal` into plain `<div>` elements
|
|
9
|
+
* appended to `document.body`. These portals have no id or className and contain
|
|
10
|
+
* a child element with the class `visx-tooltip`.
|
|
11
|
+
* @param node - The DOM node to check.
|
|
12
|
+
* @return Whether the node is a visx tooltip portal div.
|
|
13
|
+
*/
|
|
14
|
+
function isVisxPortalNode( node: Node ): node is HTMLDivElement {
|
|
15
|
+
return (
|
|
16
|
+
node instanceof HTMLDivElement &&
|
|
17
|
+
node.parentElement === document.body &&
|
|
18
|
+
! node.id &&
|
|
19
|
+
! node.className &&
|
|
20
|
+
node.querySelector( '.visx-tooltip' ) !== null
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Shared state for the document.body.removeChild patch.
|
|
25
|
+
// Reference-counted so multiple hook instances can coexist safely.
|
|
26
|
+
let patchRefCount = 0;
|
|
27
|
+
let origRemoveChild: typeof document.body.removeChild | null = null;
|
|
28
|
+
let patchedRemoveChild: typeof document.body.removeChild | null = null;
|
|
29
|
+
const relocatedNodes = new WeakSet< Node >();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Installs (or increments the ref count of) the shared removeChild patch.
|
|
33
|
+
*/
|
|
34
|
+
function installRemoveChildPatch() {
|
|
35
|
+
if ( patchRefCount++ > 0 ) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
origRemoveChild = document.body.removeChild;
|
|
39
|
+
patchedRemoveChild = function < T extends Node >( this: HTMLElement, child: T ): T {
|
|
40
|
+
if ( relocatedNodes.has( child ) && child.parentNode !== this ) {
|
|
41
|
+
relocatedNodes.delete( child );
|
|
42
|
+
child.parentNode?.removeChild( child );
|
|
43
|
+
return child;
|
|
44
|
+
}
|
|
45
|
+
return origRemoveChild!.call( this, child );
|
|
46
|
+
};
|
|
47
|
+
document.body.removeChild = patchedRemoveChild;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Decrements the ref count and removes the patch when no instances remain.
|
|
52
|
+
* If another library has since wrapped our patch, we leave it in place to
|
|
53
|
+
* avoid breaking their chain — our function becomes a transparent pass-through
|
|
54
|
+
* once all relocated nodes have been cleaned up.
|
|
55
|
+
*/
|
|
56
|
+
function uninstallRemoveChildPatch() {
|
|
57
|
+
if ( --patchRefCount > 0 ) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Only revert if removeChild is still our function. If something else
|
|
61
|
+
// has wrapped it, reverting would break their patch.
|
|
62
|
+
if ( document.body.removeChild === patchedRemoveChild ) {
|
|
63
|
+
document.body.removeChild = origRemoveChild!;
|
|
64
|
+
}
|
|
65
|
+
origRemoveChild = null;
|
|
66
|
+
patchedRemoveChild = null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Relocates visx chart tooltip portals from `document.body` into a target
|
|
71
|
+
* container element. This allows the tooltips to participate in the same CSS
|
|
72
|
+
* stacking context as other elements in the container (e.g. a sticky header),
|
|
73
|
+
* so z-index ordering works correctly between them.
|
|
74
|
+
*
|
|
75
|
+
* The relocated portal divs use `position: fixed` at the viewport origin to
|
|
76
|
+
* preserve the tooltip coordinate system (visx calculates positions relative
|
|
77
|
+
* to the viewport).
|
|
78
|
+
*
|
|
79
|
+
* Because the visx Portal class calls `document.body.removeChild(node)` during
|
|
80
|
+
* unmount, we patch `document.body.removeChild` to gracefully handle nodes that
|
|
81
|
+
* were moved out of body. Without this, React throws a "not a child of this
|
|
82
|
+
* node" error when tooltips unmount.
|
|
83
|
+
*
|
|
84
|
+
* **Important:** The container and its ancestors must not have CSS `transform`,
|
|
85
|
+
* `perspective`, or `filter` properties set, as these create a new containing
|
|
86
|
+
* block for `position: fixed` children, breaking viewport-relative positioning.
|
|
87
|
+
*
|
|
88
|
+
* @param containerRef - Ref to the element that portals should be relocated into.
|
|
89
|
+
* The element referenced here, or one of its ancestors,
|
|
90
|
+
* should establish the desired stacking context (for example
|
|
91
|
+
* by using position and z-index).
|
|
92
|
+
*/
|
|
93
|
+
export function useTooltipPortalRelocator(
|
|
94
|
+
containerRef: RefObject< HTMLElement | null > | undefined
|
|
95
|
+
) {
|
|
96
|
+
useEffect( () => {
|
|
97
|
+
const container = containerRef?.current;
|
|
98
|
+
if ( ! container ) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Track nodes relocated by this instance so we can move them back on cleanup.
|
|
103
|
+
const instanceNodes = new Set< Node >();
|
|
104
|
+
|
|
105
|
+
const relocateNode = ( node: Node ) => {
|
|
106
|
+
if ( ! isVisxPortalNode( node ) ) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Position the portal at the viewport origin so visx's
|
|
111
|
+
// absolute-positioned tooltip coordinates remain correct.
|
|
112
|
+
// Zero-size with overflow: visible so it doesn't affect layout
|
|
113
|
+
// but tooltip content still renders. pointerEvents: none on the
|
|
114
|
+
// wrapper is intentional — tooltip inner elements manage their own.
|
|
115
|
+
node.classList.add( styles.relocatedPortal );
|
|
116
|
+
|
|
117
|
+
// Remember the focused element before moving the node — relocating
|
|
118
|
+
// a DOM subtree causes the browser to blur any focused descendants.
|
|
119
|
+
const { activeElement } = node.ownerDocument;
|
|
120
|
+
const focusedElement =
|
|
121
|
+
activeElement instanceof HTMLElement && node.contains( activeElement )
|
|
122
|
+
? activeElement
|
|
123
|
+
: null;
|
|
124
|
+
|
|
125
|
+
// Insert at the start of the container (before header and content).
|
|
126
|
+
container.insertBefore( node, container.firstChild );
|
|
127
|
+
relocatedNodes.add( node );
|
|
128
|
+
instanceNodes.add( node );
|
|
129
|
+
|
|
130
|
+
// Restore focus that was lost due to the DOM move.
|
|
131
|
+
if ( focusedElement ) {
|
|
132
|
+
focusedElement.focus();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Patch document.body.removeChild so visx Portal unmount doesn't throw
|
|
137
|
+
// when it tries to remove a node we already moved out of body.
|
|
138
|
+
installRemoveChildPatch();
|
|
139
|
+
|
|
140
|
+
// Relocate any portals that already exist.
|
|
141
|
+
for ( const child of Array.from( document.body.children ) ) {
|
|
142
|
+
relocateNode( child );
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Watch for new portals being appended to body.
|
|
146
|
+
const observer = new MutationObserver( mutations => {
|
|
147
|
+
for ( const mutation of mutations ) {
|
|
148
|
+
for ( const node of mutation.addedNodes ) {
|
|
149
|
+
relocateNode( node );
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} );
|
|
153
|
+
|
|
154
|
+
observer.observe( document.body, { childList: true } );
|
|
155
|
+
|
|
156
|
+
return () => {
|
|
157
|
+
// Disconnect first to avoid the observer re-relocating nodes
|
|
158
|
+
// as we move them back to body.
|
|
159
|
+
observer.disconnect();
|
|
160
|
+
|
|
161
|
+
// Move relocated nodes back to body so visx can clean them up
|
|
162
|
+
// normally with the original removeChild.
|
|
163
|
+
for ( const node of instanceNodes ) {
|
|
164
|
+
if ( node instanceof HTMLElement ) {
|
|
165
|
+
node.classList.remove( styles.relocatedPortal );
|
|
166
|
+
}
|
|
167
|
+
if ( node.parentNode === container ) {
|
|
168
|
+
document.body.appendChild( node );
|
|
169
|
+
}
|
|
170
|
+
relocatedNodes.delete( node );
|
|
171
|
+
}
|
|
172
|
+
instanceNodes.clear();
|
|
173
|
+
|
|
174
|
+
uninstallRemoveChildPatch();
|
|
175
|
+
};
|
|
176
|
+
}, [ containerRef ] );
|
|
177
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
useLayoutEffect,
|
|
9
9
|
useRef,
|
|
10
10
|
} from 'react';
|
|
11
|
+
import { useTooltipPortalRelocator } from '../../hooks/use-tooltip-portal-relocator';
|
|
11
12
|
import {
|
|
12
13
|
getItemShapeStyles,
|
|
13
14
|
getSeriesLineStyles,
|
|
@@ -26,9 +27,22 @@ export const GlobalChartsContext = createContext< GlobalChartsContextValue | nul
|
|
|
26
27
|
export interface GlobalChartsProviderProps {
|
|
27
28
|
children: ReactNode;
|
|
28
29
|
theme?: Partial< ChartTheme >;
|
|
30
|
+
/**
|
|
31
|
+
* Optional ref to an element that chart tooltip portals should be relocated into.
|
|
32
|
+
* When provided, visx tooltip portals (normally appended to document.body) will be
|
|
33
|
+
* moved into this container so they participate in the same effective CSS stacking context.
|
|
34
|
+
* The element referenced here, or one of its ancestors, should establish the desired
|
|
35
|
+
* stacking context (for example by using `position` and `z-index`) so that tooltips
|
|
36
|
+
* appear above the relevant chart content.
|
|
37
|
+
*/
|
|
38
|
+
portalContainer?: React.RefObject< HTMLElement | null >;
|
|
29
39
|
}
|
|
30
40
|
|
|
31
|
-
export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
|
|
41
|
+
export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
|
|
42
|
+
children,
|
|
43
|
+
theme,
|
|
44
|
+
portalContainer,
|
|
45
|
+
} ) => {
|
|
32
46
|
const [ charts, setCharts ] = useState< Map< string, ChartRegistration > >( () => new Map() );
|
|
33
47
|
// Track hidden series per chart: chartId -> Set<seriesLabel>
|
|
34
48
|
const [ hiddenSeries, setHiddenSeries ] = useState< Map< string, Set< string > > >(
|
|
@@ -38,6 +52,9 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { childre
|
|
|
38
52
|
// Ref to the wrapper element for resolving scoped CSS variables
|
|
39
53
|
const wrapperRef = useRef< HTMLDivElement >( null );
|
|
40
54
|
|
|
55
|
+
// Relocate tooltip portals into the wrapper (or a consumer-provided container) for z-index control.
|
|
56
|
+
useTooltipPortalRelocator( portalContainer ?? wrapperRef );
|
|
57
|
+
|
|
41
58
|
const providerTheme: CompleteChartTheme = useMemo( () => {
|
|
42
59
|
return theme ? mergeThemes( defaultTheme, theme ) : defaultTheme;
|
|
43
60
|
}, [ theme ] );
|
package/tsup.config.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import babel from 'esbuild-plugin-babel';
|
|
1
2
|
import { sassPlugin, postcssModules } from 'esbuild-sass-plugin';
|
|
2
3
|
import { defineConfig } from 'tsup';
|
|
3
4
|
import pkg from './package.json';
|
|
@@ -23,6 +24,16 @@ export default defineConfig( {
|
|
|
23
24
|
'.png': 'file',
|
|
24
25
|
},
|
|
25
26
|
esbuildPlugins: [
|
|
27
|
+
babel( {
|
|
28
|
+
filter: /\.tsx$/,
|
|
29
|
+
config: {
|
|
30
|
+
presets: [
|
|
31
|
+
'@babel/preset-typescript',
|
|
32
|
+
[ '@babel/preset-react', { runtime: 'automatic' } ],
|
|
33
|
+
],
|
|
34
|
+
plugins: [ [ 'react-remove-properties', { properties: [ 'data-testid' ] } ] ],
|
|
35
|
+
},
|
|
36
|
+
} ),
|
|
26
37
|
sassPlugin( {
|
|
27
38
|
filter: /\.module\.(css|scss)$/,
|
|
28
39
|
embedded: true,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/charts/bar-chart/bar-chart.tsx","../src/charts/bar-chart/bar-chart.module.scss","../src/charts/bar-chart/private/use-bar-chart-options.ts","../src/charts/bar-chart/private/truncated-tick-component.tsx"],"sourcesContent":["import { formatNumber } from '@automattic/number-formatters';\nimport { PatternLines, PatternCircles, PatternWaves, PatternHexagons } from '@visx/pattern';\nimport { Axis, BarSeries, BarGroup, Grid, XYChart } from '@visx/xychart';\nimport { __ } from '@wordpress/i18n';\nimport { Stack } from '@wordpress/ui';\nimport clsx from 'clsx';\nimport { useCallback, useContext, useState, useRef, useMemo } from 'react';\nimport { Legend, useChartLegendItems } from '../../components/legend';\nimport { AccessibleTooltip, useKeyboardNavigation } from '../../components/tooltip';\nimport {\n\tuseXYChartTheme,\n\tuseChartDataTransform,\n\tuseZeroValueDisplay,\n\tuseChartMargin,\n\tuseElementHeight,\n\tuseHasLegendChild,\n\tusePrefersReducedMotion,\n} from '../../hooks';\nimport {\n\tGlobalChartsProvider,\n\tuseChartId,\n\tuseChartRegistration,\n\tuseGlobalChartsContext,\n\tuseGlobalChartsTheme,\n\tGlobalChartsContext,\n} from '../../providers';\nimport { attachSubComponents } from '../../utils';\nimport { SingleChartContext } from '../private/single-chart-context';\nimport { withResponsive } from '../private/with-responsive';\nimport styles from './bar-chart.module.scss';\nimport { useBarChartOptions } from './private';\nimport type { BaseChartProps, DataPointDate, SeriesData, Optional } from '../../types';\nimport type { ResponsiveConfig } from '../private/with-responsive';\nimport type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';\nimport type { GapSize } from '@wordpress/theme';\nimport type { FC, ReactNode, ComponentType } from 'react';\n\nexport interface BarChartProps extends BaseChartProps< SeriesData[] > {\n\trenderTooltip?: ( params: RenderTooltipParams< DataPointDate > ) => ReactNode;\n\torientation?: 'horizontal' | 'vertical';\n\twithPatterns?: boolean;\n\tshowZeroValues?: boolean;\n\tlegendInteractive?: boolean;\n\tchildren?: ReactNode;\n\t/**\n\t * Gap between chart elements (SVG, legend, children).\n\t * Uses WordPress design system tokens.\n\t * @default 'md'\n\t */\n\tgap?: GapSize;\n}\n\n// Base props type with optional responsive properties\ntype BarChartBaseProps = Optional< BarChartProps, 'width' | 'height' | 'size' >;\n\n// Composition API types\ninterface BarChartSubComponents {\n\tLegend: ComponentType< React.ComponentProps< typeof Legend > >;\n}\n\ntype BarChartComponent = FC< BarChartBaseProps > & BarChartSubComponents;\ntype BarChartResponsiveComponent = FC< BarChartBaseProps & ResponsiveConfig > &\n\tBarChartSubComponents;\n\n// Validation function similar to LineChart\nconst validateData = ( data: SeriesData[] ) => {\n\tif ( ! data?.length ) return 'No data available';\n\n\tconst hasInvalidData = data.some( series =>\n\t\tseries.data.some(\n\t\t\tpoint =>\n\t\t\t\tisNaN( point.value as number ) ||\n\t\t\t\tpoint.value === null ||\n\t\t\t\tpoint.value === undefined ||\n\t\t\t\t( ! point.label &&\n\t\t\t\t\t( ! ( 'date' in point && point.date ) || isNaN( point.date.getTime() ) ) )\n\t\t)\n\t);\n\n\tif ( hasInvalidData ) return 'Invalid data';\n\treturn null;\n};\n\nconst getPatternId = ( chartId: string, index: number ) => `bar-pattern-${ chartId }-${ index }`;\n\nconst BarChartInternal: FC< BarChartProps > = ( {\n\tdata,\n\tchartId: providedChartId,\n\twidth,\n\theight,\n\tclassName,\n\tmargin,\n\twithTooltips = false,\n\tshowLegend = false,\n\tlegendOrientation = 'horizontal',\n\tlegendPosition = 'bottom',\n\tlegendAlignment = 'center',\n\tlegendMaxWidth,\n\tlegendTextOverflow = 'wrap',\n\tlegendItemClassName,\n\tlegendShape = 'rect',\n\tgridVisibility: gridVisibilityProp,\n\trenderTooltip,\n\toptions = {},\n\torientation = 'vertical',\n\twithPatterns = false,\n\tshowZeroValues = false,\n\tlegendInteractive = false,\n\tanimation,\n\tchildren,\n\tgap = 'md',\n} ) => {\n\tconst horizontal = orientation === 'horizontal';\n\tconst chartId = useChartId( providedChartId );\n\tconst theme = useXYChartTheme( data );\n\n\tconst dataSorted = useChartDataTransform( data );\n\n\t// Transform data to add a small value for zero bars to make them visible\n\tconst dataWithVisibleZeros = useZeroValueDisplay( dataSorted, {\n\t\tenabled: showZeroValues,\n\t} );\n\n\t// Create legend items using the reusable hook\n\tconst legendItems = useChartLegendItems( dataSorted );\n\tconst chartOptions = useBarChartOptions( dataWithVisibleZeros, horizontal, options );\n\tconst defaultMargin = useChartMargin( height, chartOptions, dataSorted, theme, horizontal );\n\tconst [ svgWrapperRef, svgWrapperHeight ] = useElementHeight< HTMLDivElement >();\n\tconst chartRef = useRef< HTMLDivElement >( null );\n\n\t// Check if children contain a Legend component (composition pattern)\n\tconst hasLegendChild = useHasLegendChild( children );\n\n\t// Use the measured SVG wrapper height, falling back to the passed height if provided.\n\t// When there's a legend (via prop or composition), we must wait for measurement because\n\t// the legend takes space and the svg-wrapper height will be less than the total height.\n\tconst chartHeight = svgWrapperHeight > 0 ? svgWrapperHeight : height;\n\tconst hasLegend = showLegend || hasLegendChild;\n\tconst isWaitingForMeasurement = hasLegend ? svgWrapperHeight === 0 : ! chartHeight;\n\tconst [ selectedIndex, setSelectedIndex ] = useState< number | undefined >( undefined );\n\tconst [ isNavigating, setIsNavigating ] = useState( false );\n\n\tconst totalPoints =\n\t\tMath.max( 0, ...data.map( series => series.data?.length || 0 ) ) * data.length;\n\n\t// Use the keyboard navigation hook\n\tconst { tooltipRef, onChartFocus, onChartBlur, onChartKeyDown } = useKeyboardNavigation( {\n\t\tselectedIndex,\n\t\tsetSelectedIndex,\n\t\tisNavigating,\n\t\tsetIsNavigating,\n\t\tchartRef,\n\t\ttotalPoints,\n\t} );\n\n\tconst { getElementStyles, isSeriesVisible } = useGlobalChartsContext();\n\tconst providerTheme = useGlobalChartsTheme();\n\n\t// Add visibility information to series when using interactive legends\n\tconst seriesWithVisibility = useMemo( () => {\n\t\tif ( ! chartId || ! legendInteractive ) {\n\t\t\treturn dataWithVisibleZeros.map( ( series, index ) => ( {\n\t\t\t\tseries,\n\t\t\t\tindex,\n\t\t\t\tisVisible: true,\n\t\t\t} ) );\n\t\t}\n\t\treturn dataWithVisibleZeros.map( ( series, index ) => ( {\n\t\t\tseries,\n\t\t\tindex,\n\t\t\tisVisible: isSeriesVisible( chartId, series.label ),\n\t\t} ) );\n\t}, [ dataWithVisibleZeros, chartId, isSeriesVisible, legendInteractive ] );\n\n\t// Check if all series are hidden\n\tconst allSeriesHidden = useMemo( () => {\n\t\treturn seriesWithVisibility.every( ( { isVisible } ) => ! isVisible );\n\t}, [ seriesWithVisibility ] );\n\n\tconst getBarBackground = useCallback(\n\t\t( index: number ) => () =>\n\t\t\twithPatterns\n\t\t\t\t? `url(#${ getPatternId( chartId, index ) })`\n\t\t\t\t: getElementStyles( { data: dataSorted[ index ], index } ).color,\n\t\t[ withPatterns, getElementStyles, dataSorted, chartId ]\n\t);\n\n\tconst renderDefaultTooltip = useCallback(\n\t\t( { tooltipData }: RenderTooltipParams< DataPointDate > ) => {\n\t\t\tconst nearestDatum = tooltipData?.nearestDatum?.datum;\n\t\t\tif ( ! nearestDatum ) return null;\n\n\t\t\treturn (\n\t\t\t\t<div className={ styles[ 'bar-chart__tooltip' ] }>\n\t\t\t\t\t<div className={ styles[ 'bar-chart__tooltip-header' ] }>\n\t\t\t\t\t\t{ tooltipData?.nearestDatum?.key }\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className={ styles[ 'bar-chart__tooltip-row' ] }>\n\t\t\t\t\t\t<span className={ styles[ 'bar-chart__tooltip-label' ] }>\n\t\t\t\t\t\t\t{ chartOptions.tooltip.labelFormatter(\n\t\t\t\t\t\t\t\tnearestDatum.label || ( nearestDatum.date ? nearestDatum.date.getTime() : 0 ),\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t[]\n\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t:\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className={ styles[ 'bar-chart__tooltip-value' ] }>\n\t\t\t\t\t\t\t{ formatNumber( nearestDatum.value as number ) }\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t);\n\t\t},\n\t\t[ chartOptions.tooltip ]\n\t);\n\n\tconst renderPattern = useCallback(\n\t\t( index: number, color: string ) => {\n\t\t\tconst patternType = index % 4;\n\t\t\tconst id = getPatternId( chartId, index );\n\t\t\tconst commonProps = {\n\t\t\t\tid,\n\t\t\t\tstroke: 'white',\n\t\t\t\tstrokeWidth: 1,\n\t\t\t\tbackground: color,\n\t\t\t};\n\n\t\t\tswitch ( patternType ) {\n\t\t\t\tcase 0:\n\t\t\t\tdefault:\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<PatternLines\n\t\t\t\t\t\t\tkey={ id }\n\t\t\t\t\t\t\t{ ...commonProps }\n\t\t\t\t\t\t\twidth={ 5 }\n\t\t\t\t\t\t\theight={ 5 }\n\t\t\t\t\t\t\torientation={ [ 'diagonal' ] }\n\t\t\t\t\t\t/>\n\t\t\t\t\t);\n\t\t\t\tcase 1:\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<PatternCircles key={ id } { ...commonProps } width={ 6 } height={ 6 } fill=\"white\" />\n\t\t\t\t\t);\n\t\t\t\tcase 2:\n\t\t\t\t\treturn <PatternWaves key={ id } { ...commonProps } width={ 4 } height={ 4 } />;\n\t\t\t\tcase 3:\n\t\t\t\t\treturn <PatternHexagons key={ id } { ...commonProps } size={ 8 } height={ 3 } />;\n\t\t\t}\n\t\t},\n\t\t[ chartId ]\n\t);\n\n\tconst createPatternBorderStyle = useCallback(\n\t\t( index: number, color: string ) => {\n\t\t\tconst patternId = getPatternId( chartId, index );\n\t\t\treturn `\n\t\t\t.visx-bar[fill=\"url(#${ patternId })\"] {\n\t\t\t\tstroke: ${ color };\n\t\t\t\tstroke-width: 1;\n\t\t\t\t}\n\t\t\t`;\n\t\t},\n\t\t[ chartId ]\n\t);\n\n\tconst createKeyboardHighlightStyle = useCallback( () => {\n\t\tif ( selectedIndex === undefined ) return '';\n\n\t\t// Calculate which bar should be highlighted based on selectedIndex\n\t\t// Pattern: [series1[0], series2[0], series3[0], series1[1], series2[1], series3[1], ...]\n\t\tconst maxDataPoints = Math.max( ...data.map( s => s.data.length ) );\n\t\tconst dataPointIndex = Math.floor( selectedIndex / data.length );\n\t\tconst seriesIndex = selectedIndex % data.length;\n\n\t\t// Only highlight if we're within valid bounds\n\t\tif ( dataPointIndex >= maxDataPoints || seriesIndex >= data.length ) {\n\t\t\treturn '';\n\t\t}\n\n\t\tconst seriesData = data[ seriesIndex ];\n\t\tif ( dataPointIndex >= seriesData.data.length ) {\n\t\t\treturn '';\n\t\t}\n\n\t\t// Based on the DOM structure analysis:\n\t\t// - All bars are in a single .visx-bar-group\n\t\t// - Bars are ordered as: [series1[0], series1[1], series2[0], series2[1], ...]\n\t\t// - So we need to calculate the actual bar index in the DOM\n\t\tconst actualBarIndex = seriesIndex * maxDataPoints + dataPointIndex;\n\n\t\t// Use a CSS class selector instead of ID since useId() generates invalid CSS ID characters\n\t\tconst generatedStyles = `\n\t\t\t.bar-chart[data-chart-id=\"bar-chart-${ chartId }\"] .visx-bar-group .visx-bar:nth-child(${\n\t\t\t\tactualBarIndex + 1\n\t\t\t}) {\n\t\t\t\tstroke: #005fcc;\n\t\t\t\tstroke-width: 2px;\n\t\t\t}\n\t\t`;\n\n\t\treturn generatedStyles;\n\t}, [ selectedIndex, data, chartId ] );\n\n\t// Validate data first\n\tconst error = validateData( dataSorted );\n\tconst isDataValid = ! error;\n\n\t// Memoize metadata to prevent unnecessary re-registration\n\tconst chartMetadata = useMemo(\n\t\t() => ( {\n\t\t\torientation,\n\t\t\twithPatterns,\n\t\t} ),\n\t\t[ orientation, withPatterns ]\n\t);\n\n\t// Register chart with context only if data is valid\n\tuseChartRegistration( {\n\t\tchartId,\n\t\tlegendItems,\n\t\tchartType: 'bar',\n\t\tisDataValid,\n\t\tmetadata: chartMetadata,\n\t} );\n\n\tconst prefersReducedMotion = usePrefersReducedMotion();\n\n\tif ( error ) {\n\t\treturn <div className={ clsx( 'bar-chart', styles[ 'bar-chart' ] ) }>{ error }</div>;\n\t}\n\n\tconst gridVisibility = gridVisibilityProp ?? chartOptions.gridVisibility;\n\tconst highlightedBarStyle = createKeyboardHighlightStyle();\n\n\tconst legendElement = showLegend && (\n\t\t<Legend\n\t\t\torientation={ legendOrientation }\n\t\t\tposition={ legendPosition }\n\t\t\talignment={ legendAlignment }\n\t\t\tmaxWidth={ legendMaxWidth }\n\t\t\ttextOverflow={ legendTextOverflow }\n\t\t\tlegendItemClassName={ legendItemClassName }\n\t\t\tclassName={ styles[ 'bar-chart__legend' ] }\n\t\t\tshape={ legendShape }\n\t\t\tchartId={ chartId }\n\t\t\tinteractive={ legendInteractive }\n\t\t/>\n\t);\n\n\treturn (\n\t\t<SingleChartContext.Provider\n\t\t\tvalue={ {\n\t\t\t\tchartId,\n\t\t\t\tchartWidth: width,\n\t\t\t\tchartHeight,\n\t\t\t} }\n\t\t>\n\t\t\t<Stack\n\t\t\t\tdirection=\"column\"\n\t\t\t\tgap={ gap }\n\t\t\t\tclassName={ clsx(\n\t\t\t\t\t'bar-chart',\n\t\t\t\t\tstyles[ 'bar-chart' ],\n\t\t\t\t\t{\n\t\t\t\t\t\t[ styles[ `bar-chart--animated${ horizontal ? '-horizontal' : '' }` ] ]:\n\t\t\t\t\t\t\tanimation && ! prefersReducedMotion,\n\t\t\t\t\t},\n\t\t\t\t\tclassName\n\t\t\t\t) }\n\t\t\t\tdata-testid=\"bar-chart\"\n\t\t\t\tstyle={ {\n\t\t\t\t\twidth,\n\t\t\t\t\theight,\n\t\t\t\t\tvisibility: isWaitingForMeasurement ? 'hidden' : 'visible',\n\t\t\t\t} }\n\t\t\t\tdata-chart-id={ `bar-chart-${ chartId }` }\n\t\t\t>\n\t\t\t\t{ legendPosition === 'top' && legendElement }\n\n\t\t\t\t<div\n\t\t\t\t\tclassName={ styles[ 'bar-chart__svg-wrapper' ] }\n\t\t\t\t\tref={ svgWrapperRef }\n\t\t\t\t\trole=\"grid\"\n\t\t\t\t\taria-label={ __( 'Bar chart', 'jetpack-charts' ) }\n\t\t\t\t\ttabIndex={ 0 }\n\t\t\t\t\tonKeyDown={ onChartKeyDown }\n\t\t\t\t\tonFocus={ onChartFocus }\n\t\t\t\t\tonBlur={ onChartBlur }\n\t\t\t\t>\n\t\t\t\t\t{ ! isWaitingForMeasurement && (\n\t\t\t\t\t\t<div ref={ chartRef }>\n\t\t\t\t\t\t\t<XYChart\n\t\t\t\t\t\t\t\ttheme={ theme }\n\t\t\t\t\t\t\t\twidth={ width }\n\t\t\t\t\t\t\t\theight={ chartHeight }\n\t\t\t\t\t\t\t\tmargin={ {\n\t\t\t\t\t\t\t\t\t...defaultMargin,\n\t\t\t\t\t\t\t\t\t...margin,\n\t\t\t\t\t\t\t\t} }\n\t\t\t\t\t\t\t\txScale={ chartOptions.xScale }\n\t\t\t\t\t\t\t\tyScale={ chartOptions.yScale }\n\t\t\t\t\t\t\t\thorizontal={ horizontal }\n\t\t\t\t\t\t\t\tpointerEventsDataKey=\"nearest\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<Grid\n\t\t\t\t\t\t\t\t\tcolumns={ gridVisibility.includes( 'y' ) }\n\t\t\t\t\t\t\t\t\trows={ gridVisibility.includes( 'x' ) }\n\t\t\t\t\t\t\t\t\tnumTicks={ 4 }\n\t\t\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t\t\t{ withPatterns && (\n\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t<defs data-testid=\"bar-chart-patterns\">\n\t\t\t\t\t\t\t\t\t\t\t{ dataSorted.map( ( seriesData, index ) =>\n\t\t\t\t\t\t\t\t\t\t\t\trenderPattern(\n\t\t\t\t\t\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t\t\t\t\t\t\tgetElementStyles( { data: seriesData, index } ).color\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t\t\t\t</defs>\n\t\t\t\t\t\t\t\t\t\t<style>\n\t\t\t\t\t\t\t\t\t\t\t{ dataSorted.map( ( seriesData, index ) =>\n\t\t\t\t\t\t\t\t\t\t\t\tcreatePatternBorderStyle(\n\t\t\t\t\t\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t\t\t\t\t\t\tgetElementStyles( { data: seriesData, index } ).color\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t\t\t\t</style>\n\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t) }\n\n\t\t\t\t\t\t\t\t{ highlightedBarStyle && <style>{ highlightedBarStyle }</style> }\n\n\t\t\t\t\t\t\t\t{ allSeriesHidden ? (\n\t\t\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\t\t\tx={ width / 2 }\n\t\t\t\t\t\t\t\t\t\ty={ chartHeight / 2 }\n\t\t\t\t\t\t\t\t\t\ttextAnchor=\"middle\"\n\t\t\t\t\t\t\t\t\t\tfill={ providerTheme.gridStyles?.stroke || '#ccc' }\n\t\t\t\t\t\t\t\t\t\tfontSize=\"14\"\n\t\t\t\t\t\t\t\t\t\tfontFamily=\"-apple-system,BlinkMacSystemFont,Roboto,Helvetica Neue,sans-serif\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{ __(\n\t\t\t\t\t\t\t\t\t\t\t'All series are hidden. Click legend items to show data.',\n\t\t\t\t\t\t\t\t\t\t\t'jetpack-charts'\n\t\t\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t\t\t</text>\n\t\t\t\t\t\t\t\t) : null }\n\n\t\t\t\t\t\t\t\t<BarGroup padding={ chartOptions.barGroup.padding }>\n\t\t\t\t\t\t\t\t\t{ seriesWithVisibility.map( ( { series: seriesData, index, isVisible } ) => {\n\t\t\t\t\t\t\t\t\t\t// Skip rendering invisible series\n\t\t\t\t\t\t\t\t\t\tif ( ! isVisible ) {\n\t\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t<BarSeries\n\t\t\t\t\t\t\t\t\t\t\t\tkey={ seriesData?.label }\n\t\t\t\t\t\t\t\t\t\t\t\tdataKey={ seriesData?.label }\n\t\t\t\t\t\t\t\t\t\t\t\tdata={ seriesData.data as DataPointDate[] }\n\t\t\t\t\t\t\t\t\t\t\t\tyAccessor={ chartOptions.accessors.yAccessor }\n\t\t\t\t\t\t\t\t\t\t\t\txAccessor={ chartOptions.accessors.xAccessor }\n\t\t\t\t\t\t\t\t\t\t\t\tcolorAccessor={ getBarBackground( index ) }\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t} ) }\n\t\t\t\t\t\t\t\t</BarGroup>\n\n\t\t\t\t\t\t\t\t<Axis { ...chartOptions.axis.x } />\n\t\t\t\t\t\t\t\t<Axis { ...chartOptions.axis.y } />\n\n\t\t\t\t\t\t\t\t{ withTooltips && (\n\t\t\t\t\t\t\t\t\t<AccessibleTooltip\n\t\t\t\t\t\t\t\t\t\tdetectBounds\n\t\t\t\t\t\t\t\t\t\tsnapTooltipToDatumX\n\t\t\t\t\t\t\t\t\t\tsnapTooltipToDatumY\n\t\t\t\t\t\t\t\t\t\trenderTooltip={ renderTooltip || renderDefaultTooltip }\n\t\t\t\t\t\t\t\t\t\tselectedIndex={ selectedIndex }\n\t\t\t\t\t\t\t\t\t\ttooltipRef={ tooltipRef }\n\t\t\t\t\t\t\t\t\t\tkeyboardFocusedClassName={ styles[ 'bar-chart__tooltip--keyboard-focused' ] }\n\t\t\t\t\t\t\t\t\t\tseries={ data }\n\t\t\t\t\t\t\t\t\t\tmode=\"individual\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t</XYChart>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) }\n\t\t\t\t</div>\n\n\t\t\t\t{ legendPosition === 'bottom' && legendElement }\n\n\t\t\t\t{ children }\n\t\t\t</Stack>\n\t\t</SingleChartContext.Provider>\n\t);\n};\n\nconst BarChartWithProvider: FC< BarChartProps > = props => {\n\tconst existingContext = useContext( GlobalChartsContext );\n\n\t// If we're already in a GlobalChartsProvider context, don't create a new one\n\tif ( existingContext ) {\n\t\treturn <BarChartInternal { ...props } />;\n\t}\n\n\t// Otherwise, create our own GlobalChartsProvider\n\treturn (\n\t\t<GlobalChartsProvider>\n\t\t\t<BarChartInternal { ...props } />\n\t\t</GlobalChartsProvider>\n\t);\n};\n\nBarChartWithProvider.displayName = 'BarChart';\n\n// Create BarChart with composition API\nconst BarChart = attachSubComponents( BarChartWithProvider, {\n\tLegend: Legend,\n} ) as BarChartComponent;\n\n// Create responsive BarChart with composition API\nconst BarChartResponsive = attachSubComponents(\n\twithResponsive< BarChartProps >( BarChartWithProvider ),\n\t{\n\t\tLegend: Legend,\n\t}\n) as BarChartResponsiveComponent;\n\nexport { BarChartResponsive as default, BarChart as BarChartUnresponsive };\n","import 'css-chunk:src/charts/bar-chart/bar-chart.module.scss';export default {\n \"bar-chart__svg-wrapper\": \"a8ccharts-9CsqC0\",\n \"bar-chart\": \"a8ccharts-3gflnB\",\n \"bar-chart--animated\": \"a8ccharts-98W-yu\",\n \"rise\": \"a8ccharts-z6AsiQ\",\n \"bar-chart--animated-horizontal\": \"a8ccharts-HFA3FF\",\n \"stretch\": \"a8ccharts-DQp37O\"\n};","import { formatNumberCompact } from '@automattic/number-formatters';\nimport { useMemo } from 'react';\nimport { TruncatedXTickComponent, TruncatedYTickComponent } from './truncated-tick-component';\nimport type { EnhancedDataPoint } from '../../../hooks/use-zero-value-display';\nimport type { DataPointDate, BaseChartProps, SeriesData } from '../../../types';\nimport type { TickFormatter } from '@visx/axis';\n\nconst formatDateTick = ( timestamp: number ) => {\n\tconst date = new Date( timestamp );\n\treturn date.toLocaleDateString( undefined, {\n\t\tmonth: 'short',\n\t\tday: 'numeric',\n\t} );\n};\n\n/**\n * Get the group padding of a scale.\n *\n * @param scale - The scale to get the group padding of.\n * @return The group padding of the scale.\n */\nconst getGroupPadding = ( scale: Record< string, unknown > ): number => {\n\treturn typeof scale.paddingInner === 'number' ? ( scale.paddingInner as number ) : 0;\n};\n\n/**\n * Returns the merged options for the bar chart, including axis and scale configuration based on the orientation.\n *\n * @param data - The data to be displayed in the chart.\n * @param horizontal - Whether the chart is horizontal or vertical.\n * @param options - The options for the chart.\n * @return The merged options for the chart.\n */\nexport function useBarChartOptions(\n\tdata: SeriesData[],\n\thorizontal: boolean,\n\toptions: BaseChartProps[ 'options' ] = {}\n) {\n\tconst defaultOptions = useMemo( () => {\n\t\tconst bandScale = {\n\t\t\ttype: 'band' as const,\n\t\t\tpadding: 0.2,\n\t\t\tpaddingInner: 0.1,\n\t\t};\n\t\tconst linearScale = {\n\t\t\ttype: 'linear' as const,\n\t\t\tnice: true,\n\t\t\tzero: false,\n\t\t};\n\n\t\tconst labelFormatter = data?.[ 0 ]?.data?.[ 0 ]?.label\n\t\t\t? ( label: string ) => label\n\t\t\t: formatDateTick;\n\t\tconst valueFormatter = formatNumberCompact as TickFormatter< unknown >;\n\n\t\tconst labelAccessor = ( d: DataPointDate ) => d?.label || d?.date;\n\t\tconst valueAccessor = ( d: DataPointDate | EnhancedDataPoint ) => {\n\t\t\t// Use visualValue for bar rendering if available (for zero values), otherwise use value\n\t\t\tconst enhancedPoint = d as EnhancedDataPoint;\n\t\t\treturn enhancedPoint?.visualValue !== undefined ? enhancedPoint.visualValue : d?.value;\n\t\t};\n\n\t\treturn {\n\t\t\tvertical: {\n\t\t\t\txTickFormat: labelFormatter,\n\t\t\t\tyTickFormat: valueFormatter,\n\t\t\t\ttooltipLabelFormatter: labelFormatter,\n\t\t\t\txAccessor: labelAccessor,\n\t\t\t\tyAccessor: valueAccessor,\n\t\t\t\tgridVisibility: 'x',\n\t\t\t\txScale: bandScale,\n\t\t\t\tyScale: linearScale,\n\t\t\t},\n\t\t\thorizontal: {\n\t\t\t\txTickFormat: valueFormatter,\n\t\t\t\tyTickFormat: labelFormatter,\n\t\t\t\ttooltipLabelFormatter: labelFormatter,\n\t\t\t\txAccessor: valueAccessor,\n\t\t\t\tyAccessor: labelAccessor,\n\t\t\t\tgridVisibility: 'y',\n\t\t\t\txScale: linearScale,\n\t\t\t\tyScale: bandScale,\n\t\t\t},\n\t\t};\n\t}, [ data ] );\n\n\treturn useMemo( () => {\n\t\tconst orientationKey = horizontal ? 'horizontal' : 'vertical';\n\t\tconst {\n\t\t\txTickFormat,\n\t\t\tyTickFormat,\n\t\t\ttooltipLabelFormatter: defaultTooltipLabelFormatter,\n\t\t\txAccessor,\n\t\t\tyAccessor,\n\t\t\tgridVisibility,\n\t\t\txScale: baseXScale,\n\t\t\tyScale: baseYScale,\n\t\t} = defaultOptions[ orientationKey ];\n\n\t\tconst xScale = { ...baseXScale, ...( options.xScale || {} ) };\n\t\tconst yScale = { ...baseYScale, ...( options.yScale || {} ) };\n\t\tconst providedToolTipLabelFormatter = horizontal\n\t\t\t? options.axis?.y?.tickFormat\n\t\t\t: options.axis?.x?.tickFormat;\n\n\t\tconst { labelOverflow: xLabelOverflow, ...xAxisOptions } = options.axis?.x || {};\n\t\tconst { labelOverflow: yLabelOverflow, ...yAxisOptions } = options.axis?.y || {};\n\n\t\treturn {\n\t\t\tgridVisibility,\n\t\t\txScale,\n\t\t\tyScale,\n\t\t\taccessors: {\n\t\t\t\txAccessor,\n\t\t\t\tyAccessor,\n\t\t\t},\n\t\t\taxis: {\n\t\t\t\tx: {\n\t\t\t\t\torientation: 'bottom' as const,\n\t\t\t\t\tnumTicks: 4,\n\t\t\t\t\ttickFormat: xTickFormat,\n\t\t\t\t\t...( xLabelOverflow === 'ellipsis' ? { tickComponent: TruncatedXTickComponent } : {} ),\n\t\t\t\t\t...xAxisOptions,\n\t\t\t\t},\n\t\t\t\ty: {\n\t\t\t\t\torientation: 'left' as const,\n\t\t\t\t\tnumTicks: 4,\n\t\t\t\t\ttickFormat: yTickFormat,\n\t\t\t\t\t...( yLabelOverflow === 'ellipsis' ? { tickComponent: TruncatedYTickComponent } : {} ),\n\t\t\t\t\t...yAxisOptions,\n\t\t\t\t},\n\t\t\t},\n\t\t\tbarGroup: {\n\t\t\t\tpadding: getGroupPadding( horizontal ? yScale : xScale ),\n\t\t\t},\n\t\t\ttooltip: {\n\t\t\t\tlabelFormatter: providedToolTipLabelFormatter || defaultTooltipLabelFormatter,\n\t\t\t},\n\t\t};\n\t}, [ defaultOptions, options, horizontal ] );\n}\n","import { DataContext } from '@visx/xychart';\nimport { useContext } from 'react';\nimport { isSafari } from '../../../utils';\nimport type { AxisScale, TickRendererProps } from '@visx/axis';\nimport type { FC, CSSProperties } from 'react';\n\n/**\n * Get the bandwidth of a scale\n *\n * @param scale - The scale to get the bandwidth of\n * @return The bandwidth of the scale\n */\nconst getScaleBandwidth = < Scale extends AxisScale >( scale?: Scale ) => {\n\treturn scale && 'bandwidth' in scale ? scale.bandwidth() ?? 0 : 0;\n};\ninterface TruncatedTickComponentProps extends TickRendererProps {\n\t/** Which axis this tick belongs to */\n\taxis: 'x' | 'y';\n}\n\n/**\n * Minimum width in pixels for tick labels when scale bandwidth is very small.\n * Prevents labels from collapsing to unreadable widths on dense charts.\n *\n * Trade-off: When bandwidth is less than this minimum (e.g., many bars in a narrow chart),\n * adjacent labels may overlap since each label uses this minimum width regardless of\n * available space. This prioritizes label readability over preventing overlap.\n *\n * For very dense charts where overlap occurs, consider:\n * - Using `numTicks` option to reduce the number of displayed labels\n * - Using `tickFormat` to abbreviate label text\n * - Increasing chart width or reducing data points\n */\nconst MIN_TICK_LABEL_WIDTH = 20;\n\n/**\n * A tick component that renders labels with text truncation (ellipsis) when they exceed\n * the available bandwidth. Shows the full text on hover via native title attribute.\n *\n * Uses foreignObject to embed HTML within SVG, enabling CSS text-overflow: ellipsis.\n * Inherits text styles from tickLabelProps passed by visx Axis component.\n *\n * Note: A minimum label width (MIN_TICK_LABEL_WIDTH) is enforced to keep labels readable.\n * On very dense charts where bandwidth < 20px, this may cause label overlap.\n * See MIN_TICK_LABEL_WIDTH documentation for mitigation strategies.\n *\n * @param props - The props for the truncated tick component\n * @param props.x - The x position of the tick\n * @param props.y - The y position of the tick\n * @param props.formattedValue - The formatted value of the tick\n * @param props.axis - The axis this tick belongs to\n * @param props.textAnchor - The text anchor of the tick\n * @param props.fill - The fill color of the tick\n * @param props.dy - The dy offset of the tick\n *\n * @return The truncated tick component\n */\nexport const TruncatedTickComponent: FC< TruncatedTickComponentProps > = ( {\n\tx,\n\ty,\n\tformattedValue,\n\taxis,\n\ttextAnchor,\n\tfill,\n\tdy,\n\t...textProps\n} ) => {\n\t// Get max width of the tick label\n\tconst { xScale, yScale } = useContext( DataContext ) || {};\n\tconst scale = axis === 'x' ? xScale : yScale;\n\tconst bandwidth = getScaleBandwidth( scale );\n\tconst maxWidth = Math.max( bandwidth, MIN_TICK_LABEL_WIDTH );\n\n\t// Map SVG textAnchor to CSS textAlign\n\tlet textAlign: 'left' | 'right' | 'center' = 'center';\n\tif ( textAnchor === 'start' ) {\n\t\ttextAlign = 'left';\n\t} else if ( textAnchor === 'end' ) {\n\t\ttextAlign = 'right';\n\t} else if ( textAnchor === 'middle' ) {\n\t\ttextAlign = 'center';\n\t}\n\n\t// Calculate x offset based on text alignment\n\tlet xOffset = 0;\n\tif ( textAlign === 'center' ) {\n\t\txOffset = -maxWidth / 2;\n\t} else if ( textAlign === 'right' ) {\n\t\txOffset = -maxWidth;\n\t}\n\n\t// Extract compatible style properties from SVG text props\n\tconst { fontSize, fontFamily, fontWeight, fontStyle, letterSpacing, opacity } = textProps as {\n\t\tfontSize?: CSSProperties[ 'fontSize' ];\n\t\tfontFamily?: CSSProperties[ 'fontFamily' ];\n\t\tfontWeight?: CSSProperties[ 'fontWeight' ];\n\t\tfontStyle?: CSSProperties[ 'fontStyle' ];\n\t\tletterSpacing?: CSSProperties[ 'letterSpacing' ];\n\t\topacity?: CSSProperties[ 'opacity' ];\n\t};\n\n\tconst textStyles: CSSProperties = {\n\t\t/**\n\t\t * SVG <text> elements are vertically aligned to the baseline by default, but HTML <div> elements inside <foreignObject>\n\t\t * are positioned relative to the top-left corner. To visually align the tick label like SVG text,\n\t\t * we shift the div up by 100% of its height and adjust by twice the SVG dy value (from visx) to approximate original placement.\n\t\t */\n\t\ttransform: `translateY(calc(-100% + ${ dy ?? '0' } * 2))`,\n\t\t// Safari doesn't work well with foreignObject positioning. Use position: fixed as a workaround.\n\t\t...( isSafari() ? { position: 'fixed' as const } : {} ),\n\t\t// Apply compatible SVG text styles\n\t\tfontSize,\n\t\tfontFamily,\n\t\tfontWeight,\n\t\tfontStyle,\n\t\tletterSpacing,\n\t\topacity,\n\t\t// Convert svg text styles to CSS styles for the div\n\t\tcolor: fill ?? 'inherit',\n\t\ttextAlign,\n\t\t// Ensure text is truncated with ellipsis, remains on one line, and shows the full value in a tooltip on hover.\n\t\t// The surrounding div uses CSS to handle overflow, and the 'title' attribute is set for accessibility.\n\t\twidth: maxWidth,\n\t\toverflow: 'hidden',\n\t\ttextOverflow: 'ellipsis',\n\t\twhiteSpace: 'nowrap',\n\t\tcursor: 'default',\n\t\tpointerEvents: 'auto',\n\t};\n\n\treturn (\n\t\t<foreignObject x={ x + xOffset } y={ y } width={ maxWidth } height={ 0 } overflow=\"visible\">\n\t\t\t<div style={ textStyles } title={ formattedValue }>\n\t\t\t\t{ formattedValue }\n\t\t\t</div>\n\t\t</foreignObject>\n\t);\n};\n\n/**\n * Factory function to create a truncated tick component for a specific axis.\n * Returns a component that can be passed to visx's tickComponent prop.\n *\n * @param axis - The axis this tick component is for ('x' or 'y')\n * @return A tick component function compatible with visx's TickRendererProps\n */\nconst createTruncatedTickComponent = ( axis: 'x' | 'y' ) => ( props: TickRendererProps ) => {\n\treturn <TruncatedTickComponent { ...props } axis={ axis } />;\n};\n\n/**\n * Pre-created tick components for x and y axes.\n * These functions are created once at module initialization and reused,\n * avoiding repeated factory calls when configuring axes.\n */\nexport const TruncatedXTickComponent = createTruncatedTickComponent( 'x' );\nexport const TruncatedYTickComponent = createTruncatedTickComponent( 'y' );\n"],"mappings":"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","names":["useContext","useMemo","jsx","useMemo","useContext"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/tooltip/base-tooltip.tsx","../src/components/tooltip/base-tooltip.module.scss","../src/components/tooltip/accessible-tooltip.tsx"],"sourcesContent":["import { formatNumber } from '@automattic/number-formatters';\nimport styles from './base-tooltip.module.scss';\nimport type { CSSProperties, ComponentType, ReactNode } from 'react';\n\ntype TooltipData = {\n\tlabel: string;\n\tvalue: number;\n\tvalueDisplay?: string;\n};\n\ntype TooltipComponentProps = {\n\tdata: TooltipData;\n\tclassName?: string;\n};\n\ntype TooltipCommonProps = {\n\ttop: number;\n\tleft: number;\n\tstyle?: CSSProperties;\n\tclassName?: string;\n\t/**\n\t * Whether to render the tooltip container div. When false, only renders the content.\n\t * Useful when the tooltip is rendered inside a portal or custom container.\n\t * @default true\n\t */\n\trenderContainer?: boolean;\n};\n\ntype DefaultDataTooltip = {\n\tdata: TooltipData;\n\tcomponent?: ComponentType< TooltipComponentProps >;\n\tchildren?: never;\n};\n\ntype CustomTooltip = {\n\tchildren: ReactNode;\n\tdata?: never;\n\tcomponent?: never;\n};\n\ntype BaseTooltipProps = TooltipCommonProps & ( DefaultDataTooltip | CustomTooltip );\n\nconst DefaultTooltipContent = ( { data }: TooltipComponentProps ) => (\n\t<>\n\t\t{ data?.label }: { data?.valueDisplay || formatNumber( data?.value ) }\n\t</>\n);\n\nexport const BaseTooltip = ( {\n\tdata,\n\ttop,\n\tleft,\n\tcomponent: Component = DefaultTooltipContent,\n\tchildren,\n\tclassName,\n\tstyle,\n\trenderContainer = true,\n}: BaseTooltipProps ) => {\n\tconst content = children || ( data && <Component data={ data } className={ className } /> );\n\n\tif ( ! renderContainer ) {\n\t\treturn content;\n\t}\n\n\treturn (\n\t\t<div className={ styles.tooltip } style={ { top, left, ...style } } role=\"tooltip\">\n\t\t\t{ content }\n\t\t</div>\n\t);\n};\n\nexport type { BaseTooltipProps, TooltipData };\n","import 'css-chunk:src/components/tooltip/base-tooltip.module.scss';export default {\n \"tooltip\": \"a8ccharts-OfX6nd\"\n};","import { Tooltip, TooltipContext } from '@visx/xychart';\nimport { useContext, useEffect, useCallback, useMemo } from 'react';\nimport type { SeriesData, DataPointDate } from '../../types';\nimport type {\n\tTooltipProps as BaseTooltipProps,\n\tRenderTooltipParams,\n} from '@visx/xychart/lib/components/Tooltip';\nimport type { ReactNode } from 'react';\n\n// Type for flattened tooltip data used in individual mode\nexport type FlattenedTooltipData = {\n\tdatum: DataPointDate;\n\tseriesLabel: string;\n\tseriesIndex: number;\n\tdataPointIndex: number;\n};\n\n// Enhanced tooltip with keyboard navigation and accessibility\ninterface AccessibleTooltipProps\n\textends Omit< BaseTooltipProps< DataPointDate >, 'renderTooltip' > {\n\trenderTooltip?: ( params: RenderTooltipParams< DataPointDate > ) => ReactNode;\n\tselectedIndex?: number | undefined;\n\ttooltipRef?: ( element: HTMLDivElement | null ) => void;\n\tkeyboardFocusedClassName?: string;\n\t/**\n\t * Flattened tooltip data prepared by parent component\n\t * Each index corresponds to one tooltip to show\n\t */\n\ttooltipData?: FlattenedTooltipData[];\n\t/**\n\t * For line charts: series data to show all series at selected data point\n\t * When provided, shows all series instead of individual tooltips\n\t */\n\tseries?: SeriesData[];\n\t/**\n\t * Whether to combine tooltip information from multiple series into a single tooltip. This is useful for line charts.\n\t * Or to show individual tooltips for each series. This is useful for bar charts.\n\t */\n\tmode?: 'individual' | 'group';\n}\n\nexport const AccessibleTooltip: React.FC< AccessibleTooltipProps > = ( {\n\trenderTooltip,\n\tselectedIndex,\n\ttooltipRef,\n\tkeyboardFocusedClassName,\n\tseries = [],\n\tmode = 'group',\n\t...props\n} ) => {\n\tconst tooltipContext = useContext( TooltipContext );\n\n\tconst tooltipData = useMemo( () => {\n\t\tif ( mode !== 'individual' ) return [];\n\t\tif ( series.length === 0 ) return [];\n\n\t\tconst maxDataPoints = Math.max( ...series.map( s => s.data.length ) );\n\t\tconst flattened: Array< {\n\t\t\tdatum: DataPointDate;\n\t\t\tseriesLabel: string;\n\t\t\tseriesIndex: number;\n\t\t\tdataPointIndex: number;\n\t\t} > = [];\n\n\t\t// Pattern: [series1[0], series2[0], series3[0], series1[1], series2[1], series3[1], ...]\n\t\tfor ( let dataPointIndex = 0; dataPointIndex < maxDataPoints; dataPointIndex++ ) {\n\t\t\tfor ( let seriesIndex = 0; seriesIndex < series.length; seriesIndex++ ) {\n\t\t\t\tconst seriesData = series[ seriesIndex ];\n\t\t\t\tif ( dataPointIndex < seriesData.data.length ) {\n\t\t\t\t\tflattened.push( {\n\t\t\t\t\t\tdatum: seriesData.data[ dataPointIndex ] as DataPointDate,\n\t\t\t\t\t\tseriesLabel: seriesData.label,\n\t\t\t\t\t\tseriesIndex,\n\t\t\t\t\t\tdataPointIndex,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn flattened;\n\t}, [ series, mode ] );\n\n\t// Handle tooltip highlighting for keyboard navigation\n\tuseEffect( () => {\n\t\tif ( selectedIndex === undefined ) {\n\t\t\ttooltipContext?.hideTooltip();\n\t\t\treturn;\n\t\t}\n\n\t\tif ( mode === 'group' ) {\n\t\t\t// Show all series at the selected data point index in single tooltip.\n\t\t\tseries.forEach( ( s, index ) => {\n\t\t\t\tif ( selectedIndex < s.data.length ) {\n\t\t\t\t\tconst datum = s.data[ selectedIndex ];\n\n\t\t\t\t\ttooltipContext?.showTooltip( {\n\t\t\t\t\t\tdatum,\n\t\t\t\t\t\tkey: s.label,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} );\n\t\t} else if ( mode === 'individual' ) {\n\t\t\t// Show individual tooltips for each datapoint from each series.\n\t\t\tif ( selectedIndex < tooltipData.length ) {\n\t\t\t\tconst tooltipItem = tooltipData[ selectedIndex ];\n\n\t\t\t\ttooltipContext?.showTooltip( {\n\t\t\t\t\tdatum: tooltipItem.datum,\n\t\t\t\t\tkey: tooltipItem.seriesLabel,\n\t\t\t\t\tindex: tooltipItem.seriesIndex,\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t\t// Don't include tooltipContext in the dependency array to avoid loop.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [ selectedIndex, tooltipData, series ] );\n\n\t// Create a focusable renderTooltip that includes accessibility features\n\tconst focusableRenderTooltip = useMemo( () => {\n\t\tif ( ! renderTooltip ) return undefined;\n\n\t\treturn ( params: RenderTooltipParams< DataPointDate > ) => {\n\t\t\tconst tooltipContent = renderTooltip( params );\n\n\t\t\tif ( selectedIndex !== undefined ) {\n\t\t\t\treturn (\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={ tooltipRef }\n\t\t\t\t\t\ttabIndex={ -1 }\n\t\t\t\t\t\trole=\"tooltip\"\n\t\t\t\t\t\taria-atomic=\"true\"\n\t\t\t\t\t\tclassName={ keyboardFocusedClassName }\n\t\t\t\t\t\tdata-testid={ `chart-tooltip-${ selectedIndex }` }\n\t\t\t\t\t\tkey={ `chart-tooltip-${ selectedIndex }` }\n\t\t\t\t\t>\n\t\t\t\t\t\t{ tooltipContent }\n\t\t\t\t\t</div>\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn (\n\t\t\t\t<div role=\"tooltip\" aria-live=\"polite\">\n\t\t\t\t\t{ tooltipContent }\n\t\t\t\t</div>\n\t\t\t);\n\t\t};\n\t}, [ renderTooltip, selectedIndex, tooltipRef, keyboardFocusedClassName ] );\n\n\treturn <Tooltip { ...props } renderTooltip={ focusableRenderTooltip } />;\n};\n\n// Keyboard navigation hook for charts\ninterface UseKeyboardNavigationProps {\n\tselectedIndex: number | undefined;\n\tsetSelectedIndex: ( index: number | undefined ) => void;\n\tisNavigating: boolean;\n\tsetIsNavigating: ( navigating: boolean ) => void;\n\tchartRef: React.RefObject< HTMLDivElement >;\n\t/**\n\t * Total number of navigation points (length of tooltip data array)\n\t */\n\ttotalPoints: number;\n}\n\nexport const useKeyboardNavigation = ( {\n\tselectedIndex,\n\tsetSelectedIndex,\n\tisNavigating,\n\tsetIsNavigating,\n\tchartRef,\n\ttotalPoints,\n}: UseKeyboardNavigationProps ) => {\n\t// Focus the tooltip as soon as it is rendered\n\tconst tooltipRef = useCallback(\n\t\t( element: HTMLDivElement | null ) => {\n\t\t\tif ( element && selectedIndex !== undefined ) {\n\t\t\t\telement.focus();\n\t\t\t}\n\t\t},\n\t\t[ selectedIndex ]\n\t);\n\n\t// On each focus of chart, reset the selectedIndex to 0, if keyboard navigation is not already active\n\tconst onChartFocus = useCallback( () => {\n\t\tif ( ! isNavigating && selectedIndex !== undefined ) {\n\t\t\tsetSelectedIndex( 0 );\n\t\t}\n\t}, [ isNavigating, selectedIndex, setSelectedIndex ] );\n\n\t// On each blur of chart, keyboard navigation should restart from first tooltip\n\tconst onChartBlur = useCallback( () => {\n\t\tsetIsNavigating( false );\n\t}, [ setIsNavigating ] );\n\n\tconst onChartKeyDown = useCallback(\n\t\t( event: React.KeyboardEvent< HTMLDivElement > ) => {\n\t\t\tif ( totalPoints === 0 ) return;\n\n\t\t\t// Keep focus on the chart if tab is pressed\n\t\t\tif ( event.key === 'Tab' ) {\n\t\t\t\tchartRef.current?.focus();\n\t\t\t\tsetSelectedIndex( undefined );\n\t\t\t\tsetIsNavigating( false );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst currentSelectedIndex = selectedIndex === undefined ? -1 : selectedIndex;\n\n\t\t\tif ( currentSelectedIndex + 1 >= totalPoints && [ 'ArrowRight' ].includes( event.key ) ) {\n\t\t\t\tchartRef.current?.focus();\n\t\t\t\tsetSelectedIndex( undefined );\n\t\t\t\tsetIsNavigating( false );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tevent.preventDefault();\n\n\t\t\tif ( [ 'ArrowRight' ].includes( event.key ) ) {\n\t\t\t\tsetIsNavigating( true );\n\t\t\t\tsetSelectedIndex( ( currentSelectedIndex + 1 ) % totalPoints );\n\t\t\t} else if ( [ 'ArrowLeft' ].includes( event.key ) ) {\n\t\t\t\tsetIsNavigating( true );\n\t\t\t\tsetSelectedIndex( ( currentSelectedIndex - 1 + totalPoints ) % totalPoints );\n\t\t\t} else if ( event.key === 'Escape' ) {\n\t\t\t\tsetSelectedIndex( undefined );\n\t\t\t\tsetIsNavigating( false );\n\t\t\t\tchartRef.current?.focus();\n\t\t\t}\n\t\t},\n\t\t[ totalPoints, selectedIndex, setSelectedIndex, setIsNavigating, chartRef ]\n\t);\n\n\treturn {\n\t\ttooltipRef,\n\t\tonChartFocus,\n\t\tonChartBlur,\n\t\tonChartKeyDown,\n\t};\n};\n\n// Re-export the base Tooltip for backwards compatibility\nexport { Tooltip };\nexport type { BaseTooltipProps };\n"],"mappings":";AAAA,SAAS,oBAAoB;;;ACAsC,IAAO,8BAAQ;AAAA,EAChF,WAAW;AACb;;;ADyCC,mBAesC,KAftC;AADD,IAAM,wBAAwB,CAAE,EAAE,KAAK,MACtC,iCACG;AAAA,QAAM;AAAA,EAAO;AAAA,EAAI,MAAM,gBAAgB,aAAc,MAAM,KAAM;AAAA,GACpE;AAGM,IAAM,cAAc,CAAE;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW,YAAY;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AACnB,MAAyB;AACxB,QAAM,UAAU,YAAc,QAAQ,oBAAC,aAAU,MAAc,WAAwB;AAEvF,MAAK,CAAE,iBAAkB;AACxB,WAAO;AAAA,EACR;AAEA,SACC,oBAAC,SAAI,WAAY,4BAAO,SAAU,OAAQ,EAAE,KAAK,MAAM,GAAG,MAAM,GAAI,MAAK,WACtE,mBACH;AAEF;;;AErEA,SAAS,SAAS,sBAAsB;AACxC,SAAS,YAAY,WAAW,aAAa,eAAe;AA+HvD,gBAAAA,YAAA;AAvFE,IAAM,oBAAwD,CAAE;AAAA,EACtE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS,CAAC;AAAA,EACV,OAAO;AAAA,EACP,GAAG;AACJ,MAAO;AACN,QAAM,iBAAiB,WAAY,cAAe;AAElD,QAAM,cAAc,QAAS,MAAM;AAClC,QAAK,SAAS,aAAe,QAAO,CAAC;AACrC,QAAK,OAAO,WAAW,EAAI,QAAO,CAAC;AAEnC,UAAM,gBAAgB,KAAK,IAAK,GAAG,OAAO,IAAK,OAAK,EAAE,KAAK,MAAO,CAAE;AACpE,UAAM,YAKA,CAAC;AAGP,aAAU,iBAAiB,GAAG,iBAAiB,eAAe,kBAAmB;AAChF,eAAU,cAAc,GAAG,cAAc,OAAO,QAAQ,eAAgB;AACvE,cAAM,aAAa,OAAQ,WAAY;AACvC,YAAK,iBAAiB,WAAW,KAAK,QAAS;AAC9C,oBAAU,KAAM;AAAA,YACf,OAAO,WAAW,KAAM,cAAe;AAAA,YACvC,aAAa,WAAW;AAAA,YACxB;AAAA,YACA;AAAA,UACD,CAAE;AAAA,QACH;AAAA,MACD;AAAA,IACD;AAEA,WAAO;AAAA,EACR,GAAG,CAAE,QAAQ,IAAK,CAAE;AAGpB,YAAW,MAAM;AAChB,QAAK,kBAAkB,QAAY;AAClC,sBAAgB,YAAY;AAC5B;AAAA,IACD;AAEA,QAAK,SAAS,SAAU;AAEvB,aAAO,QAAS,CAAE,GAAG,UAAW;AAC/B,YAAK,gBAAgB,EAAE,KAAK,QAAS;AACpC,gBAAM,QAAQ,EAAE,KAAM,aAAc;AAEpC,0BAAgB,YAAa;AAAA,YAC5B;AAAA,YACA,KAAK,EAAE;AAAA,YACP;AAAA,UACD,CAAE;AAAA,QACH;AAAA,MACD,CAAE;AAAA,IACH,WAAY,SAAS,cAAe;AAEnC,UAAK,gBAAgB,YAAY,QAAS;AACzC,cAAM,cAAc,YAAa,aAAc;AAE/C,wBAAgB,YAAa;AAAA,UAC5B,OAAO,YAAY;AAAA,UACnB,KAAK,YAAY;AAAA,UACjB,OAAO,YAAY;AAAA,QACpB,CAAE;AAAA,MACH;AAAA,IACD;AAAA,EAID,GAAG,CAAE,eAAe,aAAa,MAAO,CAAE;AAG1C,QAAM,yBAAyB,QAAS,MAAM;AAC7C,QAAK,CAAE,cAAgB,QAAO;AAE9B,WAAO,CAAE,WAAkD;AAC1D,YAAM,iBAAiB,cAAe,MAAO;AAE7C,UAAK,kBAAkB,QAAY;AAClC,eACC,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACA,KAAM;AAAA,YACN,UAAW;AAAA,YACX,MAAK;AAAA,YACL,eAAY;AAAA,YACZ,WAAY;AAAA,YACZ,eAAc,iBAAkB,aAAc;AAAA,YAG5C;AAAA;AAAA,UAFI,iBAAkB,aAAc;AAAA,QAGvC;AAAA,MAEF;AAEA,aACC,gBAAAA,KAAC,SAAI,MAAK,WAAU,aAAU,UAC3B,0BACH;AAAA,IAEF;AAAA,EACD,GAAG,CAAE,eAAe,eAAe,YAAY,wBAAyB,CAAE;AAE1E,SAAO,gBAAAA,KAAC,WAAU,GAAG,OAAQ,eAAgB,wBAAyB;AACvE;AAeO,IAAM,wBAAwB,CAAE;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,MAAmC;AAElC,QAAM,aAAa;AAAA,IAClB,CAAE,YAAoC;AACrC,UAAK,WAAW,kBAAkB,QAAY;AAC7C,gBAAQ,MAAM;AAAA,MACf;AAAA,IACD;AAAA,IACA,CAAE,aAAc;AAAA,EACjB;AAGA,QAAM,eAAe,YAAa,MAAM;AACvC,QAAK,CAAE,gBAAgB,kBAAkB,QAAY;AACpD,uBAAkB,CAAE;AAAA,IACrB;AAAA,EACD,GAAG,CAAE,cAAc,eAAe,gBAAiB,CAAE;AAGrD,QAAM,cAAc,YAAa,MAAM;AACtC,oBAAiB,KAAM;AAAA,EACxB,GAAG,CAAE,eAAgB,CAAE;AAEvB,QAAM,iBAAiB;AAAA,IACtB,CAAE,UAAkD;AACnD,UAAK,gBAAgB,EAAI;AAGzB,UAAK,MAAM,QAAQ,OAAQ;AAC1B,iBAAS,SAAS,MAAM;AACxB,yBAAkB,MAAU;AAC5B,wBAAiB,KAAM;AACvB;AAAA,MACD;AAEA,YAAM,uBAAuB,kBAAkB,SAAY,KAAK;AAEhE,UAAK,uBAAuB,KAAK,eAAe,CAAE,YAAa,EAAE,SAAU,MAAM,GAAI,GAAI;AACxF,iBAAS,SAAS,MAAM;AACxB,yBAAkB,MAAU;AAC5B,wBAAiB,KAAM;AACvB;AAAA,MACD;AAEA,YAAM,eAAe;AAErB,UAAK,CAAE,YAAa,EAAE,SAAU,MAAM,GAAI,GAAI;AAC7C,wBAAiB,IAAK;AACtB,0BAAoB,uBAAuB,KAAM,WAAY;AAAA,MAC9D,WAAY,CAAE,WAAY,EAAE,SAAU,MAAM,GAAI,GAAI;AACnD,wBAAiB,IAAK;AACtB,0BAAoB,uBAAuB,IAAI,eAAgB,WAAY;AAAA,MAC5E,WAAY,MAAM,QAAQ,UAAW;AACpC,yBAAkB,MAAU;AAC5B,wBAAiB,KAAM;AACvB,iBAAS,SAAS,MAAM;AAAA,MACzB;AAAA,IACD;AAAA,IACA,CAAE,aAAa,eAAe,kBAAkB,iBAAiB,QAAS;AAAA,EAC3E;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;","names":["jsx"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/jetpack/jetpack/projects/js-packages/charts/dist/chunk-7HROSZRS.cjs","../src/components/tooltip/base-tooltip.tsx","../src/components/tooltip/base-tooltip.module.scss","../src/components/tooltip/accessible-tooltip.tsx"],"names":["jsx"],"mappings":"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","file":"/home/runner/work/jetpack/jetpack/projects/js-packages/charts/dist/chunk-7HROSZRS.cjs","sourcesContent":[null,"import { formatNumber } from '@automattic/number-formatters';\nimport styles from './base-tooltip.module.scss';\nimport type { CSSProperties, ComponentType, ReactNode } from 'react';\n\ntype TooltipData = {\n\tlabel: string;\n\tvalue: number;\n\tvalueDisplay?: string;\n};\n\ntype TooltipComponentProps = {\n\tdata: TooltipData;\n\tclassName?: string;\n};\n\ntype TooltipCommonProps = {\n\ttop: number;\n\tleft: number;\n\tstyle?: CSSProperties;\n\tclassName?: string;\n\t/**\n\t * Whether to render the tooltip container div. When false, only renders the content.\n\t * Useful when the tooltip is rendered inside a portal or custom container.\n\t * @default true\n\t */\n\trenderContainer?: boolean;\n};\n\ntype DefaultDataTooltip = {\n\tdata: TooltipData;\n\tcomponent?: ComponentType< TooltipComponentProps >;\n\tchildren?: never;\n};\n\ntype CustomTooltip = {\n\tchildren: ReactNode;\n\tdata?: never;\n\tcomponent?: never;\n};\n\ntype BaseTooltipProps = TooltipCommonProps & ( DefaultDataTooltip | CustomTooltip );\n\nconst DefaultTooltipContent = ( { data }: TooltipComponentProps ) => (\n\t<>\n\t\t{ data?.label }: { data?.valueDisplay || formatNumber( data?.value ) }\n\t</>\n);\n\nexport const BaseTooltip = ( {\n\tdata,\n\ttop,\n\tleft,\n\tcomponent: Component = DefaultTooltipContent,\n\tchildren,\n\tclassName,\n\tstyle,\n\trenderContainer = true,\n}: BaseTooltipProps ) => {\n\tconst content = children || ( data && <Component data={ data } className={ className } /> );\n\n\tif ( ! renderContainer ) {\n\t\treturn content;\n\t}\n\n\treturn (\n\t\t<div className={ styles.tooltip } style={ { top, left, ...style } } role=\"tooltip\">\n\t\t\t{ content }\n\t\t</div>\n\t);\n};\n\nexport type { BaseTooltipProps, TooltipData };\n","import 'css-chunk:src/components/tooltip/base-tooltip.module.scss';export default {\n \"tooltip\": \"a8ccharts-OfX6nd\"\n};","import { Tooltip, TooltipContext } from '@visx/xychart';\nimport { useContext, useEffect, useCallback, useMemo } from 'react';\nimport type { SeriesData, DataPointDate } from '../../types';\nimport type {\n\tTooltipProps as BaseTooltipProps,\n\tRenderTooltipParams,\n} from '@visx/xychart/lib/components/Tooltip';\nimport type { ReactNode } from 'react';\n\n// Type for flattened tooltip data used in individual mode\nexport type FlattenedTooltipData = {\n\tdatum: DataPointDate;\n\tseriesLabel: string;\n\tseriesIndex: number;\n\tdataPointIndex: number;\n};\n\n// Enhanced tooltip with keyboard navigation and accessibility\ninterface AccessibleTooltipProps\n\textends Omit< BaseTooltipProps< DataPointDate >, 'renderTooltip' > {\n\trenderTooltip?: ( params: RenderTooltipParams< DataPointDate > ) => ReactNode;\n\tselectedIndex?: number | undefined;\n\ttooltipRef?: ( element: HTMLDivElement | null ) => void;\n\tkeyboardFocusedClassName?: string;\n\t/**\n\t * Flattened tooltip data prepared by parent component\n\t * Each index corresponds to one tooltip to show\n\t */\n\ttooltipData?: FlattenedTooltipData[];\n\t/**\n\t * For line charts: series data to show all series at selected data point\n\t * When provided, shows all series instead of individual tooltips\n\t */\n\tseries?: SeriesData[];\n\t/**\n\t * Whether to combine tooltip information from multiple series into a single tooltip. This is useful for line charts.\n\t * Or to show individual tooltips for each series. This is useful for bar charts.\n\t */\n\tmode?: 'individual' | 'group';\n}\n\nexport const AccessibleTooltip: React.FC< AccessibleTooltipProps > = ( {\n\trenderTooltip,\n\tselectedIndex,\n\ttooltipRef,\n\tkeyboardFocusedClassName,\n\tseries = [],\n\tmode = 'group',\n\t...props\n} ) => {\n\tconst tooltipContext = useContext( TooltipContext );\n\n\tconst tooltipData = useMemo( () => {\n\t\tif ( mode !== 'individual' ) return [];\n\t\tif ( series.length === 0 ) return [];\n\n\t\tconst maxDataPoints = Math.max( ...series.map( s => s.data.length ) );\n\t\tconst flattened: Array< {\n\t\t\tdatum: DataPointDate;\n\t\t\tseriesLabel: string;\n\t\t\tseriesIndex: number;\n\t\t\tdataPointIndex: number;\n\t\t} > = [];\n\n\t\t// Pattern: [series1[0], series2[0], series3[0], series1[1], series2[1], series3[1], ...]\n\t\tfor ( let dataPointIndex = 0; dataPointIndex < maxDataPoints; dataPointIndex++ ) {\n\t\t\tfor ( let seriesIndex = 0; seriesIndex < series.length; seriesIndex++ ) {\n\t\t\t\tconst seriesData = series[ seriesIndex ];\n\t\t\t\tif ( dataPointIndex < seriesData.data.length ) {\n\t\t\t\t\tflattened.push( {\n\t\t\t\t\t\tdatum: seriesData.data[ dataPointIndex ] as DataPointDate,\n\t\t\t\t\t\tseriesLabel: seriesData.label,\n\t\t\t\t\t\tseriesIndex,\n\t\t\t\t\t\tdataPointIndex,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn flattened;\n\t}, [ series, mode ] );\n\n\t// Handle tooltip highlighting for keyboard navigation\n\tuseEffect( () => {\n\t\tif ( selectedIndex === undefined ) {\n\t\t\ttooltipContext?.hideTooltip();\n\t\t\treturn;\n\t\t}\n\n\t\tif ( mode === 'group' ) {\n\t\t\t// Show all series at the selected data point index in single tooltip.\n\t\t\tseries.forEach( ( s, index ) => {\n\t\t\t\tif ( selectedIndex < s.data.length ) {\n\t\t\t\t\tconst datum = s.data[ selectedIndex ];\n\n\t\t\t\t\ttooltipContext?.showTooltip( {\n\t\t\t\t\t\tdatum,\n\t\t\t\t\t\tkey: s.label,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} );\n\t\t} else if ( mode === 'individual' ) {\n\t\t\t// Show individual tooltips for each datapoint from each series.\n\t\t\tif ( selectedIndex < tooltipData.length ) {\n\t\t\t\tconst tooltipItem = tooltipData[ selectedIndex ];\n\n\t\t\t\ttooltipContext?.showTooltip( {\n\t\t\t\t\tdatum: tooltipItem.datum,\n\t\t\t\t\tkey: tooltipItem.seriesLabel,\n\t\t\t\t\tindex: tooltipItem.seriesIndex,\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t\t// Don't include tooltipContext in the dependency array to avoid loop.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [ selectedIndex, tooltipData, series ] );\n\n\t// Create a focusable renderTooltip that includes accessibility features\n\tconst focusableRenderTooltip = useMemo( () => {\n\t\tif ( ! renderTooltip ) return undefined;\n\n\t\treturn ( params: RenderTooltipParams< DataPointDate > ) => {\n\t\t\tconst tooltipContent = renderTooltip( params );\n\n\t\t\tif ( selectedIndex !== undefined ) {\n\t\t\t\treturn (\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={ tooltipRef }\n\t\t\t\t\t\ttabIndex={ -1 }\n\t\t\t\t\t\trole=\"tooltip\"\n\t\t\t\t\t\taria-atomic=\"true\"\n\t\t\t\t\t\tclassName={ keyboardFocusedClassName }\n\t\t\t\t\t\tdata-testid={ `chart-tooltip-${ selectedIndex }` }\n\t\t\t\t\t\tkey={ `chart-tooltip-${ selectedIndex }` }\n\t\t\t\t\t>\n\t\t\t\t\t\t{ tooltipContent }\n\t\t\t\t\t</div>\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn (\n\t\t\t\t<div role=\"tooltip\" aria-live=\"polite\">\n\t\t\t\t\t{ tooltipContent }\n\t\t\t\t</div>\n\t\t\t);\n\t\t};\n\t}, [ renderTooltip, selectedIndex, tooltipRef, keyboardFocusedClassName ] );\n\n\treturn <Tooltip { ...props } renderTooltip={ focusableRenderTooltip } />;\n};\n\n// Keyboard navigation hook for charts\ninterface UseKeyboardNavigationProps {\n\tselectedIndex: number | undefined;\n\tsetSelectedIndex: ( index: number | undefined ) => void;\n\tisNavigating: boolean;\n\tsetIsNavigating: ( navigating: boolean ) => void;\n\tchartRef: React.RefObject< HTMLDivElement >;\n\t/**\n\t * Total number of navigation points (length of tooltip data array)\n\t */\n\ttotalPoints: number;\n}\n\nexport const useKeyboardNavigation = ( {\n\tselectedIndex,\n\tsetSelectedIndex,\n\tisNavigating,\n\tsetIsNavigating,\n\tchartRef,\n\ttotalPoints,\n}: UseKeyboardNavigationProps ) => {\n\t// Focus the tooltip as soon as it is rendered\n\tconst tooltipRef = useCallback(\n\t\t( element: HTMLDivElement | null ) => {\n\t\t\tif ( element && selectedIndex !== undefined ) {\n\t\t\t\telement.focus();\n\t\t\t}\n\t\t},\n\t\t[ selectedIndex ]\n\t);\n\n\t// On each focus of chart, reset the selectedIndex to 0, if keyboard navigation is not already active\n\tconst onChartFocus = useCallback( () => {\n\t\tif ( ! isNavigating && selectedIndex !== undefined ) {\n\t\t\tsetSelectedIndex( 0 );\n\t\t}\n\t}, [ isNavigating, selectedIndex, setSelectedIndex ] );\n\n\t// On each blur of chart, keyboard navigation should restart from first tooltip\n\tconst onChartBlur = useCallback( () => {\n\t\tsetIsNavigating( false );\n\t}, [ setIsNavigating ] );\n\n\tconst onChartKeyDown = useCallback(\n\t\t( event: React.KeyboardEvent< HTMLDivElement > ) => {\n\t\t\tif ( totalPoints === 0 ) return;\n\n\t\t\t// Keep focus on the chart if tab is pressed\n\t\t\tif ( event.key === 'Tab' ) {\n\t\t\t\tchartRef.current?.focus();\n\t\t\t\tsetSelectedIndex( undefined );\n\t\t\t\tsetIsNavigating( false );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst currentSelectedIndex = selectedIndex === undefined ? -1 : selectedIndex;\n\n\t\t\tif ( currentSelectedIndex + 1 >= totalPoints && [ 'ArrowRight' ].includes( event.key ) ) {\n\t\t\t\tchartRef.current?.focus();\n\t\t\t\tsetSelectedIndex( undefined );\n\t\t\t\tsetIsNavigating( false );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tevent.preventDefault();\n\n\t\t\tif ( [ 'ArrowRight' ].includes( event.key ) ) {\n\t\t\t\tsetIsNavigating( true );\n\t\t\t\tsetSelectedIndex( ( currentSelectedIndex + 1 ) % totalPoints );\n\t\t\t} else if ( [ 'ArrowLeft' ].includes( event.key ) ) {\n\t\t\t\tsetIsNavigating( true );\n\t\t\t\tsetSelectedIndex( ( currentSelectedIndex - 1 + totalPoints ) % totalPoints );\n\t\t\t} else if ( event.key === 'Escape' ) {\n\t\t\t\tsetSelectedIndex( undefined );\n\t\t\t\tsetIsNavigating( false );\n\t\t\t\tchartRef.current?.focus();\n\t\t\t}\n\t\t},\n\t\t[ totalPoints, selectedIndex, setSelectedIndex, setIsNavigating, chartRef ]\n\t);\n\n\treturn {\n\t\ttooltipRef,\n\t\tonChartFocus,\n\t\tonChartBlur,\n\t\tonChartKeyDown,\n\t};\n};\n\n// Re-export the base Tooltip for backwards compatibility\nexport { Tooltip };\nexport type { BaseTooltipProps };\n"]}
|