@automattic/charts 1.0.1 → 1.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/{chunk-G3PMV62Z.js → chunk-5WRI5ZAA.js} +1 -6
  3. package/dist/{chunk-EMMSS5I5.cjs → chunk-DZUJEN5N.cjs} +2 -7
  4. package/dist/chunk-DZUJEN5N.cjs.map +1 -0
  5. package/dist/index.cjs +602 -1386
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.css +36 -52
  8. package/dist/index.css.map +1 -1
  9. package/dist/index.d.cts +3 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.js +699 -1483
  12. package/dist/index.js.map +1 -1
  13. package/dist/visx/group/index.cjs +1 -1
  14. package/dist/visx/group/index.js +1 -1
  15. package/dist/visx/legend/index.cjs +1 -1
  16. package/dist/visx/legend/index.js +1 -1
  17. package/dist/visx/text/index.cjs +1 -1
  18. package/dist/visx/text/index.js +1 -1
  19. package/package.json +12 -11
  20. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +26 -42
  21. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +18 -5
  22. package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +11 -0
  23. package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +1 -1
  24. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +2 -2
  25. package/src/charts/line-chart/line-chart.module.scss +3 -3
  26. package/src/charts/line-chart/private/line-chart-annotation-label-popover.tsx +2 -2
  27. package/src/charts/pie-chart/pie-chart.tsx +5 -3
  28. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +3 -3
  29. package/src/components/legend/private/base-legend.module.scss +2 -2
  30. package/src/components/tooltip/base-tooltip.module.scss +1 -1
  31. package/src/components/trend-indicator/trend-indicator.module.scss +2 -2
  32. package/src/hooks/use-chart-margin.tsx +1 -14
  33. package/src/providers/chart-context/global-charts-provider.tsx +13 -0
  34. package/src/providers/chart-context/test/chart-context.test.tsx +51 -0
  35. package/src/providers/chart-context/themes.ts +7 -1
  36. package/src/providers/chart-context/types.ts +1 -0
  37. package/src/types.ts +2 -0
  38. package/src/utils/index.ts +3 -0
  39. package/src/utils/resolve-font-size.ts +37 -0
  40. package/src/utils/test/resolve-css-var.test.ts +3 -5
  41. package/src/utils/test/resolve-font-size.test.ts +66 -0
  42. package/dist/chunk-EMMSS5I5.cjs.map +0 -1
  43. /package/dist/{chunk-G3PMV62Z.js.map → chunk-5WRI5ZAA.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});require('../../chunk-EMMSS5I5.cjs');
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});require('../../chunk-DZUJEN5N.cjs');
2
2
 
3
3
  // src/visx/group/index.ts
4
4
  var _group = require('@visx/group');
@@ -1,4 +1,4 @@
1
- import "../../chunk-G3PMV62Z.js";
1
+ import "../../chunk-5WRI5ZAA.js";
2
2
 
3
3
  // src/visx/group/index.ts
4
4
  import { Group } from "@visx/group";
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});require('../../chunk-EMMSS5I5.cjs');
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});require('../../chunk-DZUJEN5N.cjs');
2
2
 
3
3
  // src/visx/legend/index.ts
4
4
  var _legend = require('@visx/legend');
@@ -1,4 +1,4 @@
1
- import "../../chunk-G3PMV62Z.js";
1
+ import "../../chunk-5WRI5ZAA.js";
2
2
 
3
3
  // src/visx/legend/index.ts
4
4
  import { LineShape, CircleShape, RectShape } from "@visx/legend";
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
  var _chunk7OZEQ5HEcjs = require('../../chunk-7OZEQ5HE.cjs');
6
- require('../../chunk-EMMSS5I5.cjs');
6
+ require('../../chunk-DZUJEN5N.cjs');
7
7
 
8
8
 
9
9
 
@@ -3,7 +3,7 @@ import {
3
3
  getStringWidth,
4
4
  useText
5
5
  } from "../../chunk-NFRB2POF.js";
6
- import "../../chunk-G3PMV62Z.js";
6
+ import "../../chunk-5WRI5ZAA.js";
7
7
  export {
8
8
  Text,
9
9
  getStringWidth,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/charts",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
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,8 +63,8 @@
63
63
  "typecheck": "tsgo --noEmit"
64
64
  },
65
65
  "dependencies": {
66
- "@automattic/number-formatters": "^1.1.3",
67
- "@babel/runtime": "7.28.6",
66
+ "@automattic/number-formatters": "^1.1.5",
67
+ "@babel/runtime": "7.29.2",
68
68
  "@react-spring/web": "9.7.5",
69
69
  "@visx/annotation": "^3.12.0",
70
70
  "@visx/axis": "^3.12.0",
@@ -83,14 +83,14 @@
83
83
  "@visx/vendor": "^3.12.0",
84
84
  "@visx/xychart": "^3.12.0",
85
85
  "@wordpress/i18n": "^6.0.0",
86
- "@wordpress/theme": "0.9.0",
86
+ "@wordpress/icons": "^12.0.0",
87
+ "@wordpress/theme": "0.10.0",
87
88
  "@wordpress/ui": "0.9.0",
88
89
  "clsx": "2.1.1",
89
90
  "date-fns": "^4.1.0",
90
91
  "deepmerge": "4.3.1",
91
92
  "dompurify": "^3.3.3",
92
93
  "fast-deep-equal": "3.1.3",
93
- "gridicons": "3.4.2",
94
94
  "react-google-charts": "^5.2.1",
95
95
  "tslib": "2.8.1"
96
96
  },
@@ -99,8 +99,8 @@
99
99
  "@babel/core": "7.29.0",
100
100
  "@babel/preset-react": "7.28.5",
101
101
  "@babel/preset-typescript": "7.28.5",
102
- "@storybook/addon-docs": "10.3.1",
103
- "@storybook/react": "10.3.1",
102
+ "@storybook/addon-docs": "10.3.3",
103
+ "@storybook/react": "10.3.3",
104
104
  "@testing-library/dom": "^10.0.0",
105
105
  "@testing-library/jest-dom": "^6.0.0",
106
106
  "@testing-library/react": "^16.0.0",
@@ -110,11 +110,12 @@
110
110
  "@types/react-dom": "18.3.7",
111
111
  "@typescript/native-preview": "7.0.0-dev.20260225.1",
112
112
  "@visx/glyph": "3.12.0",
113
- "@wordpress/components": "32.4.0",
114
- "@wordpress/element": "6.42.0",
113
+ "@wordpress/components": "32.5.0",
114
+ "@wordpress/element": "6.43.0",
115
+ "@wordpress/private-apis": "1.43.0",
115
116
  "babel-jest": "30.3.0",
116
117
  "babel-plugin-react-remove-properties": "^0.3.1",
117
- "esbuild": "0.25.9",
118
+ "esbuild": "0.27.4",
118
119
  "esbuild-plugin-babel": "^0.2.3",
119
120
  "esbuild-sass-plugin": "^3.1.0",
120
121
  "identity-obj-proxy": "^3.0.0",
@@ -125,7 +126,7 @@
125
126
  "react": "18.3.1",
126
127
  "react-dom": "18.3.1",
127
128
  "sass-embedded": "1.97.3",
128
- "storybook": "10.3.1",
129
+ "storybook": "10.3.3",
129
130
  "tsup": "8.5.1",
130
131
  "typescript": "5.9.3"
131
132
  },
@@ -1,7 +1,6 @@
1
- .conversionFunnelChart {
2
- font-family: var(--funnel-font-family, "SF Pro Text");
1
+ .conversion-funnel-chart {
3
2
 
4
- &.loading {
3
+ &--loading {
5
4
  opacity: 0.6;
6
5
  pointer-events: none;
7
6
  }
@@ -19,22 +18,20 @@
19
18
  overflow: hidden;
20
19
  color: #1e1e1e;
21
20
  text-overflow: ellipsis;
22
- font-family: var(--funnel-font-family, "SF Pro Text");
23
- font-size: 18px;
21
+ font-size: var(--wpds-font-size-xl, 18px);
24
22
  font-style: normal;
25
- font-weight: 500;
26
- line-height: 20px;
23
+ font-weight: var(--wpds-font-weight-medium, 499);
24
+ line-height: var(--wpds-font-line-height-sm, 20px);
27
25
  margin: 0;
28
26
  }
29
27
 
30
28
  .change-indicator {
31
29
  overflow: hidden;
32
30
  text-overflow: ellipsis;
33
- font-family: var(--funnel-font-family, "SF Pro Text");
34
- font-size: 13px;
31
+ font-size: var(--wpds-font-size-md, 13px);
35
32
  font-style: normal;
36
- font-weight: 500;
37
- line-height: 20px;
33
+ font-weight: var(--wpds-font-weight-medium, 499);
34
+ line-height: var(--wpds-font-line-height-sm, 20px);
38
35
  margin: 0;
39
36
  }
40
37
 
@@ -53,9 +50,12 @@
53
50
  display: flex;
54
51
  flex-direction: column;
55
52
  height: 100%;
56
- transition: all 0.3s ease;
57
53
 
58
- &.blurred {
54
+ &--animated {
55
+ transition: opacity 0.3s ease;
56
+ }
57
+
58
+ &--blurred {
59
59
  opacity: 0.3;
60
60
  }
61
61
  }
@@ -66,11 +66,10 @@
66
66
 
67
67
  .step-label {
68
68
  color: #757575;
69
- font-family: var(--step-font-family, "SF Pro");
70
- font-size: 12px;
69
+ font-size: var(--wpds-font-size-sm, 12px);
71
70
  font-style: normal;
72
- font-weight: 400;
73
- line-height: 16px;
71
+ font-weight: var(--wpds-font-weight-regular, 400);
72
+ line-height: var(--wpds-font-line-height-xs, 16px);
74
73
  margin: 0 0 2px 0;
75
74
  display: block;
76
75
  overflow: hidden;
@@ -79,11 +78,10 @@
79
78
 
80
79
  .step-rate {
81
80
  color: #1e1e1e;
82
- font-family: var(--step-font-family, "SF Pro");
83
- font-size: 13px;
81
+ font-size: var(--wpds-font-size-md, 13px);
84
82
  font-style: normal;
85
- font-weight: 500;
86
- line-height: 20px;
83
+ font-weight: var(--wpds-font-weight-medium, 499);
84
+ line-height: var(--wpds-font-line-height-sm, 20px);
87
85
  margin: 0;
88
86
  display: block;
89
87
  }
@@ -95,24 +93,12 @@
95
93
  border-radius: 4px;
96
94
  position: relative;
97
95
  cursor: pointer;
98
- transition: all 0.2s ease;
99
-
100
-
101
- &.disabled {
102
- cursor: pointer;
103
- }
104
96
  }
105
97
 
106
98
  .funnel-bar {
107
99
  width: 100%;
108
100
  min-height: 4px;
109
101
  border-radius: 4px 4px 0 0;
110
- transition: all 0.3s ease;
111
-
112
- &.selected {
113
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
114
- filter: brightness(1.1);
115
- }
116
102
 
117
103
  &--animated {
118
104
  transform-origin: bottom;
@@ -148,25 +134,23 @@
148
134
 
149
135
  .tooltip-title {
150
136
  color: #1e1e1e;
151
- font-family: "SF Pro", sans-serif;
152
- font-size: 12px;
137
+ font-size: var(--wpds-font-size-sm, 12px);
153
138
  font-style: normal;
154
- font-weight: 400;
155
- line-height: 16px;
139
+ font-weight: var(--wpds-font-weight-regular, 400);
140
+ line-height: var(--wpds-font-line-height-xs, 16px);
156
141
  }
157
142
 
158
143
  .tooltip-content {
159
144
  color: #1e1e1e;
160
- font-family: "SF Pro", sans-serif;
161
- font-size: 13px;
145
+ font-size: var(--wpds-font-size-md, 13px);
162
146
  font-style: normal;
163
- font-weight: 500;
164
- line-height: 20px;
147
+ font-weight: var(--wpds-font-weight-medium, 499);
148
+ line-height: var(--wpds-font-line-height-sm, 20px);
165
149
  }
166
150
 
167
151
  .empty-state {
168
152
  text-align: center;
169
153
  padding: 48px 24px;
170
154
  color: #6b7280;
171
- font-size: 16px;
155
+ font-size: var(--wpds-font-size-lg, 16px);
172
156
  }
@@ -52,7 +52,7 @@ const ConversionFunnelChartInternal: FC< ConversionFunnelChartProps > = ( {
52
52
  } ) => {
53
53
  const chartId = useChartId( providedChartId );
54
54
  const { conversionFunnelChart: conversionFunnelChartSettings } = useGlobalChartsTheme();
55
- const { getElementStyles } = useGlobalChartsContext();
55
+ const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
56
56
  const chartRef = useRef< HTMLDivElement >( null );
57
57
  const selectedBarRef = useRef< HTMLDivElement | null >( null );
58
58
 
@@ -301,7 +301,11 @@ const ConversionFunnelChartInternal: FC< ConversionFunnelChartProps > = ( {
301
301
  <Stack
302
302
  direction="column"
303
303
  data-testid="conversion-funnel-chart"
304
- className={ clsx( styles.conversionFunnelChart, loading && styles.loading, className ) }
304
+ className={ clsx(
305
+ styles[ 'conversion-funnel-chart' ],
306
+ loading && styles[ 'conversion-funnel-chart--loading' ],
307
+ className
308
+ ) }
305
309
  style={ { ...style, height: resolvedHeight } }
306
310
  >
307
311
  <div className={ styles[ 'empty-state' ] }>
@@ -324,7 +328,11 @@ const ConversionFunnelChartInternal: FC< ConversionFunnelChartProps > = ( {
324
328
  portalContainerRef( node );
325
329
  chartRef.current = node;
326
330
  } }
327
- className={ clsx( styles.conversionFunnelChart, loading && styles.loading, className ) }
331
+ className={ clsx(
332
+ styles[ 'conversion-funnel-chart' ],
333
+ loading && styles[ 'conversion-funnel-chart--loading' ],
334
+ className
335
+ ) }
328
336
  style={ { ...style, height: resolvedHeight } }
329
337
  >
330
338
  { /* Main Metric */ }
@@ -348,7 +356,12 @@ const ConversionFunnelChartInternal: FC< ConversionFunnelChartProps > = ( {
348
356
  return (
349
357
  <div
350
358
  key={ step.id }
351
- className={ clsx( styles[ 'funnel-step' ], isBlurred && styles.blurred ) }
359
+ data-testid="funnel-step"
360
+ className={ clsx(
361
+ styles[ 'funnel-step' ],
362
+ isColorPaletteResolved && styles[ 'funnel-step--animated' ],
363
+ isBlurred && styles[ 'funnel-step--blurred' ]
364
+ ) }
352
365
  >
353
366
  { /* Step Label and Rate */ }
354
367
  <div className={ styles[ 'step-header' ] }>
@@ -376,7 +389,7 @@ const ConversionFunnelChartInternal: FC< ConversionFunnelChartProps > = ( {
376
389
 
377
390
  { /* Funnel Bar */ }
378
391
  <div
379
- className={ clsx( styles[ 'bar-container' ], isBlurred && styles.disabled ) }
392
+ className={ styles[ 'bar-container' ] }
380
393
  onClick={ stepHandlers.get( step.id )?.onClick }
381
394
  onKeyDown={ stepHandlers.get( step.id )?.onKeyDown }
382
395
  role="button"
@@ -461,4 +461,15 @@ describe( 'ConversionFunnelChart', () => {
461
461
  expect( customRenderMainMetric ).toHaveBeenCalled();
462
462
  } );
463
463
  } );
464
+
465
+ describe( 'Color palette readiness', () => {
466
+ it( 'enables transitions once color palette is resolved', () => {
467
+ render( <ConversionFunnelChart { ...defaultProps } /> );
468
+
469
+ // After render, effects have run (via useEffect in GlobalChartsProvider),
470
+ // so the palette is resolved and the animated class is applied to funnel steps
471
+ const funnelStep = screen.getAllByTestId( 'funnel-step' )[ 0 ];
472
+ expect( funnelStep ).toHaveClass( 'funnel-step--animated' );
473
+ } );
474
+ } );
464
475
  } );
@@ -73,6 +73,6 @@
73
73
  padding: 32px 16px;
74
74
  text-align: center;
75
75
  color: #666;
76
- font-size: 14px;
76
+ font-size: var(--wpds-font-size-md, 13px);
77
77
  font-style: italic;
78
78
  }
@@ -1,8 +1,8 @@
1
1
  /* eslint-disable @wordpress/no-unsafe-wp-apis */
2
- import { __experimentalGrid as Grid, __experimentalText as Text } from '@wordpress/components';
2
+ import { __experimentalGrid as Grid } from '@wordpress/components';
3
3
  import { Fragment } from '@wordpress/element';
4
4
  import { __ } from '@wordpress/i18n';
5
- import { Stack } from '@wordpress/ui';
5
+ import { Stack, Text } from '@wordpress/ui';
6
6
  import clsx from 'clsx';
7
7
  import { useContext, useMemo, type FC } from 'react';
8
8
  import { Legend } from '../../components/legend';
@@ -21,7 +21,7 @@
21
21
  }
22
22
 
23
23
  &__tooltip-date {
24
- font-weight: 700;
24
+ font-weight: var(--wpds-font-weight-medium, 499);
25
25
  padding-bottom: 10px;
26
26
  }
27
27
 
@@ -33,7 +33,7 @@
33
33
  }
34
34
 
35
35
  &__tooltip-label {
36
- font-weight: 500;
36
+ font-weight: var(--wpds-font-weight-medium, 499);
37
37
  padding-right: 1rem;
38
38
  }
39
39
 
@@ -65,7 +65,7 @@
65
65
  background: #fff;
66
66
  border: none;
67
67
  border-radius: 4px;
68
- font-size: 14px;
68
+ font-size: var(--wpds-font-size-md, 13px);
69
69
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
70
70
  position: fixed;
71
71
  margin: 0.5rem;
@@ -1,6 +1,6 @@
1
1
  import { __ } from '@wordpress/i18n';
2
+ import { Icon, close } from '@wordpress/icons';
2
3
  import clsx from 'clsx';
3
- import Gridicon from 'gridicons';
4
4
  import { useEffect, useId, useRef, useState } from 'react';
5
5
  import { isSafari } from '../../../utils';
6
6
  import styles from '../line-chart.module.scss';
@@ -100,7 +100,7 @@ const LineChartAnnotationLabelWithPopover: FC< LineChartAnnotationLabelWithPopov
100
100
  className={ styles[ 'line-chart__annotation-label-popover-close-button' ] }
101
101
  aria-label={ __( 'Close', 'jetpack-charts' ) }
102
102
  >
103
- <Gridicon icon="cross" size={ 16 } />
103
+ <Icon icon={ close } size={ 16 } />
104
104
  </button>
105
105
  </div>
106
106
  </div>
@@ -20,7 +20,7 @@ import {
20
20
  useGlobalChartsTheme,
21
21
  GlobalChartsContext,
22
22
  } from '../../providers';
23
- import { attachSubComponents } from '../../utils';
23
+ import { attachSubComponents, resolveFontSize } from '../../utils';
24
24
  import { getStringWidth } from '../../visx/text';
25
25
  import { ChartSVG, ChartHTML, useChartChildren } from '../private/chart-composition';
26
26
  import { ChartLayout } from '../private/chart-layout';
@@ -441,10 +441,12 @@ const PieChartInternal = ( {
441
441
  groupProps.onMouseLeave = onMouseLeave;
442
442
  }
443
443
 
444
- // Estimate text width more accurately for background sizing
445
- const fontSize = 12;
444
+ const svgLabelSmall = providerTheme.svgLabelSmall;
445
+ const fontSize = resolveFontSize( svgLabelSmall?.fontSize ) ?? 12;
446
446
  const estimatedTextWidth = getStringWidth( arc.data.label, {
447
447
  fontSize,
448
+ fontFamily: svgLabelSmall?.fontFamily,
449
+ fontWeight: svgLabelSmall?.fontWeight,
448
450
  } );
449
451
  const labelPadding = 6;
450
452
  const backgroundWidth = estimatedTextWidth + labelPadding * 2;
@@ -11,11 +11,11 @@
11
11
  }
12
12
 
13
13
  .label {
14
- font-weight: 600;
15
- font-size: 16px;
14
+ font-weight: var(--wpds-font-weight-medium, 499);
15
+ font-size: var(--wpds-font-size-lg, 16px);
16
16
  }
17
17
 
18
18
  .note {
19
- font-size: 14px;
19
+ font-size: var(--wpds-font-size-md, 13px);
20
20
  }
21
21
  }
@@ -44,7 +44,7 @@
44
44
  .legend-item {
45
45
  display: flex;
46
46
  align-items: center;
47
- font-size: 0.875rem;
47
+ font-size: var(--wpds-font-size-md, 13px);
48
48
 
49
49
  &--interactive {
50
50
  cursor: pointer;
@@ -101,6 +101,6 @@
101
101
  }
102
102
 
103
103
  .legend-item-value {
104
- font-weight: 500;
104
+ font-weight: var(--wpds-font-weight-medium, 499);
105
105
  flex-shrink: 0; // Prevent value from shrinking when text is ellipsized
106
106
  }
@@ -3,7 +3,7 @@
3
3
  background-color: rgba(0, 0, 0, 0.85);
4
4
  color: #fff;
5
5
  border-radius: 4px;
6
- font-size: 14px;
6
+ font-size: var(--wpds-font-size-md, 13px);
7
7
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
8
8
  position: absolute;
9
9
  pointer-events: none;
@@ -2,8 +2,8 @@
2
2
  display: inline-flex;
3
3
  align-items: center;
4
4
  gap: 0.125em;
5
- font-size: 0.875rem;
6
- font-weight: 500;
5
+ font-size: var(--wpds-font-size-md, 13px);
6
+ font-weight: var(--wpds-font-weight-medium, 499);
7
7
  line-height: 1;
8
8
 
9
9
  &--up {
@@ -1,6 +1,6 @@
1
1
  import { createScale, getTicks } from '@visx/scale';
2
2
  import { useMemo } from 'react';
3
- import { getLongestTickWidth } from '../utils';
3
+ import { getLongestTickWidth, resolveFontSize } from '../utils';
4
4
  import type { BaseChartProps, DataPointDate, SeriesData } from '../types';
5
5
  import type { XYChartTheme } from '@visx/xychart';
6
6
 
@@ -50,19 +50,6 @@ const DEFAULT_TICK_LENGTH = 8;
50
50
  */
51
51
  const DEFAULT_Y_TICK_WIDTH = 40;
52
52
 
53
- const resolveFontSize = ( val?: number | string ): number | undefined => {
54
- if ( typeof val === 'number' && ! isNaN( val ) ) {
55
- return val;
56
- }
57
-
58
- if ( typeof val === 'string' ) {
59
- const parsed = parseFloat( val );
60
- return isNaN( parsed ) ? undefined : parsed;
61
- }
62
-
63
- return undefined;
64
- };
65
-
66
53
  const getXAxisLabelMetrics = ( theme: XYChartTheme, orientation: 'top' | 'bottom' ) => {
67
54
  const xAxisStyles =
68
55
  orientation === 'top' ? theme.axisStyles?.x?.top : theme.axisStyles?.x?.bottom;
@@ -70,11 +70,16 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
70
70
  maxHue: 0,
71
71
  } ) );
72
72
 
73
+ // Track if the color palette has been resolved from the DOM
74
+ // Useful for animations that should only run after the color palette is resolved
75
+ const [ isColorPaletteResolved, setIsColorPaletteResolved ] = useState( false );
76
+
73
77
  // Compute color cache after DOM is updated (so CSS variables are available)
74
78
  // Resolves CSS variables from the wrapper element's scope to handle scoped variables
75
79
  // Note: Only re-runs when providerTheme changes, not when wrapper element changes.
76
80
  // This is intentional, as wrapperRef is expected to be stable for the lifetime of the provider.
77
81
  useLayoutEffect( () => {
82
+ setIsColorPaletteResolved( false );
78
83
  const { colors } = providerTheme;
79
84
  const resolvedColors: string[] = [];
80
85
  const hues: number[] = [];
@@ -125,6 +130,12 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
125
130
  } );
126
131
  }, [ providerTheme ] );
127
132
 
133
+ useEffect( () => {
134
+ if ( colorCache.colors.length > 0 ) {
135
+ setIsColorPaletteResolved( true );
136
+ }
137
+ }, [ colorCache ] );
138
+
128
139
  const [ groupToColorMap, setGroupToColorMap ] = useState< Map< string, string > >(
129
140
  () => new Map()
130
141
  );
@@ -271,6 +282,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
271
282
  toggleSeriesVisibility,
272
283
  isSeriesVisible,
273
284
  getHiddenSeries,
285
+ isColorPaletteResolved,
274
286
  } ),
275
287
  [
276
288
  charts,
@@ -282,6 +294,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
282
294
  toggleSeriesVisibility,
283
295
  isSeriesVisible,
284
296
  getHiddenSeries,
297
+ isColorPaletteResolved,
285
298
  ]
286
299
  );
287
300
 
@@ -47,6 +47,57 @@ describe( 'ChartContext', () => {
47
47
  expect( contextValue.charts ).toBeInstanceOf( Map );
48
48
  } );
49
49
 
50
+ it( 'exposes isColorPaletteResolved as true after render', () => {
51
+ let contextValue: GlobalChartsContextValue;
52
+
53
+ const TestComponent = () => {
54
+ contextValue = useGlobalChartsContext();
55
+ return <div>Test</div>;
56
+ };
57
+
58
+ render(
59
+ <GlobalChartsProvider>
60
+ <TestComponent />
61
+ </GlobalChartsProvider>
62
+ );
63
+
64
+ // After render and effects, isColorPaletteResolved should be true
65
+ expect( contextValue.isColorPaletteResolved ).toBe( true );
66
+ } );
67
+
68
+ it( 'resolves palette again after theme change', () => {
69
+ let contextValue: GlobalChartsContextValue;
70
+
71
+ const TestComponent = () => {
72
+ contextValue = useGlobalChartsContext();
73
+ return <div>Test</div>;
74
+ };
75
+
76
+ const theme1: Partial< ChartTheme > = {
77
+ colors: [ '#006DAB', '#1F9828' ],
78
+ };
79
+ const theme2: Partial< ChartTheme > = {
80
+ colors: [ '#FF0000', '#00FF00' ],
81
+ };
82
+
83
+ const { rerender } = render(
84
+ <GlobalChartsProvider theme={ theme1 }>
85
+ <TestComponent />
86
+ </GlobalChartsProvider>
87
+ );
88
+
89
+ expect( contextValue.isColorPaletteResolved ).toBe( true );
90
+
91
+ rerender(
92
+ <GlobalChartsProvider theme={ theme2 }>
93
+ <TestComponent />
94
+ </GlobalChartsProvider>
95
+ );
96
+
97
+ // After theme change, palette should re-resolve to true
98
+ expect( contextValue.isColorPaletteResolved ).toBe( true );
99
+ } );
100
+
50
101
  it( 'throws error when useGlobalChartsContext is used outside provider', () => {
51
102
  const TestComponent = () => {
52
103
  useGlobalChartsContext();
@@ -26,7 +26,13 @@ const defaultTheme: CompleteChartTheme = {
26
26
  },
27
27
  seriesLineStyles: [],
28
28
  glyphs: [],
29
- svgLabelSmall: { fill: 'var(--jp-gray-80, #2c3338)' },
29
+ // `fontFamily: 'inherit'` overrides visx's hardcoded default font stack
30
+ // (`-apple-system,BlinkMacSystemFont,Roboto,Helvetica Neue,sans-serif`)
31
+ // that `buildChartTheme` injects as an inline style on SVG `<text>`
32
+ // elements for axis labels and ticks. Setting `inherit` lets SVG text
33
+ // pick up the host application's font-family via normal CSS inheritance.
34
+ svgLabelSmall: { fill: 'var(--jp-gray-80, #2c3338)', fontFamily: 'inherit' },
35
+ svgLabelBig: { fontFamily: 'inherit' },
30
36
  annotationStyles: {
31
37
  label: {
32
38
  anchorLineStroke: 'var(--jp-gray-80, #2c3338)',
@@ -35,4 +35,5 @@ export interface GlobalChartsContextValue {
35
35
  toggleSeriesVisibility: ( chartId: string, seriesLabel: string ) => void;
36
36
  isSeriesVisible: ( chartId: string, seriesLabel: string ) => boolean;
37
37
  getHiddenSeries: ( chartId: string ) => Set< string >;
38
+ isColorPaletteResolved: boolean;
38
39
  }
package/src/types.ts CHANGED
@@ -243,6 +243,8 @@ export type ChartTheme = {
243
243
  };
244
244
  /** Styles for small SVG text (eg. axis tick labels), passed through to the XYChart theme. */
245
245
  svgLabelSmall?: TextProps;
246
+ /** Styles for large SVG text (eg. axis titles), passed through to the XYChart theme. */
247
+ svgLabelBig?: TextProps;
246
248
  annotationStyles?: AnnotationStyles;
247
249
  /** GeoChart specific settings */
248
250
  geoChart?: {
@@ -26,3 +26,6 @@ export * from './color-utils';
26
26
 
27
27
  // CSS utilities
28
28
  export { resolveCssVariable } from './resolve-css-var';
29
+
30
+ // Font sizing utilities
31
+ export { resolveFontSize } from './resolve-font-size';