@automattic/charts 1.9.0 → 1.10.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 (39) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/index.cjs +5661 -5466
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.css +40 -28
  5. package/dist/index.d.cts +31 -7
  6. package/dist/index.d.ts +32 -8
  7. package/dist/index.js +1239 -1046
  8. package/dist/index.js.map +1 -1
  9. package/package.json +5 -5
  10. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +7 -7
  11. package/src/charts/geo-chart/geo-chart.tsx +148 -3
  12. package/src/charts/geo-chart/index.ts +1 -1
  13. package/src/charts/geo-chart/test/geo-chart.test.tsx +122 -2
  14. package/src/charts/geo-chart/types.ts +11 -0
  15. package/src/charts/heatmap-chart/heatmap-chart.module.scss +11 -11
  16. package/src/charts/heatmap-chart/heatmap-chart.tsx +37 -36
  17. package/src/charts/heatmap-chart/private/build-calendar-data.ts +17 -1
  18. package/src/charts/heatmap-chart/private/heatmap-context.ts +10 -0
  19. package/src/charts/heatmap-chart/private/heatmap-legend.tsx +3 -2
  20. package/src/charts/heatmap-chart/private/index.ts +2 -0
  21. package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +23 -1
  22. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +10 -3
  23. package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +15 -3
  24. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +65 -37
  25. package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +46 -0
  26. package/src/charts/line-chart/line-chart.module.scss +2 -2
  27. package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +1 -1
  28. package/src/charts/private/x-zoom.module.scss +2 -2
  29. package/src/components/trend-indicator/trend-indicator.module.scss +3 -3
  30. package/src/hooks/test/use-chart-margin.test.tsx +8 -5
  31. package/src/hooks/use-chart-margin.tsx +11 -1
  32. package/src/index.ts +1 -1
  33. package/src/providers/chart-context/global-charts-provider.tsx +7 -0
  34. package/src/providers/chart-context/test/chart-context.test.tsx +40 -0
  35. package/src/providers/chart-context/themes.ts +17 -14
  36. package/src/providers/chart-context/types.ts +3 -0
  37. package/src/types.ts +9 -6
  38. package/src/utils/color-utils.ts +28 -0
  39. package/src/utils/test/color-utils.test.ts +29 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/charts",
3
- "version": "1.9.0",
3
+ "version": "1.10.1",
4
4
  "description": "Display charts within Automattic products.",
5
5
  "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/charts/#readme",
6
6
  "bugs": {
@@ -63,7 +63,7 @@
63
63
  "typecheck": "tsgo --noEmit"
64
64
  },
65
65
  "dependencies": {
66
- "@automattic/number-formatters": "^1.2.5",
66
+ "@automattic/number-formatters": "^1.2.6",
67
67
  "@babel/runtime": "7.29.7",
68
68
  "@react-spring/web": "10.1.1",
69
69
  "@visx/annotation": "^4.0.0",
@@ -83,9 +83,9 @@
83
83
  "@visx/vendor": "^4.0.0",
84
84
  "@visx/xychart": "^4.0.0",
85
85
  "@wordpress/i18n": "^6.0.0",
86
- "@wordpress/icons": "^13.0.0",
87
- "@wordpress/theme": "0.15.1",
88
- "@wordpress/ui": "0.13.0",
86
+ "@wordpress/icons": "^15.0.0",
87
+ "@wordpress/theme": "0.17.0",
88
+ "@wordpress/ui": "0.17.0",
89
89
  "clsx": "2.1.1",
90
90
  "date-fns": "^4.1.0",
91
91
  "deepmerge": "4.3.1",
@@ -12,7 +12,7 @@
12
12
 
13
13
  .main-rate {
14
14
  overflow: hidden;
15
- color: var(--wpds-color-fg-content-neutral, #1e1e1e);
15
+ color: var(--wpds-color-foreground-content-neutral, #1e1e1e);
16
16
  text-overflow: ellipsis;
17
17
  font-size: var(--wpds-typography-font-size-xl, 18px);
18
18
  font-style: normal;
@@ -54,7 +54,7 @@
54
54
  }
55
55
 
56
56
  .step-label {
57
- color: var(--wpds-color-fg-content-neutral-weak, #707070);
57
+ color: var(--wpds-color-foreground-content-neutral-weak, #707070);
58
58
  font-size: var(--wpds-typography-font-size-sm, 12px);
59
59
  font-weight: var(--wpds-typography-font-weight-regular, 400);
60
60
  line-height: var(--wpds-typography-line-height-xs, 16px);
@@ -64,7 +64,7 @@
64
64
  }
65
65
 
66
66
  .step-rate {
67
- color: var(--wpds-color-fg-content-neutral, #1e1e1e);
67
+ color: var(--wpds-color-foreground-content-neutral, #1e1e1e);
68
68
  font-size: var(--wpds-typography-font-size-md, 13px);
69
69
  font-weight: var(--wpds-typography-font-weight-medium, 499);
70
70
  line-height: var(--wpds-typography-line-height-xs, 16px);
@@ -100,7 +100,7 @@
100
100
  }
101
101
 
102
102
  .tooltip-wrapper {
103
- background: var(--wpds-color-bg-surface-neutral-strong, #fff);
103
+ background: var(--wpds-color-background-surface-neutral-strong, #fff);
104
104
 
105
105
  // Override .visx-tooltip inline styles.
106
106
  border-radius: var(--wpds-border-radius-md, 4px) !important;
@@ -110,7 +110,7 @@
110
110
  }
111
111
 
112
112
  .tooltip-title {
113
- color: var(--wpds-color-fg-content-neutral, #1e1e1e);
113
+ color: var(--wpds-color-foreground-content-neutral, #1e1e1e);
114
114
  font-size: var(--wpds-typography-font-size-sm, 12px);
115
115
  font-style: normal;
116
116
  font-weight: var(--wpds-typography-font-weight-regular, 400);
@@ -118,7 +118,7 @@
118
118
  }
119
119
 
120
120
  .tooltip-content {
121
- color: var(--wpds-color-fg-content-neutral, #1e1e1e);
121
+ color: var(--wpds-color-foreground-content-neutral, #1e1e1e);
122
122
  font-size: var(--wpds-typography-font-size-md, 13px);
123
123
  font-style: normal;
124
124
  font-weight: var(--wpds-typography-font-weight-medium, 499);
@@ -127,6 +127,6 @@
127
127
 
128
128
  .empty-state {
129
129
  text-align: center;
130
- color: var(--wpds-color-fg-content-neutral-weak, #707070);
130
+ color: var(--wpds-color-foreground-content-neutral-weak, #707070);
131
131
  font-size: var(--wpds-typography-font-size-lg, 16px);
132
132
  }
@@ -3,8 +3,8 @@
3
3
  */
4
4
  import { __ } from '@wordpress/i18n';
5
5
  import clsx from 'clsx';
6
- import { FC, useContext, useMemo } from 'react';
7
- import { Chart, type GoogleChartPackages } from 'react-google-charts';
6
+ import { FC, useContext, useEffect, useMemo, useRef } from 'react';
7
+ import { Chart, type GoogleChartPackages, type ReactGoogleChartEvent } from 'react-google-charts';
8
8
  /**
9
9
  * Internal dependencies
10
10
  */
@@ -15,7 +15,7 @@ import { sanitizeHtml } from '../../utils/sanitize-html';
15
15
  import { Center } from '../private/center';
16
16
  import { withResponsive } from '../private/with-responsive';
17
17
  import styles from './geo-chart.module.scss';
18
- import { GeoChartProps } from './types';
18
+ import type { GeoChartError, GeoChartProps } from './types';
19
19
 
20
20
  const DEFAULT_FEATURE_FILL_COLOR = '#ffffff';
21
21
  const DEFAULT_BACKGROUND_COLOR = '#ffffff';
@@ -25,6 +25,89 @@ const DEFAULT_BACKGROUND_COLOR = '#ffffff';
25
25
  const GEO_CHART_PACKAGES: GoogleChartPackages[] = [ 'corechart', 'controls', 'geochart' ];
26
26
 
27
27
  type GoogleChartOptions = Record< string, unknown >;
28
+ type GoogleChartErrorPayload = {
29
+ id?: unknown;
30
+ message?: unknown;
31
+ detailedMessage?: unknown;
32
+ options?: unknown;
33
+ };
34
+
35
+ // Google Charts renders draw errors as DOM elements injected into the chart
36
+ // container: a wrapper `<div id="google-visualization-errors-all-N">` holding
37
+ // one `<span id="google-visualization-errors-N">` per error. The span id is the
38
+ // error id accepted by `google.visualization.errors.removeError()`.
39
+ const GOOGLE_CHARTS_ERROR_ID_PREFIX = 'google-visualization-errors-';
40
+ const GOOGLE_CHARTS_ERROR_WRAPPER_INFIX = '-all-';
41
+
42
+ /**
43
+ * Collects Google Charts error elements rendered inside a chart container.
44
+ *
45
+ * @param container - The chart container element to scan.
46
+ * @return Errors found in the container, one per error span.
47
+ */
48
+ function collectRenderedGeoChartErrors(
49
+ container: HTMLElement
50
+ ): Required< Pick< GeoChartError, 'id' | 'message' > >[] {
51
+ const elements = container.querySelectorAll< HTMLElement >(
52
+ `[id^="${ GOOGLE_CHARTS_ERROR_ID_PREFIX }"]`
53
+ );
54
+
55
+ return Array.from( elements )
56
+ .filter( element => ! element.id.includes( GOOGLE_CHARTS_ERROR_WRAPPER_INFIX ) )
57
+ .map( element => ( {
58
+ id: element.id,
59
+ message: element.textContent?.trim() ?? '',
60
+ } ) )
61
+ .filter( error => error.message.length > 0 );
62
+ }
63
+
64
+ /**
65
+ * Whether a node added to the chart container is — or contains — a Google
66
+ * Charts error element. Also matches text appended into an existing error
67
+ * span, in case Google fills the message after inserting the element.
68
+ *
69
+ * @param node - The added DOM node to inspect.
70
+ * @return Whether the node involves a Google Charts error element.
71
+ */
72
+ function involvesGeoChartErrorElement( node: Node ): boolean {
73
+ if ( node.nodeType === Node.TEXT_NODE ) {
74
+ return !! node.parentElement?.id.startsWith( GOOGLE_CHARTS_ERROR_ID_PREFIX );
75
+ }
76
+
77
+ if ( ! ( node instanceof HTMLElement ) ) {
78
+ return false;
79
+ }
80
+
81
+ return (
82
+ node.id.startsWith( GOOGLE_CHARTS_ERROR_ID_PREFIX ) ||
83
+ node.querySelector( `[id^="${ GOOGLE_CHARTS_ERROR_ID_PREFIX }"]` ) !== null
84
+ );
85
+ }
86
+
87
+ /**
88
+ * Normalizes the raw Google Charts error event into the GeoChart error shape.
89
+ *
90
+ * @param eventArgs - Error event payload from react-google-charts.
91
+ * @return Normalized GeoChart error.
92
+ */
93
+ function normalizeGeoChartError( eventArgs: unknown ): GeoChartError {
94
+ const payload = Array.isArray( eventArgs ) ? eventArgs[ 0 ] : eventArgs;
95
+
96
+ if ( ! payload || typeof payload !== 'object' ) {
97
+ return {};
98
+ }
99
+
100
+ const { id, message, detailedMessage, options } = payload as GoogleChartErrorPayload;
101
+
102
+ return {
103
+ ...( typeof id === 'string' && { id } ),
104
+ ...( typeof message === 'string' && { message } ),
105
+ ...( typeof detailedMessage === 'string' && { detailedMessage } ),
106
+ ...( options &&
107
+ typeof options === 'object' &&
108
+ ! Array.isArray( options ) && { options: options as Record< string, unknown > } ),
109
+ };
110
+ }
28
111
 
29
112
  /**
30
113
  * Renders a geographical chart using Google Charts GeoChart to visualize data.
@@ -41,6 +124,7 @@ type GoogleChartOptions = Record< string, unknown >;
41
124
  * @param props.height - Height of the chart in pixels
42
125
  * @param props.region - Region to display ('world', 'US', or ISO 3166-1 alpha-2 code)
43
126
  * @param props.resolution - Resolution level ('countries', 'provinces', or 'metros')
127
+ * @param props.onError - Optional callback for Google Charts errors
44
128
  * @param props.className - Additional CSS class name for the chart container
45
129
  * @param props.renderPlaceholder - Optional render function for the loading placeholder
46
130
  * @return A React component displaying an interactive map with data visualization
@@ -52,6 +136,7 @@ const GeoChartInternal: FC< GeoChartProps > = ( {
52
136
  height,
53
137
  region = 'world',
54
138
  resolution = 'countries',
139
+ onError,
55
140
  renderPlaceholder,
56
141
  } ) => {
57
142
  const {
@@ -61,6 +146,49 @@ const GeoChartInternal: FC< GeoChartProps > = ( {
61
146
  backgroundColor,
62
147
  },
63
148
  } = useGlobalChartsContext();
149
+ const containerRef = useRef< HTMLDivElement >( null );
150
+ const reportedErrorIdsRef = useRef< Set< string > >( new Set() );
151
+
152
+ // The ChartWrapper `error` event does not fire for every draw failure —
153
+ // notably not when GeoChart's asynchronous map-file load fails (e.g.
154
+ // `resolution: 'provinces'` for a country without a provinces map). Those
155
+ // errors only surface as DOM elements Google injects into the container, so
156
+ // watch the container and report them through the same `onError` callback.
157
+ useEffect( () => {
158
+ const container = containerRef.current;
159
+
160
+ if ( ! onError || ! container || typeof MutationObserver === 'undefined' ) {
161
+ return undefined;
162
+ }
163
+
164
+ const reportRenderedErrors = () => {
165
+ for ( const error of collectRenderedGeoChartErrors( container ) ) {
166
+ if ( reportedErrorIdsRef.current.has( error.id ) ) {
167
+ continue;
168
+ }
169
+
170
+ reportedErrorIdsRef.current.add( error.id );
171
+ onError( error );
172
+ }
173
+ };
174
+
175
+ // GeoChart mutates the container heavily while drawing and resizing;
176
+ // only rescan when an added node involves a Google error element.
177
+ const observer = new MutationObserver( records => {
178
+ const hasErrorNodes = records.some( record =>
179
+ Array.from( record.addedNodes ).some( involvesGeoChartErrorElement )
180
+ );
181
+
182
+ if ( hasErrorNodes ) {
183
+ reportRenderedErrors();
184
+ }
185
+ } );
186
+ observer.observe( container, { childList: true, subtree: true } );
187
+ // Report errors already rendered before the observer attached.
188
+ reportRenderedErrors();
189
+
190
+ return () => observer.disconnect();
191
+ }, [ onError ] );
64
192
 
65
193
  // Render loading placeholder
66
194
  const loadingPlaceholder = (
@@ -150,8 +278,24 @@ const GeoChartInternal: FC< GeoChartProps > = ( {
150
278
  ]
151
279
  );
152
280
 
281
+ const chartEvents = useMemo< ReactGoogleChartEvent[] | undefined >( () => {
282
+ if ( ! onError ) {
283
+ return undefined;
284
+ }
285
+
286
+ return [
287
+ {
288
+ eventName: 'error',
289
+ callback: ( { eventArgs } ) => {
290
+ onError( normalizeGeoChartError( eventArgs ) );
291
+ },
292
+ },
293
+ ];
294
+ }, [ onError ] );
295
+
153
296
  return (
154
297
  <Center
298
+ ref={ containerRef }
155
299
  className={ clsx( 'geo-chart', styles.container, className ) }
156
300
  data-testid="geo-chart"
157
301
  style={ { width, height, backgroundColor } }
@@ -163,6 +307,7 @@ const GeoChartInternal: FC< GeoChartProps > = ( {
163
307
  height={ height }
164
308
  data={ sanitizedData.data }
165
309
  options={ options }
310
+ chartEvents={ chartEvents }
166
311
  loader={ loadingPlaceholder }
167
312
  />
168
313
  </Center>
@@ -1,2 +1,2 @@
1
1
  export { default as GeoChart, GeoChartUnresponsive } from './geo-chart';
2
- export type { GeoChartProps, GeoRegion, GeoResolution } from './types';
2
+ export type { GeoChartProps, GeoRegion, GeoResolution, GeoChartError } from './types';
@@ -1,12 +1,16 @@
1
- import { render, screen } from '@testing-library/react';
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import { Chart } from 'react-google-charts';
2
3
  import { GlobalChartsProvider } from '../../../providers';
3
4
  import GeoChart, { GeoChartUnresponsive } from '../geo-chart';
4
5
 
5
6
  // Mock react-google-charts
6
7
  jest.mock( 'react-google-charts', () => ( {
7
- Chart: jest.fn( ( { chartPackages, data, options, width, height } ) => {
8
+ Chart: jest.fn( ( { chartEvents, chartPackages, data, options, width, height } ) => {
8
9
  return (
9
10
  <div data-testid="google-chart-mock" data-width={ width } data-height={ height }>
11
+ <div data-testid="chart-events">
12
+ { JSON.stringify( chartEvents?.map( event => event.eventName ) ?? [] ) }
13
+ </div>
10
14
  <div data-testid="chart-packages">{ JSON.stringify( chartPackages ) }</div>
11
15
  <div data-testid="chart-data">{ JSON.stringify( data ) }</div>
12
16
  <div data-testid="chart-options">{ JSON.stringify( options ) }</div>
@@ -16,6 +20,8 @@ jest.mock( 'react-google-charts', () => ( {
16
20
  } ) );
17
21
 
18
22
  describe( 'GeoChart', () => {
23
+ const ChartMock = Chart as jest.Mock;
24
+
19
25
  const defaultProps = {
20
26
  width: 800,
21
27
  height: 400,
@@ -258,6 +264,120 @@ describe( 'GeoChart', () => {
258
264
  } );
259
265
  } );
260
266
 
267
+ describe( 'Chart Events', () => {
268
+ test( 'does not pass chart events when onError is omitted', () => {
269
+ renderWithTheme();
270
+
271
+ const chartEvents = screen.getByTestId( 'chart-events' );
272
+ const events = JSON.parse( chartEvents.textContent || '[]' );
273
+
274
+ expect( events ).toEqual( [] );
275
+ } );
276
+
277
+ test( 'calls onError when Google Charts emits an error', () => {
278
+ const onError = jest.fn();
279
+ renderWithTheme( { onError } );
280
+
281
+ const chartProps = ChartMock.mock.calls[ ChartMock.mock.calls.length - 1 ]?.[ 0 ];
282
+ const errorEvent = chartProps.chartEvents.find(
283
+ ( event: { eventName: string } ) => event.eventName === 'error'
284
+ );
285
+
286
+ errorEvent.callback( {
287
+ eventArgs: [
288
+ {
289
+ id: 'map-error',
290
+ message: 'Requested map does not exist.',
291
+ detailedMessage: 'The requested map is not available.',
292
+ options: { region: 'SG', resolution: 'provinces' },
293
+ },
294
+ ],
295
+ } );
296
+
297
+ expect( onError ).toHaveBeenCalledWith( {
298
+ id: 'map-error',
299
+ message: 'Requested map does not exist.',
300
+ detailedMessage: 'The requested map is not available.',
301
+ options: { region: 'SG', resolution: 'provinces' },
302
+ } );
303
+ } );
304
+
305
+ test( 'reports error elements Google injects into the chart container', async () => {
306
+ const onError = jest.fn();
307
+ renderWithTheme( { onError } );
308
+
309
+ // Simulate Google Charts rendering a draw error into the container,
310
+ // as happens when an async map-file load fails without firing the
311
+ // ChartWrapper error event.
312
+ const container = screen.getByTestId( 'geo-chart' );
313
+ const errorWrapper = document.createElement( 'div' );
314
+ errorWrapper.id = 'google-visualization-errors-all-1';
315
+ const errorSpan = document.createElement( 'span' );
316
+ errorSpan.id = 'google-visualization-errors-1';
317
+ errorSpan.textContent = 'Requested map does not exist.';
318
+ errorWrapper.appendChild( errorSpan );
319
+ container.appendChild( errorWrapper );
320
+
321
+ await waitFor( () =>
322
+ expect( onError ).toHaveBeenCalledWith( {
323
+ id: 'google-visualization-errors-1',
324
+ message: 'Requested map does not exist.',
325
+ } )
326
+ );
327
+ } );
328
+
329
+ test( 'reports error elements nested inside an added plain node', async () => {
330
+ const onError = jest.fn();
331
+ renderWithTheme( { onError } );
332
+
333
+ // The observer filters mutation records by the added node; an error
334
+ // element arriving inside a plain wrapper must still be found.
335
+ const container = screen.getByTestId( 'geo-chart' );
336
+ const plainWrapper = document.createElement( 'div' );
337
+ const errorSpan = document.createElement( 'span' );
338
+ errorSpan.id = 'google-visualization-errors-9';
339
+ errorSpan.textContent = 'Requested map does not exist.';
340
+ plainWrapper.appendChild( errorSpan );
341
+ container.appendChild( plainWrapper );
342
+
343
+ await waitFor( () =>
344
+ expect( onError ).toHaveBeenCalledWith( {
345
+ id: 'google-visualization-errors-9',
346
+ message: 'Requested map does not exist.',
347
+ } )
348
+ );
349
+ } );
350
+
351
+ test( 'reports each rendered error element only once', async () => {
352
+ const onError = jest.fn();
353
+ renderWithTheme( { onError } );
354
+
355
+ const container = screen.getByTestId( 'geo-chart' );
356
+ const errorSpan = document.createElement( 'span' );
357
+ errorSpan.id = 'google-visualization-errors-2';
358
+ errorSpan.textContent = 'Requested map does not exist.';
359
+ container.appendChild( errorSpan );
360
+
361
+ await waitFor( () => expect( onError ).toHaveBeenCalledTimes( 1 ) );
362
+
363
+ // A later mutation makes the observer re-scan the container; the first
364
+ // error is seen again but must not be re-reported. The second error
365
+ // being reported proves the re-scan happened.
366
+ const secondErrorSpan = document.createElement( 'span' );
367
+ secondErrorSpan.id = 'google-visualization-errors-3';
368
+ secondErrorSpan.textContent = 'Requested map does not exist.';
369
+ container.appendChild( secondErrorSpan );
370
+
371
+ await waitFor( () =>
372
+ expect( onError ).toHaveBeenCalledWith( {
373
+ id: 'google-visualization-errors-3',
374
+ message: 'Requested map does not exist.',
375
+ } )
376
+ );
377
+ expect( onError ).toHaveBeenCalledTimes( 2 );
378
+ } );
379
+ } );
380
+
261
381
  describe( 'Loading State', () => {
262
382
  test( 'provides loading placeholder to Google Charts', () => {
263
383
  // The loading placeholder is passed to the Chart component's loader prop
@@ -15,6 +15,13 @@ export type GeoRegion = 'world' | ( string & {} );
15
15
  */
16
16
  export type GeoResolution = 'countries' | 'provinces' | 'metros';
17
17
 
18
+ export interface GeoChartError {
19
+ id?: string;
20
+ message?: string;
21
+ detailedMessage?: string;
22
+ options?: Record< string, unknown >;
23
+ }
24
+
18
25
  export interface GeoChartProps
19
26
  extends Pick< BaseChartProps, 'className' | 'chartId' | 'width' | 'height' > {
20
27
  /**
@@ -39,6 +46,10 @@ export interface GeoChartProps
39
46
  * @default 'countries'
40
47
  */
41
48
  resolution?: GeoResolution;
49
+ /**
50
+ * Callback fired when Google Charts emits a chart error.
51
+ */
52
+ onError?: ( error: GeoChartError ) => void;
42
53
  /**
43
54
  * Optional render function for the loading placeholder.
44
55
  * Called while Google Charts is loading.
@@ -7,7 +7,7 @@
7
7
 
8
8
  .heatmap-chart__empty {
9
9
  padding: var(--wpds-dimension-padding-lg, 16px);
10
- color: var(--wpds-color-fg-content-neutral-weak, #707070);
10
+ color: var(--wpds-color-foreground-content-neutral-weak, #707070);
11
11
  }
12
12
 
13
13
  // Track template is set inline because CSS `repeat()` won't take a `var()`
@@ -17,13 +17,13 @@
17
17
  gap: var(--heatmap-cell-gap, var(--wpds-dimension-gap-xs, 4px));
18
18
  flex: 1 1 0;
19
19
  width: 100%;
20
- // Floor so the grid stays usable when the parent has no explicit height.
21
- min-height: 200px;
20
+ // Override flex's auto minimum so short widgets do not scroll.
21
+ min-height: 0;
22
22
 
23
23
  &:focus-visible {
24
24
  outline:
25
25
  var(--wpds-border-width-focus, var(--wp-admin-border-width-focus, 2px)) solid
26
- var(--wpds-color-stroke-focus-brand, var(--wp-admin-theme-color, #3858e9));
26
+ var(--wpds-color-stroke-focus, var(--wp-admin-theme-color, #3858e9));
27
27
  outline-offset: 2px;
28
28
  border-radius: var(--wpds-border-radius-sm, 2px);
29
29
  }
@@ -42,7 +42,7 @@
42
42
 
43
43
  .heatmap-chart__col-label,
44
44
  .heatmap-chart__row-label {
45
- color: var(--wpds-color-fg-content-neutral-weak, #707070);
45
+ color: var(--wpds-color-foreground-content-neutral-weak, #707070);
46
46
  font-size: var(--wpds-typography-font-size-xs, 11px);
47
47
  white-space: nowrap;
48
48
  overflow: visible;
@@ -68,36 +68,36 @@
68
68
  min-height: 0;
69
69
  border-radius: var(--wpds-border-radius-sm, 2px);
70
70
  // Empty (no-data) default; cells with a value override it below.
71
- background: var(--wpds-color-bg-track-neutral-weak, #f0f0f0);
71
+ background: var(--wpds-color-background-track-neutral-weak, #f0f0f0);
72
72
  }
73
73
 
74
74
  // Floor the mix at 15% so the lowest value stays visibly tinted, distinct from
75
75
  // an empty cell.
76
76
  .heatmap-chart__cell--filled {
77
- background: color-mix(in sRGB, var(--heatmap-primary) calc((0.15 + 0.85 * var(--intensity)) * 100%), transparent);
77
+ background: color-mix(in sRGB, var(--heatmap-primary) calc((0.15 + 0.85 * var(--intensity)) * 100%), var(--heatmap-bg, #fff));
78
78
  }
79
79
 
80
80
  .heatmap-chart__cell--selected {
81
81
  outline:
82
82
  var(--wpds-border-width-focus, var(--wp-admin-border-width-focus, 2px)) solid
83
- var(--wpds-color-stroke-focus-brand, var(--wp-admin-theme-color, #3858e9));
83
+ var(--wpds-color-stroke-focus, var(--wp-admin-theme-color, #3858e9));
84
84
  outline-offset: calc(-1 * var(--wpds-border-width-focus, var(--wp-admin-border-width-focus, 2px)));
85
85
  }
86
86
 
87
87
  .heatmap-chart__cell-value {
88
- color: var(--wpds-color-fg-content-neutral, #1e1e1e);
88
+ color: var(--wpds-color-foreground-content-neutral, #1e1e1e);
89
89
  font-size: var(--wpds-typography-font-size-md, 13px);
90
90
  }
91
91
 
92
92
  // Light text on fills dark enough to out-contrast dark text (decided in JS from
93
93
  // the blended fill luminance).
94
94
  .heatmap-chart__cell--strong .heatmap-chart__cell-value {
95
- color: var(--wpds-color-fg-interactive-neutral-strong, #f0f0f0);
95
+ color: var(--wpds-color-foreground-interactive-neutral-strong, #f0f0f0);
96
96
  }
97
97
 
98
98
  .heatmap-chart__legend-swatch {
99
99
  width: var(--wpds-dimension-size-3xs, 12px);
100
100
  height: var(--wpds-dimension-size-3xs, 12px);
101
101
  border-radius: var(--wpds-border-radius-sm, 2px);
102
- background: color-mix(in sRGB, var(--heatmap-primary) calc((0.15 + 0.85 * var(--intensity)) * 100%), transparent);
102
+ background: color-mix(in sRGB, var(--heatmap-primary) calc((0.15 + 0.85 * var(--intensity)) * 100%), var(--heatmap-bg, #fff));
103
103
  }