@automattic/charts 3.2.0 → 4.1.0
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 +47 -0
- package/SECURITY.md +1 -0
- package/TOKENS.md +53 -58
- package/dist/index.cjs +1483 -664
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +93 -29
- package/dist/index.d.cts +233 -247
- package/dist/index.d.ts +233 -247
- package/dist/index.js +1484 -667
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/charts/area-chart/area-chart.tsx +39 -25
- package/src/charts/area-chart/test/area-chart.test.tsx +4 -33
- package/src/charts/area-chart/test/tick-placement.test.tsx +55 -0
- package/src/charts/area-chart/test/tooltip-resolution-branches.test.tsx +86 -0
- package/src/charts/area-chart/test/tooltip-resolution.test.tsx +69 -0
- package/src/charts/area-chart/types.ts +4 -7
- package/src/charts/bar-chart/bar-chart.tsx +2 -1
- package/src/charts/bar-chart/private/test/use-bar-chart-options-formatting.test.tsx +69 -0
- package/src/charts/bar-chart/private/use-bar-chart-options.ts +27 -15
- package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -0
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +33 -23
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +138 -182
- package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +46 -0
- package/src/charts/geo-chart/geo-chart.module.scss +1 -0
- package/src/charts/geo-chart/geo-chart.tsx +5 -10
- package/src/charts/geo-chart/test/geo-chart.test.tsx +2 -2
- package/src/charts/heatmap-chart/heatmap-chart.module.scss +52 -16
- package/src/charts/heatmap-chart/heatmap-chart.tsx +89 -37
- package/src/charts/heatmap-chart/index.ts +2 -1
- package/src/charts/heatmap-chart/private/build-calendar-data.ts +126 -89
- package/src/charts/heatmap-chart/private/civil-day.ts +174 -0
- package/src/charts/heatmap-chart/private/heatmap-legend.tsx +11 -4
- package/src/charts/heatmap-chart/private/index.ts +1 -1
- package/src/charts/heatmap-chart/private/use-heatmap-colors.ts +5 -1
- package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +3 -0
- package/src/charts/heatmap-chart/test/civil-day.test.ts +70 -0
- package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +142 -12
- package/src/charts/heatmap-chart/test/use-calendar-heatmap-data.test.tsx +118 -0
- package/src/charts/heatmap-chart/test/use-heatmap-colors.test.ts +8 -0
- package/src/charts/heatmap-chart/test/zoned-calendar-data.test.ts +248 -0
- package/src/charts/heatmap-chart/types.ts +8 -2
- package/src/charts/heatmap-chart/use-calendar-heatmap-data.ts +42 -0
- package/src/charts/leaderboard-chart/hooks/use-fitted-row-count.ts +2 -2
- package/src/charts/leaderboard-chart/hooks/use-leaderboard-legend-items.ts +3 -5
- package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +31 -19
- package/src/charts/leaderboard-chart/leaderboard-chart.tsx +27 -17
- package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +14 -0
- package/src/charts/leaderboard-chart/test/use-leaderboard-legend-items.test.tsx +2 -9
- package/src/charts/leaderboard-chart/types.ts +2 -2
- package/src/charts/line-chart/line-chart.module.scss +10 -18
- package/src/charts/line-chart/line-chart.tsx +128 -26
- package/src/charts/line-chart/private/line-chart-annotation.tsx +18 -19
- package/src/charts/line-chart/test/crosshair-style-types.test.ts +31 -0
- package/src/charts/line-chart/test/default-tooltip-formatting.test.tsx +74 -0
- package/src/charts/line-chart/test/line-chart.test.tsx +174 -41
- package/src/charts/line-chart/test/tick-placement.test.tsx +84 -0
- package/src/charts/line-chart/test/tooltip-resolution.test.tsx +140 -0
- package/src/charts/line-chart/test/zoned-date-string.test.tsx +37 -0
- package/src/charts/line-chart/types.ts +17 -3
- package/src/charts/pie-chart/pie-chart.module.scss +13 -1
- package/src/charts/pie-chart/pie-chart.tsx +30 -40
- package/src/charts/pie-chart/test/pie-chart.test.tsx +59 -12
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +8 -3
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +23 -37
- package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +50 -0
- package/src/charts/private/chart-layout/chart-layout.module.scss +3 -2
- package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +1 -1
- package/src/charts/private/test/axis-formatting.test.tsx +85 -0
- package/src/charts/private/test/time-axis-formatting.test.ts +109 -0
- package/src/charts/private/test/time-axis-options.test.ts +267 -0
- package/src/charts/private/test/time-axis-tick-invariants.test.ts +153 -0
- package/src/charts/private/test/time-axis-tick-values.test.ts +280 -0
- package/src/charts/private/test/time-axis.test.ts +9 -69
- package/src/charts/private/time-axis-options.ts +106 -0
- package/src/charts/private/time-axis.ts +318 -136
- package/src/charts/private/x-zoom/x-zoom.module.scss +3 -3
- package/src/charts/private/xy-plot/xy-plot.module.scss +8 -0
- package/src/charts/sparkline/test/sparkline.test.tsx +29 -56
- package/src/charts/sparkline/types.ts +1 -1
- package/src/components/legend/private/base-legend.module.scss +9 -5
- package/src/components/tooltip/accessible-tooltip.tsx +21 -17
- package/src/components/tooltip/base-tooltip.module.scss +3 -3
- package/src/components/tooltip/private/bounded-tooltip.tsx +227 -0
- package/src/components/tooltip/test/accessible-tooltip.test.tsx +30 -4
- package/src/components/tooltip/test/bounded-tooltip.test.tsx +225 -0
- package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +379 -0
- package/src/components/tooltip/xy-chart-tooltip.tsx +326 -0
- package/src/components/trend-indicator/trend-indicator.module.scss +2 -2
- package/src/hooks/test/use-chart-data-transform-zone.test.tsx +36 -0
- package/src/hooks/test/use-chart-margin.test.tsx +173 -3
- package/src/hooks/test/use-xychart-theme.test.tsx +16 -3
- package/src/hooks/use-chart-data-transform.ts +10 -3
- package/src/hooks/use-chart-margin.tsx +54 -9
- package/src/hooks/use-xychart-theme.ts +25 -9
- package/src/index.ts +8 -0
- package/src/providers/chart-context/global-charts-provider.tsx +37 -38
- package/src/providers/chart-context/hooks/use-chart-formatting.ts +22 -0
- package/src/providers/chart-context/index.ts +1 -0
- package/src/providers/chart-context/private/catalog-pointers.ts +42 -0
- package/src/providers/chart-context/private/series-palette.ts +3 -13
- package/src/providers/chart-context/test/{sparse-theme.test.ts → catalog-pointers.test.ts} +22 -10
- package/src/providers/chart-context/test/chart-context.test.tsx +177 -792
- package/src/providers/chart-context/test/use-global-charts-theme.test.tsx +7 -25
- package/src/providers/chart-context/themes.ts +7 -52
- package/src/providers/chart-context/types.ts +3 -0
- package/src/styles/chart-scope.scss +39 -62
- package/src/styles/test/chart-scope.test.ts +19 -47
- package/src/test-utils/series-fixtures.ts +31 -0
- package/src/test-utils/zoned-parsing-suite.ts +90 -0
- package/src/types.ts +79 -166
- package/src/utils/bucket-info.ts +87 -0
- package/src/utils/color-utils.ts +0 -27
- package/src/utils/date-formatting.ts +157 -0
- package/src/utils/date-parsing.ts +103 -52
- package/src/utils/get-edge-tick-widths.ts +33 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/test/bucket-info.test.ts +185 -0
- package/src/utils/test/color-utils.test.ts +1 -48
- package/src/utils/test/date-formatting.test.ts +89 -0
- package/src/utils/test/date-parsing.auckland.test.ts +6 -0
- package/src/utils/test/date-parsing.santiago.test.ts +6 -0
- package/src/utils/test/date-parsing.test.ts +16 -1
- package/src/utils/test/get-edge-tick-widths.test.ts +55 -0
- package/src/utils/test/get-styles.test.ts +10 -7
- package/src/utils/test/merge-themes.test.ts +18 -46
- package/src/utils/warn-once.ts +23 -0
- package/src/visx/types.ts +56 -5
- package/src/hooks/test/use-xychart-theme-provider-override.test.tsx +0 -28
- package/src/providers/chart-context/private/theme-override-vars.ts +0 -131
- package/src/providers/chart-context/private/with-catalog-pointers.ts +0 -93
- package/src/providers/chart-context/test/theme-override-vars-guard.test.tsx +0 -29
- package/src/providers/chart-context/test/theme-override-vars.test.ts +0 -183
- package/src/providers/chart-context/test/with-catalog-pointers.test.ts +0 -104
- package/src/styles/test/wpds-fallbacks.test.ts +0 -131
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getEdgeTickWidths } from '../get-edge-tick-widths';
|
|
2
|
+
|
|
3
|
+
const mockGetStringWidth = jest.fn();
|
|
4
|
+
jest.mock( '@visx/text', () => ( {
|
|
5
|
+
...jest.requireActual( '@visx/text' ),
|
|
6
|
+
getStringWidth: ( ...args: unknown[] ) => mockGetStringWidth( ...args ),
|
|
7
|
+
} ) );
|
|
8
|
+
|
|
9
|
+
describe( 'getEdgeTickWidths', () => {
|
|
10
|
+
beforeEach( () => {
|
|
11
|
+
mockGetStringWidth.mockReset();
|
|
12
|
+
mockGetStringWidth.mockImplementation( ( label: string ) => label.length );
|
|
13
|
+
} );
|
|
14
|
+
|
|
15
|
+
it( 'measures the formatted first and last ticks', () => {
|
|
16
|
+
const style = { fontSize: 11 };
|
|
17
|
+
|
|
18
|
+
const widths = getEdgeTickWidths( [ 1, 2, 3 ], value => `tick-${ value }`, style );
|
|
19
|
+
|
|
20
|
+
expect( widths ).toEqual( { first: 6, last: 6 } );
|
|
21
|
+
expect( mockGetStringWidth ).toHaveBeenCalledWith( 'tick-1', style );
|
|
22
|
+
expect( mockGetStringWidth ).toHaveBeenCalledWith( 'tick-3', style );
|
|
23
|
+
} );
|
|
24
|
+
|
|
25
|
+
it( 'passes each tick its own index', () => {
|
|
26
|
+
getEdgeTickWidths( [ 'a', 'b', 'c' ], ( value, index ) => `${ index }:${ value }` );
|
|
27
|
+
|
|
28
|
+
expect( mockGetStringWidth ).toHaveBeenCalledWith( '0:a', undefined );
|
|
29
|
+
expect( mockGetStringWidth ).toHaveBeenCalledWith( '2:c', undefined );
|
|
30
|
+
} );
|
|
31
|
+
|
|
32
|
+
it( 'measures a single tick as both edges', () => {
|
|
33
|
+
expect( getEdgeTickWidths( [ 42 ], value => `${ value }` ) ).toEqual( { first: 2, last: 2 } );
|
|
34
|
+
} );
|
|
35
|
+
|
|
36
|
+
it( 'measures the raw value when there is no formatter', () => {
|
|
37
|
+
getEdgeTickWidths( [ 'abcd' ] );
|
|
38
|
+
|
|
39
|
+
expect( mockGetStringWidth ).toHaveBeenCalledWith( 'abcd', undefined );
|
|
40
|
+
} );
|
|
41
|
+
|
|
42
|
+
it( 'returns nothing to reserve for an axis with no ticks', () => {
|
|
43
|
+
expect( getEdgeTickWidths( [] ) ).toEqual( { first: 0, last: 0 } );
|
|
44
|
+
expect( mockGetStringWidth ).not.toHaveBeenCalled();
|
|
45
|
+
} );
|
|
46
|
+
|
|
47
|
+
it( 'reserves nothing for a label the measurer cannot size', () => {
|
|
48
|
+
mockGetStringWidth.mockReturnValue( null );
|
|
49
|
+
|
|
50
|
+
expect( getEdgeTickWidths( [ 1, 2, 3 ], value => `tick-${ value }` ) ).toEqual( {
|
|
51
|
+
first: 0,
|
|
52
|
+
last: 0,
|
|
53
|
+
} );
|
|
54
|
+
} );
|
|
55
|
+
} );
|
|
@@ -13,8 +13,11 @@ describe( 'Series styling utility functions', () => {
|
|
|
13
13
|
options: {},
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
// `getSeriesStroke` takes the resolved palette as an argument rather than reading it
|
|
17
|
+
// off the theme, so this is a plain array of colors.
|
|
18
|
+
const palette = [ '#FF0000', '#00FF00', '#0000FF' ];
|
|
19
|
+
|
|
16
20
|
const mockTheme: Partial< ChartTheme > = {
|
|
17
|
-
colors: [ '#FF0000', '#00FF00', '#0000FF' ],
|
|
18
21
|
lineChart: {
|
|
19
22
|
lineStyles: {
|
|
20
23
|
comparison: {
|
|
@@ -40,17 +43,17 @@ describe( 'Series styling utility functions', () => {
|
|
|
40
43
|
options: { stroke: '#CUSTOM' },
|
|
41
44
|
};
|
|
42
45
|
|
|
43
|
-
const result = getSeriesStroke( seriesWithStroke, 0,
|
|
46
|
+
const result = getSeriesStroke( seriesWithStroke, 0, palette );
|
|
44
47
|
expect( result ).toBe( '#CUSTOM' );
|
|
45
48
|
} );
|
|
46
49
|
|
|
47
|
-
it( 'returns
|
|
48
|
-
const result = getSeriesStroke( mockSeriesData, 1,
|
|
50
|
+
it( 'returns the palette color by index when no custom stroke', () => {
|
|
51
|
+
const result = getSeriesStroke( mockSeriesData, 1, palette );
|
|
49
52
|
expect( result ).toBe( '#00FF00' ); // Second color
|
|
50
53
|
} );
|
|
51
54
|
|
|
52
|
-
it( 'wraps around
|
|
53
|
-
const result = getSeriesStroke( mockSeriesData, 5,
|
|
55
|
+
it( 'wraps around the palette when index exceeds its length', () => {
|
|
56
|
+
const result = getSeriesStroke( mockSeriesData, 5, palette );
|
|
54
57
|
expect( result ).toBe( '#0000FF' ); // 5 % 3 = 2, third color
|
|
55
58
|
} );
|
|
56
59
|
} );
|
|
@@ -92,7 +95,7 @@ describe( 'Series styling utility functions', () => {
|
|
|
92
95
|
} );
|
|
93
96
|
|
|
94
97
|
it( 'returns empty object when no styles available', () => {
|
|
95
|
-
const themeWithoutStyles = {
|
|
98
|
+
const themeWithoutStyles = { tickLength: 4 } as ChartTheme;
|
|
96
99
|
const result = getSeriesLineStyles( mockSeriesData, 0, themeWithoutStyles );
|
|
97
100
|
expect( result ).toEqual( {} );
|
|
98
101
|
} );
|
|
@@ -3,33 +3,24 @@ import type { ChartTheme } from '../../types';
|
|
|
3
3
|
|
|
4
4
|
describe( 'mergeThemes', () => {
|
|
5
5
|
const baseTheme: ChartTheme = {
|
|
6
|
-
backgroundColor: '#FFFFFF',
|
|
7
|
-
colors: [ '#red', '#blue' ],
|
|
8
6
|
gridStyles: {
|
|
9
|
-
stroke: '#DCDCDE',
|
|
10
7
|
strokeWidth: 1,
|
|
11
8
|
},
|
|
12
9
|
tickLength: 4,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
secondaryColor: '#global-secondary',
|
|
10
|
+
seriesLineStyles: [ { strokeWidth: 1 }, { strokeWidth: 2 } ],
|
|
11
|
+
sparkline: {
|
|
12
|
+
strokeWidth: 1.5,
|
|
13
|
+
margin: { top: 2, right: 2, bottom: 2, left: 2 },
|
|
18
14
|
},
|
|
19
15
|
};
|
|
20
16
|
|
|
21
17
|
const overrideTheme: ChartTheme = {
|
|
22
|
-
backgroundColor: '#F0F0F0',
|
|
23
|
-
colors: [ '#green', '#yellow' ],
|
|
24
18
|
gridStyles: {
|
|
25
|
-
stroke: '#000000',
|
|
26
19
|
strokeWidth: 2,
|
|
27
20
|
},
|
|
28
21
|
tickLength: 8,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
leaderboardChart: {
|
|
32
|
-
primaryColor: '#local-primary',
|
|
22
|
+
sparkline: {
|
|
23
|
+
strokeWidth: 3,
|
|
33
24
|
},
|
|
34
25
|
};
|
|
35
26
|
|
|
@@ -41,60 +32,41 @@ describe( 'mergeThemes', () => {
|
|
|
41
32
|
it( 'should merge themes with local theme taking precedence for top-level properties', () => {
|
|
42
33
|
const result = mergeThemes( baseTheme, overrideTheme );
|
|
43
34
|
|
|
44
|
-
expect( result.
|
|
45
|
-
expect( result.tickLength ).toBe( 8 ); // from local
|
|
46
|
-
expect( result.colors ).toEqual( [ '#green', '#yellow' ] ); // from local (array replacement)
|
|
35
|
+
expect( result.tickLength ).toBe( 8 );
|
|
47
36
|
} );
|
|
48
37
|
|
|
49
38
|
it( 'should deeply merge nested objects with local theme taking precedence', () => {
|
|
50
39
|
const result = mergeThemes( baseTheme, overrideTheme );
|
|
51
40
|
|
|
52
|
-
|
|
53
|
-
expect( result.gridStyles ).toEqual( {
|
|
54
|
-
stroke: '#000000', // from local
|
|
55
|
-
strokeWidth: 2, // from local
|
|
56
|
-
} );
|
|
41
|
+
expect( result.gridStyles ).toEqual( { strokeWidth: 2 } );
|
|
57
42
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
secondaryColor: '#global-secondary', // from global (not overridden)
|
|
43
|
+
expect( result.sparkline ).toEqual( {
|
|
44
|
+
strokeWidth: 3, // from local
|
|
45
|
+
margin: { top: 2, right: 2, bottom: 2, left: 2 }, // from global (not overridden)
|
|
62
46
|
} );
|
|
63
47
|
} );
|
|
64
48
|
|
|
65
49
|
it( 'should use global theme properties when not defined in local theme', () => {
|
|
66
50
|
const partialLocalTheme: ChartTheme = {
|
|
67
|
-
backgroundColor: '#F0F0F0',
|
|
68
|
-
colors: [ '#green' ],
|
|
69
51
|
tickLength: 8,
|
|
70
|
-
gridColor: '',
|
|
71
|
-
gridColorDark: '',
|
|
72
52
|
};
|
|
73
53
|
|
|
74
54
|
const result = mergeThemes( baseTheme, partialLocalTheme );
|
|
75
55
|
|
|
76
|
-
expect( result.
|
|
77
|
-
expect( result.gridStyles ).toEqual( baseTheme.gridStyles );
|
|
78
|
-
expect( result.leaderboardChart ).toEqual( baseTheme.leaderboardChart );
|
|
56
|
+
expect( result.tickLength ).toBe( 8 );
|
|
57
|
+
expect( result.gridStyles ).toEqual( baseTheme.gridStyles );
|
|
58
|
+
expect( result.leaderboardChart ).toEqual( baseTheme.leaderboardChart );
|
|
79
59
|
} );
|
|
80
60
|
|
|
81
61
|
it( 'should handle array replacement correctly', () => {
|
|
82
|
-
const
|
|
83
|
-
...baseTheme,
|
|
84
|
-
colors: [ '#red', '#blue', '#purple' ],
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const localWithFewerColors: ChartTheme = {
|
|
62
|
+
const localWithFewerStyles: ChartTheme = {
|
|
88
63
|
...overrideTheme,
|
|
89
|
-
|
|
64
|
+
seriesLineStyles: [ { strokeWidth: 5 } ],
|
|
90
65
|
};
|
|
91
66
|
|
|
92
|
-
const result = mergeThemes(
|
|
67
|
+
const result = mergeThemes( baseTheme, localWithFewerStyles );
|
|
93
68
|
|
|
94
69
|
// Arrays should be replaced, not concatenated
|
|
95
|
-
expect( result.
|
|
96
|
-
expect( result.colors ).not.toContain( '#red' );
|
|
97
|
-
expect( result.colors ).not.toContain( '#blue' );
|
|
98
|
-
expect( result.colors ).not.toContain( '#purple' );
|
|
70
|
+
expect( result.seriesLineStyles ).toEqual( [ { strokeWidth: 5 } ] );
|
|
99
71
|
} );
|
|
100
72
|
} );
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `process.env.NODE_ENV` is replaced by the bundler at build time. Declare a
|
|
3
|
+
* minimal `process` locally so this file type-checks as source under `jetpack:src`.
|
|
4
|
+
*/
|
|
5
|
+
declare const process: { env: Record< string, string | undefined > };
|
|
6
|
+
|
|
7
|
+
const warned = new Set< string >();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Warns about a host-supplied value once per process, and never in production.
|
|
11
|
+
*
|
|
12
|
+
* @param key - Identifies the value, so a bad prop on every point warns once.
|
|
13
|
+
* @param message - What the host got wrong and what happens instead.
|
|
14
|
+
*/
|
|
15
|
+
export const warnOnce = ( key: string, message: string ): void => {
|
|
16
|
+
if ( warned.has( key ) || process.env.NODE_ENV === 'production' ) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
warned.add( key );
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.warn( `[Charts] ${ message }` );
|
|
23
|
+
};
|
package/src/visx/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PickD3Scale } from '@visx/scale';
|
|
2
2
|
import type { TooltipProps as VisxTooltipProps, UseTooltipPortalOptions } from '@visx/tooltip';
|
|
3
3
|
import type { GlyphProps, TooltipContextType } from '@visx/xychart';
|
|
4
|
-
import type { ReactNode, SVGProps } from 'react';
|
|
4
|
+
import type { CSSProperties, ReactNode, SVGProps } from 'react';
|
|
5
5
|
|
|
6
6
|
export type RenderTooltipParams< Datum extends object > = TooltipContextType< Datum > & {
|
|
7
7
|
colorScale?: PickD3Scale< 'ordinal', string, string >;
|
|
@@ -12,20 +12,71 @@ export interface RenderTooltipGlyphProps< Datum extends object > extends GlyphPr
|
|
|
12
12
|
isNearestDatum: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export type TooltipPlacement = 'auto' | 'below-axis';
|
|
16
|
+
|
|
17
|
+
type CrosshairPaintProperty =
|
|
18
|
+
| 'stroke'
|
|
19
|
+
| 'strokeWidth'
|
|
20
|
+
| 'strokeOpacity'
|
|
21
|
+
| 'strokeDasharray'
|
|
22
|
+
| 'strokeLinecap'
|
|
23
|
+
| 'opacity';
|
|
24
|
+
|
|
25
|
+
export type CrosshairStyle = Pick< SVGProps< SVGLineElement >, CrosshairPaintProperty > & {
|
|
26
|
+
className?: string;
|
|
27
|
+
style?: Pick< CSSProperties, CrosshairPaintProperty >;
|
|
28
|
+
};
|
|
29
|
+
|
|
15
30
|
export type XyChartTooltipProps< Datum extends object > = {
|
|
16
31
|
renderTooltip: ( params: RenderTooltipParams< Datum > ) => ReactNode;
|
|
17
32
|
renderGlyph?: ( params: RenderTooltipGlyphProps< Datum > ) => ReactNode;
|
|
33
|
+
/**
|
|
34
|
+
* Keep the panel below the x-axis label band, centered at the datum x and horizontally clamped.
|
|
35
|
+
* Vertical bounds do not move this placement; clipping ancestors can still cut it off.
|
|
36
|
+
* @default 'auto'
|
|
37
|
+
*/
|
|
38
|
+
tooltipPlacement?: TooltipPlacement;
|
|
39
|
+
/** Merge overrides with the default box styles; use `unstyled` to strip the box styling. */
|
|
40
|
+
style?: VisxTooltipProps[ 'style' ];
|
|
18
41
|
snapTooltipToDatumX?: boolean;
|
|
19
42
|
snapTooltipToDatumY?: boolean;
|
|
20
43
|
showVerticalCrosshair?: boolean;
|
|
21
44
|
showHorizontalCrosshair?: boolean;
|
|
22
45
|
showDatumGlyph?: boolean;
|
|
23
46
|
showSeriesGlyphs?: boolean;
|
|
24
|
-
verticalCrosshairStyle?:
|
|
25
|
-
horizontalCrosshairStyle?:
|
|
47
|
+
verticalCrosshairStyle?: CrosshairStyle;
|
|
48
|
+
horizontalCrosshairStyle?: CrosshairStyle;
|
|
26
49
|
glyphStyle?: SVGProps< SVGCircleElement >;
|
|
50
|
+
/**
|
|
51
|
+
* Flip and clamp the tooltip box so it stays inside the nearest ancestor
|
|
52
|
+
* that clips its overflow, or the viewport when there is none. The box may
|
|
53
|
+
* leave the chart wrapper. (It used to keep a body-level portal inside the
|
|
54
|
+
* viewport.)
|
|
55
|
+
* Ignored by `below-axis`.
|
|
56
|
+
* @default true
|
|
57
|
+
*/
|
|
58
|
+
detectBounds?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Stacking order of the tooltip box inside the chart wrapper, which isolates
|
|
61
|
+
* its stacking context: the value never competes with page chrome outside
|
|
62
|
+
* the chart.
|
|
63
|
+
* @default 3
|
|
64
|
+
*/
|
|
65
|
+
zIndex?: number;
|
|
66
|
+
/**
|
|
67
|
+
* @deprecated Accepted and ignored. The box renders inside the chart wrapper
|
|
68
|
+
* and moves with it, so it needs no scroll tracking.
|
|
69
|
+
*/
|
|
70
|
+
scroll?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* @deprecated Accepted and ignored. Nothing measures the box any more, so
|
|
73
|
+
* there is no measurement to debounce.
|
|
74
|
+
*/
|
|
75
|
+
debounce?: number;
|
|
76
|
+
/**
|
|
77
|
+
* @deprecated Accepted and ignored. No ResizeObserver is used.
|
|
78
|
+
*/
|
|
27
79
|
resizeObserverPolyfill?: UseTooltipPortalOptions[ 'polyfill' ];
|
|
28
|
-
} & Omit< VisxTooltipProps, 'left' | 'top' | 'children'
|
|
29
|
-
Pick< UseTooltipPortalOptions, 'debounce' | 'detectBounds' | 'scroll' | 'zIndex' >;
|
|
80
|
+
} & Omit< VisxTooltipProps, 'left' | 'top' | 'children' | 'applyPositionStyle' >;
|
|
30
81
|
|
|
31
82
|
export type { LineStyles, GridStyles } from '@visx/xychart';
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { renderHook } from '@testing-library/react';
|
|
2
|
-
import { GlobalChartsProvider } from '../../providers/chart-context/global-charts-provider';
|
|
3
|
-
import { ChartScopeContext } from '../../providers/chart-scope';
|
|
4
|
-
import { useXYChartTheme } from '../use-xychart-theme';
|
|
5
|
-
import type { ReactNode } from 'react';
|
|
6
|
-
|
|
7
|
-
// A `theme` prop override reaches CSS-painted elements through the inline `--a8c-charts-*` var GlobalChartsProvider writes on its wrapper, and reaches the values our own components read through `providerTheme`. Before the fix, `providerTheme` carried the override's literal directly, so an element-level `--a8c-charts-color-background` override set inside the provider tree had no effect on the JS-resolved value — the two paths could disagree. After the fix, `providerTheme` carries the catalog pointer for an overridden role, so both paths resolve through the same CSS cascade and the nearer element wins.
|
|
8
|
-
describe( 'useXYChartTheme resolves a theme prop override through the CSS cascade', () => {
|
|
9
|
-
it( "an element override set inside the provider tree beats the provider's theme prop literal", () => {
|
|
10
|
-
const nestedOverride = document.createElement( 'div' );
|
|
11
|
-
nestedOverride.style.setProperty( '--a8c-charts-color-background', '#00ff00' );
|
|
12
|
-
document.body.appendChild( nestedOverride );
|
|
13
|
-
|
|
14
|
-
const wrapper = ( { children }: { children: ReactNode } ) => (
|
|
15
|
-
<GlobalChartsProvider theme={ { backgroundColor: 'rgb(255,0,0)' } }>
|
|
16
|
-
<ChartScopeContext.Provider value={ nestedOverride }>
|
|
17
|
-
{ children }
|
|
18
|
-
</ChartScopeContext.Provider>
|
|
19
|
-
</GlobalChartsProvider>
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
const { result } = renderHook( () => useXYChartTheme( [] ), { wrapper } );
|
|
23
|
-
|
|
24
|
-
expect( result.current.backgroundColor ).toBe( '#00ff00' );
|
|
25
|
-
|
|
26
|
-
document.body.removeChild( nestedOverride );
|
|
27
|
-
} );
|
|
28
|
-
} );
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { SERIES_SLOT_COUNT, seriesRole } from './series-palette';
|
|
2
|
-
import type { ChartTheme } from '../../../types';
|
|
3
|
-
|
|
4
|
-
/*
|
|
5
|
-
* `process.env.NODE_ENV` is replaced by the bundler at build time. Declare a
|
|
6
|
-
* minimal `process` locally so this file type-checks as source under `jetpack:src`.
|
|
7
|
-
*/
|
|
8
|
-
declare const process: { env: Record< string, string | undefined > };
|
|
9
|
-
|
|
10
|
-
// The consumer theme fields that correspond to an emitted catalog role, and the role each one overrides. Each role must be read by exactly the elements its field controlled before the role existed: `svgLabelSmall.fill` maps to the narrow `--a8c-charts-color-label-axis` rather than `--a8c-charts-color-label`, because the broad role is also read by legend labels, heatmap cell values, funnel labels and the line-chart tooltip, which that field never moved.
|
|
11
|
-
//
|
|
12
|
-
// `theme.colors` is the one field mapped by position rather than by name: entry N publishes slot N+1, which is what makes it sugar over the five slots rather than a second palette with its own precedence.
|
|
13
|
-
const ROLE_FOR_FIELD: Array< [ string, ( theme: Partial< ChartTheme > ) => string | undefined ] > =
|
|
14
|
-
[
|
|
15
|
-
[ '--a8c-charts-color-background', theme => theme.backgroundColor ],
|
|
16
|
-
[ '--a8c-charts-color-grid', theme => theme.gridStyles?.stroke ],
|
|
17
|
-
[ '--a8c-charts-color-axis', theme => theme.xAxisLineStyles?.stroke ],
|
|
18
|
-
[ '--a8c-charts-color-tick', theme => theme.xTickLineStyles?.stroke ],
|
|
19
|
-
[ '--a8c-charts-color-label-axis', theme => theme.svgLabelSmall?.fill ],
|
|
20
|
-
[ '--a8c-charts-color-label-background', theme => theme.labelBackgroundColor ],
|
|
21
|
-
[ '--a8c-charts-color-label-on-fill', theme => theme.labelTextColor ],
|
|
22
|
-
...Array.from(
|
|
23
|
-
{ length: SERIES_SLOT_COUNT },
|
|
24
|
-
( _, index ): [ string, ( theme: Partial< ChartTheme > ) => string | undefined ] => [
|
|
25
|
-
seriesRole( index + 1 ),
|
|
26
|
-
theme => theme.colors?.[ index ],
|
|
27
|
-
]
|
|
28
|
-
),
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
let warnedAboutExtraColors = false;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Warns once when a consumer passes more colors than there are slots to publish them into.
|
|
35
|
-
*
|
|
36
|
-
* @param count - How many entries `theme.colors` holds.
|
|
37
|
-
*/
|
|
38
|
-
function warnOnceAboutExtraColors( count: number ): void {
|
|
39
|
-
if ( warnedAboutExtraColors || process.env.NODE_ENV === 'production' ) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
warnedAboutExtraColors = true;
|
|
44
|
-
// eslint-disable-next-line no-console
|
|
45
|
-
console.warn(
|
|
46
|
-
`[Charts] theme.colors holds ${ count } colors and the palette has ${ SERIES_SLOT_COUNT } slots, so entries past the ${ SERIES_SLOT_COUNT }th are ignored. ` +
|
|
47
|
-
'Set a per-series color with `options.stroke` instead.'
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* The variable a `theme` prop override is published as, one layer outside the role itself.
|
|
53
|
-
*
|
|
54
|
-
* `chart-scope.scss` declares each of these roles as `var(<role>-theme, <catalog default>)`, so an override that is invalid at computed-value time — a fallback-less `var()` naming a token the host never set — only invalidates this variable, and the role still resolves its mapped `--wpds-*` token. Published as the role directly, such a value took the role and every bare `var(<role>)` read site down with it.
|
|
55
|
-
*
|
|
56
|
-
* @param role - The catalog role being overridden.
|
|
57
|
-
* @return The custom property carrying the consumer's value.
|
|
58
|
-
*/
|
|
59
|
-
export const themeLayerVar = ( role: string ): string => `${ role }-theme`;
|
|
60
|
-
|
|
61
|
-
/** Every role whose catalog entry must read a theme layer. `src/styles/test/chart-scope.test.ts` checks the stylesheet against this. */
|
|
62
|
-
export const THEME_LAYERED_ROLES: readonly string[] = ROLE_FOR_FIELD.map( ( [ role ] ) => role );
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Whether a value reads the role it is being published for, which would make the catalog entry depend on itself.
|
|
66
|
-
*
|
|
67
|
-
* The dependency runs `<role>: var(<role>-theme, …)`, so a value naming `<role>` closes a cycle through the catalog entry. CSS marks every custom property in a cycle invalid at computed-value time — the role's own fallback is *not* used, so the token resolves to nothing and every chart loses that color. Verified in Chrome: `--role-theme: var(--role, blue); --role: var(--role-theme, green)` leaves `--role` empty, not `green`.
|
|
68
|
-
*
|
|
69
|
-
* Three things this has to get right, none of which a plain substring test gets:
|
|
70
|
-
*
|
|
71
|
-
* `var()` permits whitespace after the opening paren, so testing for `var(<role>` misses `var( --a8c-charts-color-grid, … )`.
|
|
72
|
-
*
|
|
73
|
-
* Several roles are prefixes of others (`--a8c-charts-color-label` of `--a8c-charts-color-label-secondary`), and every role is a prefix of its own theme layer. A prefix match would read a legitimate cross-role pointer as a self-reference and silently drop the override, so the role must be followed by something that cannot continue an identifier — `-` included. A value naming `<role>-theme` is therefore not treated as a self-reference: that cycle is confined to the theme layer, which leaves the role free to fall back to the catalog default.
|
|
74
|
-
*
|
|
75
|
-
* `resolveCssVariable` also accepts a bare custom-property name with no `var()` wrapper (`--a8c-charts-color-grid`), so that is legal `theme` input too. It forms no cycle — custom properties accept arbitrary token streams — so it survives as a literal string and drops silently at the use site instead of falling back to the catalog default.
|
|
76
|
-
*
|
|
77
|
-
* @param value - The consumer's value for the field.
|
|
78
|
-
* @param role - The catalog role this value would override.
|
|
79
|
-
* @return True when the value reads `role`, so it must not be published.
|
|
80
|
-
*/
|
|
81
|
-
const readsOwnRole = ( value: string, role: string ): boolean =>
|
|
82
|
-
value.trim() === role ||
|
|
83
|
-
new RegExp( `var\\(\\s*${ role.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ) }(?![\\w-])` ).test(
|
|
84
|
-
value
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
export type ThemeOverrides = {
|
|
88
|
-
/** The custom properties to write on the provider wrapper. */
|
|
89
|
-
vars: Record< string, string >;
|
|
90
|
-
/** Every mapped role the consumer set a value for, whether or not it was published. */
|
|
91
|
-
roles: readonly string[];
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Maps a sparse consumer theme onto the theme-layer variables its overridden catalog roles read, so CSS-painted and JS-resolved colors read one source.
|
|
96
|
-
*
|
|
97
|
-
* A value that reads the role it would override is left unpublished — that is the default theme's own pointer surviving `mergeThemes`, and publishing it would invalidate the role. The role is still reported in `roles`: `withCatalogPointers` restores its theme field to the catalog pointer either way, so CSS and visx agree on the catalog default rather than visx painting a literal CSS never sees.
|
|
98
|
-
*
|
|
99
|
-
* `theme.colors` publishes through the same mechanism, one slot per entry, so a CSS declaration of `--a8c-charts-color-series-N` still beats it and a short array leaves the later slots unset rather than blank.
|
|
100
|
-
*
|
|
101
|
-
* @param theme - The consumer's `theme` prop, before merging with the default theme.
|
|
102
|
-
* @return The wrapper's custom properties, and the roles the consumer overrode.
|
|
103
|
-
*/
|
|
104
|
-
export const themeOverrideVars = ( theme?: Partial< ChartTheme > ): ThemeOverrides => {
|
|
105
|
-
if ( ! theme ) {
|
|
106
|
-
return { vars: {}, roles: [] };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if ( Array.isArray( theme.colors ) && theme.colors.length > SERIES_SLOT_COUNT ) {
|
|
110
|
-
warnOnceAboutExtraColors( theme.colors.length );
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const vars: Record< string, string > = {};
|
|
114
|
-
const roles: string[] = [];
|
|
115
|
-
|
|
116
|
-
for ( const [ role, read ] of ROLE_FOR_FIELD ) {
|
|
117
|
-
const value = read( theme );
|
|
118
|
-
|
|
119
|
-
if ( typeof value !== 'string' || value === '' ) {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
roles.push( role );
|
|
124
|
-
|
|
125
|
-
if ( ! readsOwnRole( value, role ) ) {
|
|
126
|
-
vars[ themeLayerVar( role ) ] = value;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return { vars, roles };
|
|
131
|
-
};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { defaultTheme } from '../themes';
|
|
2
|
-
import { SERIES_PALETTE_POINTERS, SERIES_SLOT_COUNT, seriesRole } from './series-palette';
|
|
3
|
-
import type { CompleteChartTheme } from '../../../types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The pointer one palette slot is restored to.
|
|
7
|
-
*
|
|
8
|
-
* Unlike the other roles, this keeps the consumer's own color as the pointer's terminal literal instead of the catalog's. The literal is only ever reached where `getComputedStyle` resolves nothing — SSR and jsdom — and there the palette is the one field where falling back to the catalog default would be visible: every series would paint the same seeded blue. In a browser the slot resolves first, so a CSS declaration of it still outranks the consumer's `theme.colors`.
|
|
9
|
-
*
|
|
10
|
-
* @param index - The slot's zero-based index in `colors`.
|
|
11
|
-
* @param value - The merged theme's entry for it.
|
|
12
|
-
* @return The catalog pointer for that slot.
|
|
13
|
-
*/
|
|
14
|
-
const seriesPointer = ( index: number, value: string | undefined ): string => {
|
|
15
|
-
const role = seriesRole( index + 1 );
|
|
16
|
-
|
|
17
|
-
// Idempotent: `withCatalogPointers` runs this once per overridden slot, so from the second call on the array already holds pointers.
|
|
18
|
-
if ( typeof value === 'string' && value.startsWith( `var(${ role }` ) ) {
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// A slot the consumer left empty keeps the catalog's own pointer, so a CSS declaration of it still reaches the palette.
|
|
23
|
-
if ( typeof value !== 'string' || value === '' ) {
|
|
24
|
-
return SERIES_PALETTE_POINTERS[ index ];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return `var(${ role }, ${ value })`;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// Restores each overridden role's mapped theme field to the catalog pointer already written for it — `defaultTheme` for the ordinary roles, `series-palette.ts` for the palette slots — so the value comes from the one place that declares it rather than a second, hand-copied literal.
|
|
31
|
-
//
|
|
32
|
-
// Every series slot restores the whole `colors` array, not just its own entry. `mergeThemes` replaces arrays outright, so a consumer passing two colors would otherwise leave a two-entry palette and put slots 3 to 5 out of reach of a CSS declaration that sets them.
|
|
33
|
-
const CATALOG_RESTORE_FOR_ROLE: Record<
|
|
34
|
-
string,
|
|
35
|
-
( theme: CompleteChartTheme ) => Partial< CompleteChartTheme >
|
|
36
|
-
> = {
|
|
37
|
-
// Each of the five entries rebuilds the whole array, so a consumer overriding every slot runs this five times. That is deliberate rather than merely tolerated: `seriesPointer` is idempotent, so every pass after the first is a no-op, and five allocations of a five-element array is not worth a special case in a map that is otherwise one entry per role.
|
|
38
|
-
...Object.fromEntries(
|
|
39
|
-
Array.from( { length: SERIES_SLOT_COUNT }, ( _, index ) => [
|
|
40
|
-
seriesRole( index + 1 ),
|
|
41
|
-
( theme: CompleteChartTheme ) => ( {
|
|
42
|
-
colors: SERIES_PALETTE_POINTERS.map( ( _pointer, slot ) =>
|
|
43
|
-
seriesPointer( slot, theme.colors?.[ slot ] )
|
|
44
|
-
),
|
|
45
|
-
} ),
|
|
46
|
-
] )
|
|
47
|
-
),
|
|
48
|
-
'--a8c-charts-color-background': () => ( { backgroundColor: defaultTheme.backgroundColor } ),
|
|
49
|
-
'--a8c-charts-color-grid': theme => ( {
|
|
50
|
-
gridStyles: { ...theme.gridStyles, stroke: defaultTheme.gridStyles.stroke },
|
|
51
|
-
} ),
|
|
52
|
-
'--a8c-charts-color-axis': theme => ( {
|
|
53
|
-
xAxisLineStyles: { ...theme.xAxisLineStyles, stroke: defaultTheme.xAxisLineStyles.stroke },
|
|
54
|
-
} ),
|
|
55
|
-
'--a8c-charts-color-tick': theme => ( {
|
|
56
|
-
xTickLineStyles: { ...theme.xTickLineStyles, stroke: defaultTheme.xTickLineStyles.stroke },
|
|
57
|
-
} ),
|
|
58
|
-
'--a8c-charts-color-label-axis': theme => ( {
|
|
59
|
-
svgLabelSmall: { ...theme.svgLabelSmall, fill: defaultTheme.svgLabelSmall.fill },
|
|
60
|
-
} ),
|
|
61
|
-
'--a8c-charts-color-label-background': () => ( {
|
|
62
|
-
labelBackgroundColor: defaultTheme.labelBackgroundColor,
|
|
63
|
-
} ),
|
|
64
|
-
'--a8c-charts-color-label-on-fill': () => ( { labelTextColor: defaultTheme.labelTextColor } ),
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Restores the mapped theme fields of `overriddenRoles` to their catalog pointer, so the theme-layer variable `themeOverrideVars` writes on the provider wrapper is the only carrier for an overridden role — CSS and the JS bridge (`useXYChartTheme`) then resolve it through the same cascade instead of visx reading a baked literal that can disagree with a closer CSS override.
|
|
69
|
-
*
|
|
70
|
-
* This covers roles `themeOverrideVars` deliberately left unpublished as well as the ones it published. A value that reads its own role is not publishable, but leaving visx the consumer's literal is what the whole mechanism exists to prevent: `theme={ { gridStyles: { stroke: 'var(--brand, var(--a8c-charts-color-grid, red))' } } }` would have CSS paint the catalog default while visx painted `--brand`.
|
|
71
|
-
*
|
|
72
|
-
* Every field outside the mapped roles is left exactly as `merged` provided it: those aren't published as theme-layer vars, so rewriting them would erase a consumer's override with no replacement carrier.
|
|
73
|
-
*
|
|
74
|
-
* @param merged - The consumer theme merged onto `defaultTheme` (`mergeThemes` output).
|
|
75
|
-
* @param overriddenRoles - The catalog roles the consumer overrode, from `themeOverrideVars`.
|
|
76
|
-
* @return A new theme object; `merged` is not mutated.
|
|
77
|
-
*/
|
|
78
|
-
export const withCatalogPointers = (
|
|
79
|
-
merged: CompleteChartTheme,
|
|
80
|
-
overriddenRoles: readonly string[]
|
|
81
|
-
): CompleteChartTheme => {
|
|
82
|
-
let result = merged;
|
|
83
|
-
|
|
84
|
-
for ( const role of overriddenRoles ) {
|
|
85
|
-
const restore = CATALOG_RESTORE_FOR_ROLE[ role ];
|
|
86
|
-
|
|
87
|
-
if ( restore ) {
|
|
88
|
-
result = { ...result, ...restore( result ) };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return result;
|
|
93
|
-
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import { GlobalChartsProvider } from '../global-charts-provider';
|
|
3
|
-
|
|
4
|
-
// The stock defaultTheme's mapped fields are themselves self-referential pointers (e.g. `gridStyles.stroke: 'var(--a8c-charts-color-grid, #dbdbdb)'`), so themeOverrideVars' own self-reference guard already filters them out no matter which theme object the provider passes in. That makes "reads theme, not providerTheme" unfalsifiable against the real default theme. Mocking the default here to a plain, non-pointer value removes that overlap, so this test only passes when the provider reads the raw consumer `theme` prop rather than the merged `providerTheme`.
|
|
5
|
-
jest.mock( '../themes', () => {
|
|
6
|
-
const actual = jest.requireActual( '../themes' );
|
|
7
|
-
return {
|
|
8
|
-
...actual,
|
|
9
|
-
defaultTheme: {
|
|
10
|
-
...actual.defaultTheme,
|
|
11
|
-
gridStyles: { ...actual.defaultTheme.gridStyles, stroke: '#dbdbdb' },
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
} );
|
|
15
|
-
|
|
16
|
-
describe( 'GlobalChartsProvider wrapper style (discriminating guard test)', () => {
|
|
17
|
-
it( 'does not emit an instance override for a role the consumer left unset, even though the merged theme carries a concrete default value', () => {
|
|
18
|
-
render(
|
|
19
|
-
<GlobalChartsProvider theme={ { tickLength: 8 } }>
|
|
20
|
-
<div>child</div>
|
|
21
|
-
</GlobalChartsProvider>
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
const wrapper = screen.getByTestId( 'charts-scope' );
|
|
25
|
-
|
|
26
|
-
expect( wrapper.style.getPropertyValue( '--a8c-charts-color-grid-theme' ) ).toBe( '' );
|
|
27
|
-
expect( wrapper.style.getPropertyValue( '--a8c-charts-color-grid' ) ).toBe( '' );
|
|
28
|
-
} );
|
|
29
|
-
} );
|