@automattic/charts 4.10.0 → 4.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/TOKENS.md +7 -3
  3. package/dist/index.cjs +337 -708
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +52 -3
  6. package/dist/index.d.cts +39 -39
  7. package/dist/index.d.ts +39 -39
  8. package/dist/index.js +308 -660
  9. package/dist/index.js.map +1 -1
  10. package/dist/visx/group/index.js +1 -2
  11. package/dist/visx/legend/index.js +1 -2
  12. package/dist/visx/text/index.js +1 -2
  13. package/package.json +13 -12
  14. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +5 -0
  15. package/src/charts/geo-chart/geo-chart.module.scss +27 -0
  16. package/src/charts/geo-chart/geo-chart.tsx +68 -43
  17. package/src/charts/geo-chart/private/use-tooltip-position.ts +106 -0
  18. package/src/charts/geo-chart/test/geo-chart.test.tsx +68 -42
  19. package/src/charts/geo-chart/test/use-tooltip-position.test.ts +43 -0
  20. package/src/charts/heatmap-chart/heatmap-chart.module.scss +3 -3
  21. package/src/charts/heatmap-chart/private/heatmap-scale.ts +48 -43
  22. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +5 -5
  23. package/src/charts/heatmap-chart/test/heatmap-scale.test.ts +55 -27
  24. package/src/charts/heatmap-chart/types.ts +1 -1
  25. package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +8 -5
  26. package/src/components/legend/private/base-legend.module.scss +5 -0
  27. package/src/components/legend/private/base-legend.tsx +8 -2
  28. package/src/components/tooltip/_surface.scss +19 -0
  29. package/src/components/tooltip/private/bounded-tooltip.tsx +2 -2
  30. package/src/components/tooltip/tooltip-box.module.scss +3 -17
  31. package/src/styles/chart-scope.scss +6 -4
  32. package/src/styles/test/chart-scope.test.ts +6 -2
@@ -1,9 +1,14 @@
1
- import { MIN_BACKGROUND_CONTRAST } from '../../../providers/chart-context/private/palette-generator';
2
1
  import { contrastRatio } from '../../../providers/chart-context/private/perceptual-color';
3
2
  import { mixHexColors, relativeLuminance } from '../../../utils/color-utils';
4
3
 
4
+ /** Contrast the lowest step aims for against the background and the empty cell. */
5
+ export const HEATMAP_LOW_CONTRAST = 2.5;
6
+
7
+ /** Contrast every step keeps against the background, and the lowest against the highest. */
8
+ export const HEATMAP_STEP_CONTRAST = 2;
9
+
5
10
  /** Contrast the highest step aims for against the background, so the steps stay apart. */
6
- export const HEATMAP_HIGH_CONTRAST = 9;
11
+ export const HEATMAP_HIGH_CONTRAST = 6;
7
12
 
8
13
  const STEPS = 200;
9
14
 
@@ -12,20 +17,13 @@ export type HeatmapScale = {
12
17
  high: string;
13
18
  };
14
19
 
15
- // Measured on the rounded hex the browser paints, so an endpoint never lands under its threshold.
16
- const firstMeeting = ( from: string, to: string, meets: ( color: string ) => boolean ) => {
17
- for ( let step = 0; step <= STEPS; step++ ) {
18
- const color = mixHexColors( from, to, step / STEPS );
19
- if ( meets( color ) ) {
20
- return color;
21
- }
22
- }
23
- return null;
24
- };
20
+ // Mixed to the rounded hex the browser paints, so an endpoint never lands under its threshold.
21
+ const ramp = ( from: string, to: string ) =>
22
+ Array.from( { length: STEPS + 1 }, ( _, step ) => mixHexColors( from, to, step / STEPS ) );
25
23
 
26
24
  /**
27
- * The two ends of the heatmap fill scale: 3:1 against the background and the empty cell at the
28
- * low end, toward 9:1 against the background at the high end.
25
+ * The two ends of the heatmap fill scale: toward 2.5:1 against the background and the empty cell at
26
+ * the low end, never under 2:1 against the background or the high end, and toward 6:1 at the high end.
29
27
  *
30
28
  * @param primary - The resolved primary color, as six-digit hex.
31
29
  * @param background - The resolved chart background, as six-digit hex.
@@ -37,38 +35,45 @@ export const getHeatmapScale = (
37
35
  background: string,
38
36
  emptyCell?: string | null
39
37
  ): HeatmapScale => {
40
- const primaryStandsOut = contrastRatio( primary, background ) >= MIN_BACKGROUND_CONTRAST;
41
- // The scale deepens to black or white on the primary's own side of the background, so it never
42
- // crosses it. A primary too close to the background takes whichever contrasts more.
43
- const towardWhite = primaryStandsOut
44
- ? relativeLuminance( primary ) > relativeLuminance( background )
45
- : contrastRatio( '#ffffff', background ) > contrastRatio( '#000000', background );
46
- const extreme = towardWhite ? '#ffffff' : '#000000';
38
+ const backgroundLuminance = relativeLuminance( background );
39
+ const ownSide = relativeLuminance( primary ) > backgroundLuminance ? '#ffffff' : '#000000';
40
+ const otherSide = ownSide === '#ffffff' ? '#000000' : '#ffffff';
41
+ // The scale deepens on the primary's own side of the background, unless only the other side
42
+ // reaches the high end or contrasts more; it never crosses the background either way.
43
+ const extreme =
44
+ contrastRatio( ownSide, background ) >= HEATMAP_HIGH_CONTRAST ||
45
+ contrastRatio( ownSide, background ) >= contrastRatio( otherSide, background )
46
+ ? ownSide
47
+ : otherSide;
48
+ const onExtremeSide = ( color: string ) =>
49
+ extreme === '#ffffff'
50
+ ? relativeLuminance( color ) > backgroundLuminance
51
+ : relativeLuminance( color ) < backgroundLuminance;
47
52
 
48
- const lowAgainst = ( references: string[] ) => {
49
- const meets = ( color: string ) =>
50
- references.every( reference => contrastRatio( color, reference ) >= MIN_BACKGROUND_CONTRAST );
51
- return (
52
- ( primaryStandsOut ? firstMeeting( background, primary, meets ) : null ) ??
53
- firstMeeting( primary, extreme, meets )
54
- );
55
- };
56
- // Only `high` can miss: black or white tops out near 4.58:1 on a mid-tone background.
53
+ const deepening = ramp( primary, extreme );
54
+ // Only `high` can miss its target: black or white tops out near 4.58:1 on a mid-tone background.
57
55
  const high =
58
- firstMeeting(
59
- primary,
60
- extreme,
61
- color => contrastRatio( color, background ) >= HEATMAP_HIGH_CONTRAST
62
- ) ?? extreme;
56
+ deepening.find( color => contrastRatio( color, background ) >= HEATMAP_HIGH_CONTRAST ) ??
57
+ extreme;
63
58
 
64
- // The lowest step sits beside empty cells, so it stands apart from them too, unless an
65
- // empty-cell color far from the background would push it past the high end.
66
- const againstEmptyCell = emptyCell ? lowAgainst( [ background, emptyCell ] ) : null;
59
+ const path = [ ...ramp( background, primary ), ...deepening ];
60
+ // Never empty: `high` sits at least 4.58:1 out.
61
+ const spread = path
62
+ .slice( 0, path.indexOf( high ) + 1 )
63
+ .filter(
64
+ color =>
65
+ onExtremeSide( color ) &&
66
+ contrastRatio( color, background ) >= HEATMAP_STEP_CONTRAST &&
67
+ contrastRatio( color, high ) >= HEATMAP_STEP_CONTRAST
68
+ );
69
+ const surroundings = emptyCell ? [ background, emptyCell ] : [ background ];
70
+ const weakest = ( color: string ) =>
71
+ Math.min( ...surroundings.map( other => contrastRatio( color, other ) ) );
72
+ // Short of 2.5:1 the low end settles for 2:1, then for the step farthest from its nearest neighbor.
67
73
  const low =
68
- againstEmptyCell &&
69
- contrastRatio( againstEmptyCell, background ) <= contrastRatio( high, background )
70
- ? againstEmptyCell
71
- : lowAgainst( [ background ] );
74
+ spread.find( color => weakest( color ) >= HEATMAP_LOW_CONTRAST ) ??
75
+ spread.find( color => weakest( color ) >= HEATMAP_STEP_CONTRAST ) ??
76
+ spread.reduce( ( best, color ) => ( weakest( color ) > weakest( best ) ? color : best ) );
72
77
 
73
- return { low: low ?? extreme, high };
78
+ return { low, high };
74
79
  };
@@ -486,8 +486,8 @@ describe( 'HeatmapChart', () => {
486
486
  );
487
487
  const grid = screen.getByRole( 'grid', { name: /heatmap/i } );
488
488
 
489
- expect( grid.style.getPropertyValue( '--a8c-charts-color-heatmap-low' ) ).toBe( '#6c83ef' );
490
- expect( grid.style.getPropertyValue( '--a8c-charts-color-heatmap-high' ) ).toBe( '#283ea5' );
489
+ expect( grid.style.getPropertyValue( '--a8c-charts-color-heatmap-low' ) ).toBe( '#7f93f1' );
490
+ expect( grid.style.getPropertyValue( '--a8c-charts-color-heatmap-high' ) ).toBe( '#3554df' );
491
491
 
492
492
  document.body.removeChild( scope );
493
493
  } );
@@ -586,7 +586,7 @@ const mockRects = () =>
586
586
  describe( 'HeatmapChart value text contrast', () => {
587
587
  // The low end, a mid-tone, and the high end of the scale.
588
588
  const scale: HeatmapColumn[] = [
589
- { label: 'W1', data: [ { value: 1 }, { value: 30 }, { value: 100 } ] },
589
+ { label: 'W1', data: [ { value: 1 }, { value: 60 }, { value: 100 } ] },
590
590
  ];
591
591
 
592
592
  let injectedStyle: HTMLStyleElement | null = null;
@@ -619,7 +619,7 @@ describe( 'HeatmapChart value text contrast', () => {
619
619
  test( 'falls back to black on a mid-tone cell where neither role reaches AA', () => {
620
620
  renderScale();
621
621
 
622
- expect( screen.getByText( '30' ) ).toHaveClass( 'heatmap-chart__cell-value--black' );
622
+ expect( screen.getByText( '60' ) ).toHaveClass( 'heatmap-chart__cell-value--black' );
623
623
  } );
624
624
 
625
625
  test( 'reads the label roles once data arrives after an empty first render', () => {
@@ -691,7 +691,7 @@ describe( 'HeatmapChart value text contrast', () => {
691
691
 
692
692
  renderScale( 'pinned-heatmap' );
693
693
 
694
- [ '1', '30', '100' ].forEach( value =>
694
+ [ '1', '60', '100' ].forEach( value =>
695
695
  expect( screen.getByText( value ) ).toHaveClass( 'heatmap-chart__cell-value', {
696
696
  exact: true,
697
697
  } )
@@ -1,9 +1,13 @@
1
- import { MIN_BACKGROUND_CONTRAST } from '../../../providers/chart-context/private/palette-generator';
2
1
  import { contrastRatio } from '../../../providers/chart-context/private/perceptual-color';
3
2
  import { mixHexColors } from '../../../utils/color-utils';
4
- import { getHeatmapScale, HEATMAP_HIGH_CONTRAST } from '../private/heatmap-scale';
3
+ import {
4
+ getHeatmapScale,
5
+ HEATMAP_HIGH_CONTRAST,
6
+ HEATMAP_LOW_CONTRAST,
7
+ HEATMAP_STEP_CONTRAST,
8
+ } from '../private/heatmap-scale';
5
9
 
6
- // The wp-admin scheme colors, then the pre-7.0 ones, which include a sunrise below 3:1 on white.
10
+ // The wp-admin scheme colors, then the pre-7.0 ones.
7
11
  const PRIMARIES = [
8
12
  '#007cba',
9
13
  '#3858e9',
@@ -22,17 +26,17 @@ describe.each( [
22
26
  [ 'dark', '#242424', '#272727' ],
23
27
  ] )( 'getHeatmapScale on a %s background', ( _mode, background, emptyCell ) => {
24
28
  it.each( PRIMARIES )(
25
- 'keeps the lowest step of %s at 3:1 against the background and empty cell, no further',
29
+ 'keeps the lowest step of %s at 2.5:1 against the background and empty cell, no further',
26
30
  primary => {
27
31
  const { low } = getHeatmapScale( primary, background, emptyCell );
28
32
  const contrasts = [ contrastRatio( low, background ), contrastRatio( low, emptyCell ) ];
29
33
 
30
- expect( Math.min( ...contrasts ) ).toBeGreaterThanOrEqual( MIN_BACKGROUND_CONTRAST );
31
- expect( Math.min( ...contrasts ) ).toBeLessThan( MIN_BACKGROUND_CONTRAST + 0.1 );
34
+ expect( Math.min( ...contrasts ) ).toBeGreaterThanOrEqual( HEATMAP_LOW_CONTRAST );
35
+ expect( Math.min( ...contrasts ) ).toBeLessThan( HEATMAP_LOW_CONTRAST + 0.1 );
32
36
  }
33
37
  );
34
38
 
35
- it.each( PRIMARIES )( 'runs the scale of %s out to 9:1', primary => {
39
+ it.each( PRIMARIES )( 'runs the scale of %s out to 6:1', primary => {
36
40
  const { high } = getHeatmapScale( primary, background );
37
41
 
38
42
  expect( contrastRatio( high, background ) ).toBeGreaterThanOrEqual( HEATMAP_HIGH_CONTRAST );
@@ -40,39 +44,63 @@ describe.each( [
40
44
  } );
41
45
 
42
46
  describe( 'getHeatmapScale', () => {
43
- it( 'keeps a primary already past 9:1 as the high end', () => {
47
+ it( 'keeps a primary already past 6:1 as the high end', () => {
44
48
  expect( getHeatmapScale( '#1d2327', '#ffffff' ).high ).toBe( '#1d2327' );
45
49
  } );
46
50
 
47
51
  it.each( [
48
- [ '#ffffff', '#808080' ],
49
- [ '#ffd000', '#767676' ],
50
- [ '#3858e9', '#808080' ],
51
- [ '#1e1e1e', '#999999' ],
52
- ] )( 'keeps every step of %s at 3:1 on a mid-gray %s', ( primary, background ) => {
53
- const { low, high } = getHeatmapScale( primary, background );
52
+ [ '#ffffff', '#808080', null ],
53
+ [ '#ffd000', '#767676', null ],
54
+ [ '#3858e9', '#808080', null ],
55
+ [ '#1e1e1e', '#999999', null ],
56
+ [ '#ffd000', '#8a8a8a', null ],
57
+ [ '#ffd000', '#8a8a8a', '#cccccc' ],
58
+ [ '#1d2327', '#555555', '#272727' ],
59
+ ] )(
60
+ 'keeps every step of %s at 2:1 on a mid-gray %s with a %s empty cell, ends apart',
61
+ ( primary, background, emptyCell ) => {
62
+ const { low, high } = getHeatmapScale( primary, background, emptyCell );
54
63
 
55
- for ( let step = 0; step <= 20; step++ ) {
56
- expect(
57
- contrastRatio( mixHexColors( low, high, step / 20 ), background )
58
- ).toBeGreaterThanOrEqual( MIN_BACKGROUND_CONTRAST );
64
+ expect( contrastRatio( low, high ) ).toBeGreaterThanOrEqual( HEATMAP_STEP_CONTRAST );
65
+ for ( let step = 0; step <= 20; step++ ) {
66
+ expect(
67
+ contrastRatio( mixHexColors( low, high, step / 20 ), background )
68
+ ).toBeGreaterThanOrEqual( HEATMAP_STEP_CONTRAST );
69
+ }
59
70
  }
60
- } );
71
+ );
61
72
 
62
73
  it.each( [
63
- [ 'no step clears it', '#ffffff', '#555555' ],
64
- [ 'clearing it would pass the high end', '#ffffff', '#767676' ],
65
- [ 'clearing it would pass the high end', '#1e1e1e', '#6b6b6b' ],
74
+ [ '#ffffff', '#cccccc' ],
75
+ [ '#ffffff', '#959595' ],
76
+ [ '#1e1e1e', '#6b6b6b' ],
66
77
  ] )(
67
- 'measures against the background alone when %s (%s, %s)',
68
- ( _case, background, emptyCell ) => {
69
- expect( getHeatmapScale( '#3858e9', background, emptyCell ) ).toEqual(
70
- getHeatmapScale( '#3858e9', background )
78
+ 'moves the low end on %s farther from a %s empty cell than the background alone would',
79
+ ( background, emptyCell ) => {
80
+ const { low } = getHeatmapScale( '#3858e9', background, emptyCell );
81
+ const backgroundOnly = getHeatmapScale( '#3858e9', background ).low;
82
+
83
+ expect( contrastRatio( low, emptyCell ) ).toBeGreaterThan(
84
+ contrastRatio( backgroundOnly, emptyCell )
71
85
  );
72
86
  }
73
87
  );
74
88
 
75
- it( 'ends on black or white when the background leaves no room for 9:1', () => {
89
+ it.each( [
90
+ [ '#ffffff', '#959595' ],
91
+ [ '#ffffff', '#cccccc' ],
92
+ [ '#1e1e1e', '#555555' ],
93
+ ] )(
94
+ 'keeps the ends apart and in order on %s with a %s empty cell',
95
+ ( background, emptyCell ) => {
96
+ const { low, high } = getHeatmapScale( '#3858e9', background, emptyCell );
97
+
98
+ expect( contrastRatio( low, high ) ).toBeGreaterThanOrEqual( HEATMAP_STEP_CONTRAST );
99
+ expect( contrastRatio( low, background ) ).toBeLessThan( contrastRatio( high, background ) );
100
+ }
101
+ );
102
+
103
+ it( 'ends on black or white when the background leaves no room for 6:1', () => {
76
104
  expect( getHeatmapScale( '#3858e9', '#808080' ).high ).toBe( '#000000' );
77
105
  } );
78
106
  } );
@@ -102,7 +102,7 @@ export interface HeatmapChartProps extends Omit<
102
102
  /** Floor a cell's height (px) in non-compact mode; see `minCellWidth`. */
103
103
  minCellHeight?: number;
104
104
  /**
105
- * Color the cell scale is built from, deepened at the highest value toward 9:1
105
+ * Color the cell scale is built from, deepened at the highest value toward 6:1
106
106
  * against the background. Defaults to the first series palette slot,
107
107
  * `--a8c-charts-color-series-1`.
108
108
  */
@@ -747,15 +747,18 @@ describe( 'LeaderboardChart', () => {
747
747
 
748
748
  describe( 'grid gaps', () => {
749
749
  // eslint-disable-next-line testing-library/no-node-access -- The grid is rendered by `Grid` from @wordpress/components, which takes no test id.
750
- const grid = () => document.querySelector( '[data-leaderboard-grid]' );
750
+ const grid = () => document.querySelector< HTMLElement >( '[data-leaderboard-grid]' );
751
751
 
752
752
  it( 'hands the catalog role to the grid', () => {
753
753
  render( <LeaderboardChart data={ mockData } /> );
754
754
 
755
- expect( grid() ).toHaveStyle( {
756
- gridRowGap: 'var(--a8c-charts-dimension-leaderboard-row-gap, 12px)',
757
- gridColumnGap: 'var(--a8c-charts-dimension-leaderboard-column-gap, 4px)',
758
- } );
755
+ // Grid writes its gaps into custom properties and reads them from a stylesheet.
756
+ expect( grid().style.getPropertyValue( '--wp-components-grid-row-gap' ) ).toBe(
757
+ 'var(--a8c-charts-dimension-leaderboard-row-gap, 12px)'
758
+ );
759
+ expect( grid().style.getPropertyValue( '--wp-components-grid-column-gap' ) ).toBe(
760
+ 'var(--a8c-charts-dimension-leaderboard-column-gap, 4px)'
761
+ );
759
762
  } );
760
763
  } );
761
764
  } );
@@ -1,5 +1,10 @@
1
1
  .legend {
2
2
  align-self: stretch;
3
+
4
+ &--horizontal {
5
+ column-gap: var(--a8c-charts-dimension-legend-column-gap);
6
+ row-gap: var(--a8c-charts-dimension-legend-row-gap);
7
+ }
3
8
  }
4
9
 
5
10
  .legend-item {
@@ -257,13 +257,19 @@ export const BaseLegend: ForwardRefExoticComponent<
257
257
  <Stack
258
258
  ref={ ref }
259
259
  direction={ orientation === 'vertical' ? 'column' : 'row' }
260
- gap={ orientation === 'vertical' ? 'sm' : 'lg' }
260
+ // Stack's gap is inline; horizontal gaps live in CSS so consumers can override them.
261
+ gap={ orientation === 'vertical' ? 'sm' : undefined }
261
262
  align={ orientation === 'vertical' ? flexAlignment : undefined }
262
263
  justify={ orientation === 'horizontal' ? flexAlignment : undefined }
263
264
  wrap={ orientation === 'horizontal' ? 'wrap' : undefined }
264
265
  role={ interactive ? undefined : 'list' }
265
266
  data-testid={ `legend-${ orientation }` }
266
- className={ clsx( standaloneScopeClass, styles.legend, className ) }
267
+ className={ clsx(
268
+ standaloneScopeClass,
269
+ styles.legend,
270
+ orientation === 'horizontal' && styles[ 'legend--horizontal' ],
271
+ className
272
+ ) }
267
273
  style={ theme.legend?.containerStyles }
268
274
  >
269
275
  { labels.map( ( label, i ) => {
@@ -0,0 +1,19 @@
1
+ // The chart tooltip box: the look every chart tooltip shares.
2
+ @mixin surface {
3
+ padding: var(--wpds-dimension-padding-xs) var(--wpds-dimension-padding-sm);
4
+ background-color: var(--a8c-charts-color-surface);
5
+ color: var(--a8c-charts-color-label);
6
+ border-radius: var(--wpds-border-radius-md);
7
+ font-family: var(--wpds-typography-font-family-body);
8
+ font-size: var(--wpds-typography-font-size-sm);
9
+ line-height: 1.4;
10
+ box-shadow: var(--a8c-charts-elevation-sm, 0 1px 2px 0 #0000000d, 0 2px 3px 0 #0000000a, 0 6px 6px 0 #00000008, 0 8px 8px 0 #00000005);
11
+ pointer-events: none;
12
+ // Beats a host `max-width` on `.visx-tooltip`, even an `!important`
13
+ // one: WordPress.com's Help Center caps it at 150px.
14
+ min-width: fit-content;
15
+
16
+ @media (forced-colors: active) {
17
+ border: var(--wpds-border-width-xs) solid CanvasText;
18
+ }
19
+ }
@@ -17,7 +17,7 @@ export type BoundedTooltipProps = Omit< TooltipBoxProps, 'children' > & {
17
17
  placement?: TooltipPlacement;
18
18
  };
19
19
 
20
- const DEFAULT_OFFSET = 10;
20
+ export const DEFAULT_OFFSET = 10;
21
21
  // Above anything else layered inside a chart, such as the zoom reset button (2).
22
22
  // Chart wrappers isolate their stacking context, so it never reaches page chrome.
23
23
  export const TOOLTIP_Z_INDEX = 3;
@@ -43,7 +43,7 @@ const findLayoutParent = ( node: Element ): Element | null => {
43
43
  // The first ancestor that cuts its overflow off. The box may leave the chart
44
44
  // wrapper but never this element, which is what a body-level portal used to
45
45
  // guarantee by never being inside one.
46
- const findClippingAncestor = ( wrapper: Element ): Element | null => {
46
+ export const findClippingAncestor = ( wrapper: Element ): Element | null => {
47
47
  let element = wrapper.parentElement;
48
48
  while ( element && element !== document.body ) {
49
49
  if ( isClipping( element ) ) {
@@ -1,21 +1,7 @@
1
1
  // `.surface` is the look alone, for a chart that positions the box itself.
2
- // The box carries the tooltip scope class inside TooltipTheme, so these roles
3
- // resolve for the dark tooltip theme, as in @wordpress/ui's Tooltip popup.
2
+ @use "surface";
3
+
4
4
  .surface {
5
- padding: var(--wpds-dimension-padding-xs) var(--wpds-dimension-padding-sm);
6
- background-color: var(--a8c-charts-color-surface);
7
- color: var(--a8c-charts-color-label);
8
- border-radius: var(--wpds-border-radius-md);
9
- font-family: var(--wpds-typography-font-family-body);
10
- font-size: var(--wpds-typography-font-size-sm);
11
- line-height: 1.4;
12
- box-shadow: var(--a8c-charts-elevation-sm, 0 1px 2px 0 #0000000d, 0 2px 3px 0 #0000000a, 0 6px 6px 0 #00000008, 0 8px 8px 0 #00000005);
13
- pointer-events: none;
14
- // Beats a host `max-width` on `.visx-tooltip`, even an `!important`
15
- // one: WordPress.com's Help Center caps it at 150px.
16
- min-width: fit-content;
17
5
 
18
- @media (forced-colors: active) {
19
- border: var(--wpds-border-width-xs) solid CanvasText;
20
- }
6
+ @include surface.surface;
21
7
  }
@@ -10,9 +10,9 @@
10
10
  // the same element wins. Chart roots deliberately do NOT re-declare these: an
11
11
  // element's own declaration beats an inherited one, so re-declaring lower down
12
12
  // would shadow a consumer override set inside the provider tree.
13
- // The tooltip box re-declares the catalog under its own class, so a rule on
14
- // .a8c-charts-scope does not land on the dark box.
15
- :where(.a8c-charts-scope, .a8c-charts-tooltip-scope) {
13
+ // The tooltip boxes (ours and Google's GeoChart one) re-declare the catalog on
14
+ // themselves, so a rule on .a8c-charts-scope does not land on the dark box.
15
+ :where(.a8c-charts-scope, .a8c-charts-tooltip-scope, .geo-chart .google-visualization-tooltip) {
16
16
  // The series palette. GlobalChartsProvider resolves these five slots at its
17
17
  // wrapper and seeds ColorCache with whatever resolves; a slot that resolves
18
18
  // to nothing is skipped, so setting only slots 1 and 3 gives a two-color
@@ -87,11 +87,13 @@
87
87
 
88
88
  --a8c-charts-dimension-leaderboard-row-gap: var(--wpds-dimension-gap-md);
89
89
  --a8c-charts-dimension-leaderboard-column-gap: var(--wpds-dimension-gap-xs);
90
+ --a8c-charts-dimension-legend-column-gap: var(--wpds-dimension-gap-lg);
91
+ --a8c-charts-dimension-legend-row-gap: var(--wpds-dimension-gap-sm);
90
92
  }
91
93
 
92
94
  @media (forced-colors: active) {
93
95
 
94
- :where(.a8c-charts-scope, .a8c-charts-tooltip-scope) {
96
+ :where(.a8c-charts-scope, .a8c-charts-tooltip-scope, .geo-chart .google-visualization-tooltip) {
95
97
  --a8c-charts-color-label-axis: CanvasText;
96
98
  --a8c-charts-color-grid: GrayText;
97
99
  --a8c-charts-color-axis-x: GrayText;
@@ -127,7 +127,9 @@ describe( 'chart scope catalog', () => {
127
127
  expect( mediaRules[ 0 ].cssRules ).toHaveLength( 1 );
128
128
 
129
129
  const rule = mediaRules[ 0 ].cssRules[ 0 ] as CSSStyleRule;
130
- expect( rule.selectorText ).toBe( ':where(.a8c-charts-scope, .a8c-charts-tooltip-scope)' );
130
+ expect( rule.selectorText ).toBe(
131
+ ':where(.a8c-charts-scope, .a8c-charts-tooltip-scope, .geo-chart .google-visualization-tooltip)'
132
+ );
131
133
  expect(
132
134
  Object.fromEntries(
133
135
  Array.from( rule.style ).map( property => [
@@ -172,7 +174,9 @@ describe( 'chart scope catalog', () => {
172
174
  } );
173
175
 
174
176
  it( 'scopes the catalog to the chart and tooltip scope classes rather than :root', () => {
175
- expect( stylesheet ).toMatch( /:where\(\.a8c-charts-scope, \.a8c-charts-tooltip-scope\)\s*{/ );
177
+ expect( stylesheet ).toMatch(
178
+ /:where\(\.a8c-charts-scope, \.a8c-charts-tooltip-scope, \.geo-chart \.google-visualization-tooltip\)\s*{/
179
+ );
176
180
  expect( stylesheet ).not.toMatch( /(^|\s):root\s*{/ );
177
181
  } );
178
182