@automattic/charts 3.1.1 → 3.2.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 (38) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/TOKENS.md +51 -12
  3. package/dist/index.cjs +292 -200
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +7 -1
  6. package/dist/index.d.cts +223 -127
  7. package/dist/index.d.ts +223 -127
  8. package/dist/index.js +291 -199
  9. package/dist/index.js.map +1 -1
  10. package/package.json +8 -8
  11. package/src/charts/bar-chart/bar-chart.tsx +36 -10
  12. package/src/charts/bar-chart/test/bar-chart-pointer-events.test.tsx +67 -0
  13. package/src/charts/bar-chart/test/bar-chart.test.tsx +79 -0
  14. package/src/charts/bar-list-chart/bar-list-chart.tsx +40 -2
  15. package/src/charts/bar-list-chart/test/bar-list-chart.test.tsx +38 -0
  16. package/src/charts/line-chart/line-chart.tsx +16 -1
  17. package/src/charts/line-chart/test/line-chart.test.tsx +47 -0
  18. package/src/charts/sparkline/sparkline.tsx +9 -3
  19. package/src/charts/sparkline/types.ts +1 -1
  20. package/src/components/tooltip/accessible-tooltip.tsx +32 -2
  21. package/src/components/tooltip/test/accessible-tooltip.test.tsx +68 -0
  22. package/src/hooks/test/use-xychart-theme-provider-override.test.tsx +4 -4
  23. package/src/hooks/test/use-xychart-theme-style-reads.test.tsx +17 -10
  24. package/src/hooks/test/use-xychart-theme.test.tsx +50 -5
  25. package/src/hooks/use-xychart-theme.ts +14 -20
  26. package/src/providers/chart-context/global-charts-provider.tsx +44 -33
  27. package/src/providers/chart-context/private/series-palette.ts +45 -0
  28. package/src/providers/chart-context/private/theme-override-vars.ts +46 -2
  29. package/src/providers/chart-context/private/with-catalog-pointers.ts +45 -2
  30. package/src/providers/chart-context/test/sparse-theme.test.ts +16 -2
  31. package/src/providers/chart-context/test/theme-override-vars.test.ts +65 -0
  32. package/src/providers/chart-context/test/use-global-charts-theme.test.tsx +8 -1
  33. package/src/providers/chart-context/test/with-catalog-pointers.test.ts +57 -0
  34. package/src/providers/chart-context/themes.ts +8 -3
  35. package/src/styles/chart-scope.scss +52 -24
  36. package/src/styles/test/chart-scope.test.ts +22 -9
  37. package/src/types.ts +148 -31
  38. package/src/utils/get-styles.ts +7 -1
@@ -29,6 +29,9 @@ describe( 'themeOverrideVars', () => {
29
29
  xAxisLineStyles: { stroke: '#333' },
30
30
  xTickLineStyles: { stroke: '#444' },
31
31
  svgLabelSmall: { fill: '#555' },
32
+ labelBackgroundColor: '#556',
33
+ labelTextColor: '#557',
34
+ colors: [ '#661', '#662', '#663', '#664', '#665' ],
32
35
  } )
33
36
  ).toEqual( {
34
37
  vars: {
@@ -37,6 +40,13 @@ describe( 'themeOverrideVars', () => {
37
40
  '--a8c-charts-color-axis-theme': '#333',
38
41
  '--a8c-charts-color-tick-theme': '#444',
39
42
  '--a8c-charts-color-label-axis-theme': '#555',
43
+ '--a8c-charts-color-label-background-theme': '#556',
44
+ '--a8c-charts-color-label-on-fill-theme': '#557',
45
+ '--a8c-charts-color-series-1-theme': '#661',
46
+ '--a8c-charts-color-series-2-theme': '#662',
47
+ '--a8c-charts-color-series-3-theme': '#663',
48
+ '--a8c-charts-color-series-4-theme': '#664',
49
+ '--a8c-charts-color-series-5-theme': '#665',
40
50
  },
41
51
  roles: [
42
52
  '--a8c-charts-color-background',
@@ -44,6 +54,13 @@ describe( 'themeOverrideVars', () => {
44
54
  '--a8c-charts-color-axis',
45
55
  '--a8c-charts-color-tick',
46
56
  '--a8c-charts-color-label-axis',
57
+ '--a8c-charts-color-label-background',
58
+ '--a8c-charts-color-label-on-fill',
59
+ '--a8c-charts-color-series-1',
60
+ '--a8c-charts-color-series-2',
61
+ '--a8c-charts-color-series-3',
62
+ '--a8c-charts-color-series-4',
63
+ '--a8c-charts-color-series-5',
47
64
  ],
48
65
  } );
49
66
  } );
@@ -55,6 +72,9 @@ describe( 'themeOverrideVars', () => {
55
72
  xAxisLineStyles: { stroke: '#333' },
56
73
  xTickLineStyles: { stroke: '#444' },
57
74
  svgLabelSmall: { fill: '#555' },
75
+ labelBackgroundColor: '#556',
76
+ labelTextColor: '#557',
77
+ colors: [ '#661', '#662', '#663', '#664', '#665' ],
58
78
  } );
59
79
 
60
80
  expect( Object.keys( vars ) ).toEqual( THEME_LAYERED_ROLES.map( themeLayerVar ) );
@@ -68,6 +88,51 @@ describe( 'themeOverrideVars', () => {
68
88
  } );
69
89
  } );
70
90
 
91
+ // A short array is not padded: the later slots stay unset, so the palette compacts rather than repeating a color the consumer never chose.
92
+ it( 'publishes one palette slot per color and leaves the rest unset', () => {
93
+ expect( themeOverrideVars( { colors: [ 'red', 'blue' ] } ) ).toEqual( {
94
+ vars: {
95
+ '--a8c-charts-color-series-1-theme': 'red',
96
+ '--a8c-charts-color-series-2-theme': 'blue',
97
+ },
98
+ roles: [ '--a8c-charts-color-series-1', '--a8c-charts-color-series-2' ],
99
+ } );
100
+ } );
101
+
102
+ it( 'drops palette entries past the last slot', () => {
103
+ // Spied rather than asserted on: an over-long palette warns, and `@wordpress/jest-console` fails any test that leaves a console call unclaimed. The warning itself is covered below.
104
+ const warn = jest.spyOn( console, 'warn' ).mockImplementation( () => {} );
105
+ const { vars } = themeOverrideVars( {
106
+ colors: [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ],
107
+ } );
108
+
109
+ expect( vars[ '--a8c-charts-color-series-5-theme' ] ).toBe( 'e' );
110
+ expect( vars[ '--a8c-charts-color-series-6-theme' ] ).toBeUndefined();
111
+
112
+ warn.mockRestore();
113
+ } );
114
+
115
+ // Isolated so the warning's module-level latch starts unset — the assertion is that it warns once per module instance, not once per call.
116
+ it( 'warns once about the dropped entries, not once per render', () => {
117
+ jest.isolateModules( () => {
118
+ const warn = jest.spyOn( console, 'warn' ).mockImplementation( () => {} );
119
+ const {
120
+ themeOverrideVars: freshThemeOverrideVars,
121
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- isolateModules needs a synchronous require to get a module instance whose latch is unset.
122
+ } = require( '../private/theme-override-vars' ) as {
123
+ themeOverrideVars: typeof themeOverrideVars;
124
+ };
125
+
126
+ freshThemeOverrideVars( { colors: [ 'a', 'b', 'c', 'd', 'e', 'f' ] } );
127
+ freshThemeOverrideVars( { colors: [ 'a', 'b', 'c', 'd', 'e', 'f' ] } );
128
+
129
+ expect( warn ).toHaveBeenCalledTimes( 1 );
130
+ expect( warn ).toHaveBeenCalledWith( expect.stringContaining( 'holds 6 colors' ) );
131
+
132
+ warn.mockRestore();
133
+ } );
134
+ } );
135
+
71
136
  it( 'ignores a value that is itself a pointer at the role it would override', () => {
72
137
  expect(
73
138
  themeOverrideVars( { gridStyles: { stroke: 'var(--a8c-charts-color-grid, #dbdbdb)' } } ).vars
@@ -27,7 +27,14 @@ describe( 'useGlobalChartsTheme', () => {
27
27
 
28
28
  const { result } = renderHook( () => useGlobalChartsTheme(), { wrapper } );
29
29
 
30
- expect( result.current.colors ).toEqual( [ '#FF0000', '#00FF00', '#0000FF' ] );
30
+ // `theme.colors` publishes the palette's theme layers, so the merged theme holds catalog pointers rather than the consumer's literals. Each keeps its consumer color as the pointer's terminal literal, for SSR and jsdom.
31
+ expect( result.current.colors ).toEqual( [
32
+ 'var(--a8c-charts-color-series-1, #FF0000)',
33
+ 'var(--a8c-charts-color-series-2, #00FF00)',
34
+ 'var(--a8c-charts-color-series-3, #0000FF)',
35
+ 'var(--a8c-charts-color-series-4)',
36
+ 'var(--a8c-charts-color-series-5)',
37
+ ] );
31
38
  // Other properties should still come from default theme
32
39
  expect( result.current.backgroundColor ).toBe( defaultTheme.backgroundColor );
33
40
  expect( result.current.gridStyles ).toEqual( defaultTheme.gridStyles );
@@ -11,6 +11,8 @@ describe( 'withCatalogPointers', () => {
11
11
  xAxisLineStyles: { stroke: '#333' },
12
12
  xTickLineStyles: { stroke: '#444' },
13
13
  svgLabelSmall: { fill: '#555' },
14
+ labelBackgroundColor: '#556',
15
+ labelTextColor: '#557',
14
16
  } as Partial< ChartTheme > );
15
17
 
16
18
  const result = withCatalogPointers( merged, [
@@ -19,6 +21,8 @@ describe( 'withCatalogPointers', () => {
19
21
  '--a8c-charts-color-axis',
20
22
  '--a8c-charts-color-tick',
21
23
  '--a8c-charts-color-label-axis',
24
+ '--a8c-charts-color-label-background',
25
+ '--a8c-charts-color-label-on-fill',
22
26
  ] );
23
27
 
24
28
  expect( result.backgroundColor ).toBe( defaultTheme.backgroundColor );
@@ -26,6 +30,8 @@ describe( 'withCatalogPointers', () => {
26
30
  expect( result.xAxisLineStyles.stroke ).toBe( defaultTheme.xAxisLineStyles.stroke );
27
31
  expect( result.xTickLineStyles.stroke ).toBe( defaultTheme.xTickLineStyles.stroke );
28
32
  expect( result.svgLabelSmall.fill ).toBe( defaultTheme.svgLabelSmall.fill );
33
+ expect( result.labelBackgroundColor ).toBe( defaultTheme.labelBackgroundColor );
34
+ expect( result.labelTextColor ).toBe( defaultTheme.labelTextColor );
29
35
 
30
36
  expect( merged.backgroundColor ).toBe( '#111' );
31
37
  expect( merged.gridStyles.stroke ).toBe( '#222' );
@@ -44,4 +50,55 @@ describe( 'withCatalogPointers', () => {
44
50
  // Not a mapped role, so its literal survives even when other roles are being restored.
45
51
  expect( result.leaderboardChart.deltaColors ).toEqual( [ 'red', 'grey', 'green' ] );
46
52
  } );
53
+
54
+ // The palette is restored as a whole, so a consumer's short array cannot shorten it. `mergeThemes` replaces arrays outright, and a two-entry palette would leave slots 3 to 5 unreadable — a CSS declaration of one of them would have no entry to resolve through.
55
+ it( 'restores the full five-slot palette from a shorter consumer array', () => {
56
+ const merged = mergeThemes( defaultTheme, {
57
+ colors: [ '#111', '#222' ],
58
+ } as Partial< ChartTheme > );
59
+
60
+ const result = withCatalogPointers( merged, [
61
+ '--a8c-charts-color-series-1',
62
+ '--a8c-charts-color-series-2',
63
+ ] );
64
+
65
+ expect( result.colors ).toEqual( [
66
+ 'var(--a8c-charts-color-series-1, #111)',
67
+ 'var(--a8c-charts-color-series-2, #222)',
68
+ 'var(--a8c-charts-color-series-3)',
69
+ 'var(--a8c-charts-color-series-4)',
70
+ 'var(--a8c-charts-color-series-5)',
71
+ ] );
72
+ } );
73
+
74
+ // Each slot's restore rewrites the whole array, so restoring five of them must not wrap the pointers five times over.
75
+ it( 'restores the palette idempotently across every overridden slot', () => {
76
+ const merged = mergeThemes( defaultTheme, {
77
+ colors: [ '#111', '#222', '#333', '#444', '#555' ],
78
+ } as Partial< ChartTheme > );
79
+
80
+ const result = withCatalogPointers( merged, [
81
+ '--a8c-charts-color-series-1',
82
+ '--a8c-charts-color-series-2',
83
+ '--a8c-charts-color-series-3',
84
+ '--a8c-charts-color-series-4',
85
+ '--a8c-charts-color-series-5',
86
+ ] );
87
+
88
+ expect( result.colors ).toEqual( [
89
+ 'var(--a8c-charts-color-series-1, #111)',
90
+ 'var(--a8c-charts-color-series-2, #222)',
91
+ 'var(--a8c-charts-color-series-3, #333)',
92
+ 'var(--a8c-charts-color-series-4, #444)',
93
+ 'var(--a8c-charts-color-series-5, #555)',
94
+ ] );
95
+ } );
96
+
97
+ // The literal is only reached where getComputedStyle resolves nothing, and the palette is the one field where falling back to the catalog default would be visible: every series would paint the same seeded blue.
98
+ it( 'keeps the consumer color as each pointer’s terminal literal, for SSR and jsdom', () => {
99
+ const merged = mergeThemes( defaultTheme, { colors: [ 'red' ] } as Partial< ChartTheme > );
100
+ const result = withCatalogPointers( merged, [ '--a8c-charts-color-series-1' ] );
101
+
102
+ expect( result.colors[ 0 ] ).toBe( 'var(--a8c-charts-color-series-1, red)' );
103
+ } );
47
104
  } );
@@ -1,3 +1,4 @@
1
+ import { SERIES_PALETTE_POINTERS } from './private/series-palette';
1
2
  import type { CompleteChartTheme } from '../../types';
2
3
 
3
4
  /**
@@ -5,10 +6,14 @@ import type { CompleteChartTheme } from '../../types';
5
6
  */
6
7
  const defaultTheme: CompleteChartTheme = {
7
8
  backgroundColor: 'var(--a8c-charts-color-background, #fff)',
8
- labelBackgroundColor: 'transparent', // label background color (transparent by default)
9
- // White label text sits on top of arbitrary series colors, so it has no WPDS content-foreground equivalent. Every other colour here is a bare pointer at the catalog emitted by `chart-scope.scss`; the terminal literal is the last resort for the SSR and jsdom paths, where getComputedStyle resolves nothing.
9
+ labelBackgroundColor: 'var(--a8c-charts-color-label-background, transparent)',
10
+ // White label text sits on top of arbitrary series colors, so it has no WPDS content-foreground equivalent. Every other color here is a bare pointer at the catalog emitted by `chart-scope.scss`; the terminal literal is the last resort for the SSR and jsdom paths, where getComputedStyle resolves nothing.
10
11
  labelTextColor: 'var(--a8c-charts-color-label-on-fill, #FFFFFF)',
11
- colors: [ '#98C8DF', '#006DAB', '#A6DC80', '#1F9828', '#FF8C8F' ],
12
+ // Derived, not authored: the slot manifest lives in `private/series-palette.ts` because which
13
+ // slots exist describes the catalog, not the theme. The field itself is still load-bearing —
14
+ // see that file for why the provider reads the palette through the theme and what has to move
15
+ // before this can be deleted.
16
+ colors: [ ...SERIES_PALETTE_POINTERS ],
12
17
  gridStyles: {
13
18
  stroke: 'var(--a8c-charts-color-grid, #dbdbdb)',
14
19
  strokeWidth: 1,
@@ -1,41 +1,69 @@
1
1
  // The single mapping point between the --a8c-charts-* catalog and WordPress
2
- // design-system tokens.
2
+ // design-system tokens. Declares custom properties only — this stylesheet must
3
+ // never change layout.
3
4
 
4
- // Declared on the GlobalChartsProvider wrapper — not at :root — because
5
+ // Declared on the GlobalChartsProvider wrapper, not at :root, because
5
6
  // @wordpress/theme's ThemeProvider emits its generated --wpds-* ramp as inline
6
- // styles on its own wrapper div, not on the document root. Substituting at the
7
- // provider means a ThemeProvider above it is picked up; substituting at :root
8
- // would miss it entirely.
7
+ // styles on its own wrapper div. Substituting at :root would miss it entirely.
9
8
 
10
- // :where() keeps the rule at zero specificity so any consumer rule
11
- // targeting the same element wins. Chart roots deliberately do NOT
12
- // re-declare these: an element's own declaration beats an inherited one,
13
- // so re-declaring lower down would shadow a consumer override set inside
14
- // the provider tree.
9
+ // :where() keeps the rule at zero specificity so any consumer rule targeting
10
+ // the same element wins. Chart roots deliberately do NOT re-declare these: an
11
+ // element's own declaration beats an inherited one, so re-declaring lower down
12
+ // would shadow a consumer override set inside the provider tree.
15
13
 
16
- // The five roles a `theme` prop field can override read a `*-theme` variable
17
- // first, which GlobalChartsProvider writes inline from that field. Publishing
18
- // the consumer's value one layer out, rather than as the role itself, is what
19
- // keeps the catalog default reachable: a value that is invalid at
20
- // computed-value time (a fallback-less `var()` naming a token nothing set,
21
- // which is how premium-analytics passes its grid stroke) invalidates only the
22
- // `*-theme` variable, and the role's own fallback still resolves the mapped
23
- // --wpds-* token. Written as the role directly, that same value made the role
24
- // guaranteed-invalid and took every bare `var(--a8c-charts-color-grid)` read
25
- // site with it — gridlines painted `unset` instead of the spec grey.
14
+ // The roles a `theme` prop field can override read a `*-theme` variable first,
15
+ // which GlobalChartsProvider writes inline from that field. Publishing the
16
+ // consumer's value one layer out keeps the catalog default reachable: a value
17
+ // that is invalid at computed-value time (a fallback-less `var()` naming a
18
+ // token nothing set, which is how premium-analytics passes its grid stroke)
19
+ // invalidates only the `*-theme` variable, leaving the role's own fallback to
20
+ // resolve. Written as the role directly, it made the role guaranteed-invalid
21
+ // and took every bare read site with it.
26
22
  // `src/styles/test/chart-scope.test.ts` pins which roles carry the layer.
27
-
28
- // Declares custom properties only. This stylesheet must never change layout.
29
23
  :where(.a8c-charts-scope) {
24
+ // The series palette. GlobalChartsProvider resolves these five slots at its
25
+ // wrapper and seeds ColorCache with whatever resolves; a slot that resolves
26
+ // to nothing is skipped, so setting only slots 1 and 3 gives a two-color
27
+ // palette, and charts generate accessible colors beyond the seeds. Only
28
+ // slot 1 carries a default.
29
+
30
+ // Slot 1 names --wp-admin-theme-color first, because that is the only leg
31
+ // that does not depend on someone else's runtime. The design system's brand
32
+ // token tracks the admin color scheme only where a WPDS root provider
33
+ // boots and emits its generated ramp inline; without one the token falls to
34
+ // the plain stylesheet rule, a static #3858e9 with no reference to the
35
+ // admin color. Leading with the admin color costs nothing:
36
+ // @wordpress/theme writes --wp-admin-theme-color from a provider's own
37
+ // accent as a legacy wp-admin override, so a provider accent still wins.
38
+
39
+ // DO NOT SIMPLIFY the chain below. The innermost
40
+ // var(--wp-admin-theme-color, #3858e9) is unreachable — the outer leg
41
+ // already tried that variable — and reads like a copy-paste mistake. It is
42
+ // the design system's own spec value for the WPDS token, which
43
+ // `src/styles/test/wpds-fallbacks.test.ts` compares against literally, so
44
+ // collapsing it to a bare hex fails that test. Reordering to put the WPDS
45
+ // token first passes every test and breaks only on a host with no root
46
+ // provider.
47
+
48
+ // That test scans this file as text, comments included, so do not write an
49
+ // example --wpds-* reference in a comment here — it is read as a real one.
50
+ --a8c-charts-color-series-1: var(--a8c-charts-color-series-1-theme, var(--wp-admin-theme-color, var(--wpds-color-foreground-interactive-brand, var(--wp-admin-theme-color, #3858e9))));
51
+ --a8c-charts-color-series-2: var(--a8c-charts-color-series-2-theme);
52
+ --a8c-charts-color-series-3: var(--a8c-charts-color-series-3-theme);
53
+ --a8c-charts-color-series-4: var(--a8c-charts-color-series-4-theme);
54
+ --a8c-charts-color-series-5: var(--a8c-charts-color-series-5-theme);
30
55
  --a8c-charts-color-grid: var(--a8c-charts-color-grid-theme, var(--wpds-color-stroke-surface-neutral, #dbdbdb));
31
56
  --a8c-charts-color-axis: var(--a8c-charts-color-axis-theme, var(--wpds-color-stroke-surface-neutral, #dbdbdb));
32
57
  --a8c-charts-color-tick: var(--a8c-charts-color-tick-theme, var(--wpds-color-stroke-surface-neutral, #dbdbdb));
33
58
  --a8c-charts-color-label: var(--wpds-color-foreground-content-neutral, #1e1e1e);
34
59
  --a8c-charts-color-label-secondary: var(--wpds-color-foreground-content-neutral-weak, #707070);
35
60
  --a8c-charts-color-label-inverse: var(--wpds-color-foreground-interactive-neutral-strong, #f0f0f0);
36
- // White label text sits on arbitrary series colours, so it has no WPDS
61
+ // White label text sits on arbitrary series colors, so it has no WPDS
37
62
  // content-foreground equivalent.
38
- --a8c-charts-color-label-on-fill: #fff;
63
+ --a8c-charts-color-label-on-fill: var(--a8c-charts-color-label-on-fill-theme, #fff);
64
+ // The plate behind a pie-slice label. Transparent by default, so a label
65
+ // reads straight off the slice; there is no WPDS token for "no surface".
66
+ --a8c-charts-color-label-background: var(--a8c-charts-color-label-background-theme, transparent);
39
67
  // The SVG axis and tick labels. `svgLabelSmall.fill` publishes THIS role, so
40
68
  // deriving from --a8c-charts-color-label is safe and wanted: the theme field
41
69
  // moves only these labels, while setting the broad role in CSS still moves
@@ -20,16 +20,19 @@ const normalize = ( value: string ): string => value.replace( /\s+/g, ' ' ).trim
20
20
  /**
21
21
  * Unwraps the theme layer a `theme`-prop-overridable role carries, so the tables in `TOKENS.md` keep documenting what a role ultimately resolves to — its `--wpds-*` token and spec fallback — rather than restating the override plumbing in every row. Which roles carry the layer is pinned separately below.
22
22
  *
23
+ * A role with no catalog default reads its layer with no fallback at all — the empty series-palette slots — and unwraps to nothing, which is what it resolves to until a consumer sets it.
24
+ *
23
25
  * @param name - The declared property name.
24
26
  * @param value - Its normalized value.
25
- * @return The value with a leading `var(<name>-theme, … )` wrapper removed.
27
+ * @return The value with a leading `var(<name>-theme, … )` wrapper removed, or the empty string where that wrapper carried no fallback.
26
28
  */
27
29
  function stripThemeLayer( name: string, value: string ): string {
28
- const layered = new RegExp( `^var\\(\\s*${ themeLayerVar( name ) }\\s*,\\s*(.*)\\)$`, 's' ).exec(
29
- value
30
- );
30
+ const layered = new RegExp(
31
+ `^var\\(\\s*${ themeLayerVar( name ) }\\s*(?:,\\s*(.*))?\\)$`,
32
+ 's'
33
+ ).exec( value );
31
34
 
32
- return layered ? layered[ 1 ].trim() : value;
35
+ return layered ? ( layered[ 1 ] ?? '' ).trim() : value;
33
36
  }
34
37
 
35
38
  /**
@@ -55,6 +58,13 @@ function parseStylesheet(): Map< string, Entry > {
55
58
 
56
59
  const name = declaration.slice( 0, separator ).trim();
57
60
  const value = stripThemeLayer( name, normalize( declaration.slice( separator + 1 ) ) );
61
+
62
+ // A role that unwrapped to nothing maps to nothing and falls back to nothing.
63
+ if ( value === '' ) {
64
+ entries.set( name, { reads: null, fallback: null } );
65
+ continue;
66
+ }
67
+
58
68
  const wrapped = /^var\(\s*(.*)\)$/s.exec( value );
59
69
 
60
70
  if ( ! wrapped ) {
@@ -112,10 +122,13 @@ function parseTokensDoc(): Map< string, Entry > {
112
122
  // "no mapping" before looking for a token name inside it.
113
123
  const unmapped = mapping.startsWith( '_(none' );
114
124
  const derives = /derives from (--[\w-]+)/.exec( mapping );
115
- const wpds = /(--wpds-[\w-]+)/.exec( mapping );
125
+ // `--wp-*` rather than `--wpds-*`: the series palette maps to WordPress's own
126
+ // `--wp-admin-theme-color`, since the design system's brand token is a static
127
+ // hex on every admin screen and cannot carry the admin color scheme.
128
+ const token = /(--wp[\w-]+)/.exec( mapping );
116
129
 
117
130
  entries.set( role, {
118
- reads: unmapped ? null : derives?.[ 1 ] ?? wpds?.[ 1 ] ?? null,
131
+ reads: unmapped ? null : derives?.[ 1 ] ?? token?.[ 1 ] ?? null,
119
132
  fallback: fallback === '—' ? null : normalize( fallback ),
120
133
  } );
121
134
  }
@@ -140,13 +153,13 @@ describe( 'chart scope catalog', () => {
140
153
  // The layer is what keeps a `theme` prop override from being able to take its role down with it: an override that is invalid at computed-value time only invalidates `<role>-theme`, and the role's own fallback still resolves the mapped token. Drop the layer from a role and that role's overrides go back to blanking every read site.
141
154
  it.each( THEME_LAYERED_ROLES )( 'declares %s reading its theme layer first', role => {
142
155
  expect( stylesheet ).toMatch(
143
- new RegExp( `${ role }:\\s*var\\(\\s*${ themeLayerVar( role ) }\\s*,` )
156
+ new RegExp( `${ role }:\\s*var\\(\\s*${ themeLayerVar( role ) }\\s*[,)]` )
144
157
  );
145
158
  } );
146
159
 
147
160
  it( 'gives a theme layer to the overridable roles and to nothing else', () => {
148
161
  const layered = [ ...declared.keys() ].filter( role =>
149
- new RegExp( `${ role }:\\s*var\\(\\s*${ themeLayerVar( role ) }\\s*,` ).test( stylesheet )
162
+ new RegExp( `${ role }:\\s*var\\(\\s*${ themeLayerVar( role ) }\\s*[,)]` ).test( stylesheet )
150
163
  );
151
164
 
152
165
  expect( layered.sort() ).toEqual( [ ...THEME_LAYERED_ROLES ].sort() );
package/src/types.ts CHANGED
@@ -108,13 +108,31 @@ export type ChartType =
108
108
 
109
109
  export type OrientationType = ValueOf< typeof Orientation >;
110
110
 
111
+ /**
112
+ * Annotation styling.
113
+ *
114
+ * Each color member is deprecated on its own rather than the field it sits in: `radius`, `stroke` widths and the label geometry alongside them have no catalog role behind them and still apply.
115
+ */
111
116
  export type AnnotationStyles = {
112
- circleSubject?: Omit< CircleSubjectProps, 'x' | 'y' > & { fill?: string };
117
+ circleSubject?: Omit< CircleSubjectProps, 'x' | 'y' | 'fill' > & {
118
+ /** @deprecated Set the `--a8c-charts-color-annotation` custom property inside the provider tree instead. See `TOKENS.md`. */
119
+ fill?: string;
120
+ };
113
121
  lineSubject?: Omit< LineSubjectProps, 'x' | 'y' >;
114
- connector?: Omit< ConnectorProps, 'x' | 'y' | 'dx' | 'dy' >;
115
- label?: Omit< LabelProps, 'title' | 'subtitle' | 'x' | 'y' > & {
122
+ connector?: Omit< ConnectorProps, 'x' | 'y' | 'dx' | 'dy' | 'stroke' > & {
123
+ /** @deprecated Set the `--a8c-charts-color-annotation` custom property inside the provider tree instead. See `TOKENS.md`. */
124
+ stroke?: ConnectorProps[ 'stroke' ];
125
+ };
126
+ label?: Omit<
127
+ LabelProps,
128
+ 'title' | 'subtitle' | 'x' | 'y' | 'anchorLineStroke' | 'backgroundFill'
129
+ > & {
116
130
  x?: number | 'start' | 'end';
117
131
  y?: number | 'start' | 'end';
132
+ /** @deprecated Set the `--a8c-charts-color-annotation` custom property inside the provider tree instead. See `TOKENS.md`. */
133
+ anchorLineStroke?: LabelProps[ 'anchorLineStroke' ];
134
+ /** @deprecated Set the `--a8c-charts-color-surface` custom property inside the provider tree instead. See `TOKENS.md`. */
135
+ backgroundFill?: LabelProps[ 'backgroundFill' ];
118
136
  };
119
137
  };
120
138
 
@@ -305,7 +323,7 @@ export type DataPointPercentage = {
305
323
  */
306
324
  valueDisplay?: string;
307
325
  /**
308
- * Color code for the segment, by default colours are taken from the theme but this property can overrides it
326
+ * Color code for the segment, by default colors are taken from the theme but this property can overrides it
309
327
  */
310
328
  color?: string;
311
329
  /**
@@ -329,26 +347,67 @@ export type DataPointPercentageCalculated = DataPointPercentage & {
329
347
  * Base theme configuration for chart components with optional properties
330
348
  */
331
349
  export type ChartTheme = {
332
- /** Background color for chart components */
333
- backgroundColor: string;
334
- /** Background color for labels */
350
+ /**
351
+ * Background color for chart components.
352
+ *
353
+ * Still resolved in JS — gradient stops, the glyph stroke and `GeoChart`'s Google Charts config each need a concrete string — but that is about where the value is *consumed*, not where it is *set*. `withCatalogPointers` parks a consumer's value in the catalog role and restores the pointer, so the field is only ever a carrier for `--a8c-charts-color-background`, and setting the role directly does the same job.
354
+ *
355
+ * Optional so the deprecation is actionable: a consumer writing a full theme literal can now stop setting it. `CompleteChartTheme` is `Required< ChartTheme >`, so `defaultTheme` must still carry one.
356
+ *
357
+ * @deprecated Set the `--a8c-charts-color-background` custom property inside the provider tree instead. See `TOKENS.md`.
358
+ */
359
+ backgroundColor?: string;
360
+ /**
361
+ * Background color for labels.
362
+ *
363
+ * @deprecated Set the `--a8c-charts-color-label-background` custom property inside the provider tree instead. See `TOKENS.md`.
364
+ */
335
365
  labelBackgroundColor?: string;
336
- /** Text color for labels */
366
+ /**
367
+ * Text color for labels.
368
+ *
369
+ * @deprecated Set the `--a8c-charts-color-label-on-fill` custom property inside the provider tree instead. See `TOKENS.md`.
370
+ */
337
371
  labelTextColor?: string;
338
- /** Array of colors used for data visualization */
339
- colors: string[];
340
- /** Optional CSS styles for grid lines */
341
- gridStyles?: GridStyles;
372
+ /**
373
+ * Series palette seeds. Entry N publishes `--a8c-charts-color-series-{N+1}`; entries past the fifth are ignored.
374
+ *
375
+ * Optional so the deprecation is actionable: a consumer writing a full theme literal can now stop setting it. `CompleteChartTheme` is `Required< ChartTheme >`, so `defaultTheme` must still carry one.
376
+ *
377
+ * @deprecated Set the `--a8c-charts-color-series-1` … `-5` custom properties inside the provider tree instead, or `options.stroke` on a series for a single one. See `TOKENS.md`.
378
+ */
379
+ colors?: string[];
380
+ /**
381
+ * Optional CSS styles for grid lines.
382
+ *
383
+ * `stroke` is deprecated on its own, not the whole field: `strokeWidth` has no CSS role behind it and still applies. The same shape is used for the other fields whose color moved to CSS — annotating the field itself would strike through the half that stays.
384
+ */
385
+ gridStyles?: Omit< GridStyles, 'stroke' > & {
386
+ /** @deprecated Set the `--a8c-charts-color-grid` custom property inside the provider tree instead. See `TOKENS.md`. */
387
+ stroke?: GridStyles[ 'stroke' ];
388
+ };
342
389
  /** Length of axis ticks in pixels */
343
390
  tickLength: number;
344
- /** Color of the grid lines */
345
- gridColor: string;
346
- /** Color of the grid lines in dark mode */
347
- gridColorDark: string;
391
+ /**
392
+ * Set `--a8c-charts-color-grid` for the gridlines.
393
+ *
394
+ * @deprecated Do not drop this field without replacing it. Nothing in this package consults it, but `buildChartTheme` seeds `y.left/right.axisLine.stroke` and `y.*.tickLine.stroke` from it, and `defaultTheme`'s empty string is the only thing leaving the y axis unpainted — deleting the field hands those a black `#222` instead. CHARTS-263 replaces it with visx's own `yAxisLineStyles`/`yTickLineStyles` and a pair of catalog roles.
395
+ */
396
+ gridColor?: string;
397
+ /**
398
+ * @deprecated Paints nothing, and unlike `gridColor` is safe to delete: `buildChartTheme` seeds `x.*.axisLine.stroke` from it, but spreads `xAxisLineStyles` over the top and `defaultTheme` always carries that stroke. Charts follow the host's theme through the catalog rather than a dark-mode field of their own.
399
+ */
400
+ gridColorDark?: string;
348
401
  /** Styles for x-axis tick lines */
349
- xTickLineStyles?: LineStyles;
402
+ xTickLineStyles?: Omit< LineStyles, 'stroke' > & {
403
+ /** @deprecated Set the `--a8c-charts-color-tick` custom property inside the provider tree instead. See `TOKENS.md`. */
404
+ stroke?: LineStyles[ 'stroke' ];
405
+ };
350
406
  /** Styles for x-axis line */
351
- xAxisLineStyles?: LineStyles;
407
+ xAxisLineStyles?: Omit< LineStyles, 'stroke' > & {
408
+ /** @deprecated Set the `--a8c-charts-color-axis` custom property inside the provider tree instead. See `TOKENS.md`. */
409
+ stroke?: LineStyles[ 'stroke' ];
410
+ };
352
411
  /** Styles for series lines */
353
412
  seriesLineStyles?: LineStyles[];
354
413
  /** Array of render functions for glyphs */
@@ -357,19 +416,37 @@ export type ChartTheme = {
357
416
  legend?: {
358
417
  /** Styles for legend shapes */
359
418
  shapeStyles?: Record< string, unknown >[];
360
- /** Styles for legend labels */
361
- labelStyles?: CSSProperties;
419
+ /**
420
+ * Styles for legend labels.
421
+ *
422
+ * `color` is deprecated on its own, not the whole field: everything else a consumer sets here — font size, weight, spacing — has no catalog role behind it and still applies.
423
+ */
424
+ labelStyles?: Omit< CSSProperties, 'color' > & {
425
+ /** @deprecated Set the `--a8c-charts-color-label` custom property inside the provider tree instead. See `TOKENS.md`. */
426
+ color?: CSSProperties[ 'color' ];
427
+ };
362
428
  /** Styles for legend container */
363
429
  containerStyles?: CSSProperties;
364
430
  };
365
- /** Styles for small SVG text (eg. axis tick labels), passed through to the XYChart theme. */
366
- svgLabelSmall?: TextProps;
431
+ /**
432
+ * Styles for small SVG text (eg. axis tick labels), passed through to the XYChart theme.
433
+ *
434
+ * `fontFamily: 'inherit'` is load-bearing here — it overrides the font stack `buildChartTheme` injects inline on SVG `<text>` — so only `fill` is deprecated.
435
+ */
436
+ svgLabelSmall?: Omit< TextProps, 'fill' > & {
437
+ /** @deprecated Set the `--a8c-charts-color-label-axis` custom property inside the provider tree instead, or `--a8c-charts-color-label` to move every label at once. See `TOKENS.md`. */
438
+ fill?: TextProps[ 'fill' ];
439
+ };
367
440
  /** Styles for large SVG text (eg. axis titles), passed through to the XYChart theme. */
368
441
  svgLabelBig?: TextProps;
369
442
  annotationStyles?: AnnotationStyles;
370
443
  /** GeoChart specific settings */
371
444
  geoChart?: {
372
- /** Default fill color for a geo chart feature (e.g. country) with no data */
445
+ /**
446
+ * Default fill color for a geo chart feature (e.g. country) with no data.
447
+ *
448
+ * @deprecated Set the `--a8c-charts-color-surface-secondary` custom property inside the provider tree instead. Google Charts still takes a resolved hex, but the value is read from the role. See `TOKENS.md`.
449
+ */
373
450
  featureFillColor?: string;
374
451
  };
375
452
  /** LeaderboardChart specific settings */
@@ -380,22 +457,50 @@ export type ChartTheme = {
380
457
  columnGap?: number;
381
458
  /** Spacing between label and progress bars */
382
459
  labelSpacing?: GapSize;
383
- /** Primary color for current period bars */
460
+ /**
461
+ * Primary color for current period bars.
462
+ *
463
+ * @deprecated Use the `primaryColor` prop on `LeaderboardChart` for one chart, or set `--a8c-charts-color-series-1` to move the palette this falls back to. See `TOKENS.md`.
464
+ */
384
465
  primaryColor?: string;
385
- /** Secondary color for comparison period bars */
466
+ /**
467
+ * Secondary color for comparison period bars.
468
+ *
469
+ * @deprecated Use the `secondaryColor` prop on `LeaderboardChart` for one chart, or set `--a8c-charts-color-series-2` to move the palette this falls back to. See `TOKENS.md`.
470
+ */
386
471
  secondaryColor?: string;
387
- /** Delta colors: [negative, neutral, positive] */
472
+ /**
473
+ * Delta colors: [negative, neutral, positive].
474
+ *
475
+ * @deprecated Set the `--a8c-charts-color-trend-down`, `-trend-neutral` and `-trend-up` custom properties inside the provider tree instead. See `TOKENS.md`.
476
+ */
388
477
  deltaColors?: [ string, string, string ];
389
478
  };
390
479
  /** ConversionFunnelChart specific settings */
391
480
  conversionFunnelChart?: {
392
- /** Primary color for funnel bars */
481
+ /**
482
+ * Primary color for funnel bars.
483
+ *
484
+ * @deprecated Set `--a8c-charts-color-series-1` to move the palette this falls back to. See `TOKENS.md`.
485
+ */
393
486
  primaryColor?: string;
394
- /** Background color for chart container */
487
+ /**
488
+ * Background color for chart container.
489
+ *
490
+ * @deprecated Set the `--a8c-charts-color-surface-secondary` custom property inside the provider tree instead. See `TOKENS.md`.
491
+ */
395
492
  backgroundColor?: string;
396
- /** Color for positive change indicators */
493
+ /**
494
+ * Color for positive change indicators.
495
+ *
496
+ * @deprecated Set the `--a8c-charts-color-trend-up` custom property inside the provider tree instead. See `TOKENS.md`.
497
+ */
397
498
  positiveChangeColor?: string;
398
- /** Color for negative change indicators */
499
+ /**
500
+ * Color for negative change indicators.
501
+ *
502
+ * @deprecated Set the `--a8c-charts-color-trend-down` custom property inside the provider tree instead. See `TOKENS.md`.
503
+ */
399
504
  negativeChangeColor?: string;
400
505
  };
401
506
  lineChart?: {
@@ -424,6 +529,8 @@ export type ChartTheme = {
424
529
  /**
425
530
  * Color the cell scale interpolates toward at the highest value (prop > this >
426
531
  * palette `colors[0]`), fed to CSS `color-mix`. Omit to use the palette color.
532
+ *
533
+ * @deprecated Use the `primaryColor` prop on `HeatmapChart` for one chart, or set `--a8c-charts-color-series-1` to move the palette this falls back to. See `TOKENS.md`.
427
534
  */
428
535
  primaryColor?: string;
429
536
  /** Gap in px between cells in compact mode */
@@ -626,7 +733,7 @@ export type ChartLegendConfig< T = DataPoint | DataPointDate | LeaderboardEntry
626
733
  /**
627
734
  * Legend config for charts built from `SeriesData` (line, bar, area). Adds `collapseGroups` on top
628
735
  * of the shared config. It is intentionally absent from the base `ChartLegendConfig` so point-based
629
- * charts (pie, semi-circle pie) — whose data points carry `group` only to coordinate colours — can't
736
+ * charts (pie, semi-circle pie) — whose data points carry `group` only to coordinate colors — can't
630
737
  * set it.
631
738
  */
632
739
  export type SeriesChartLegendConfig = ChartLegendConfig< SeriesData[] > & {
@@ -706,6 +813,16 @@ export type BaseChartProps< T = DataPoint | DataPointDate | LeaderboardEntry > =
706
813
  * Callback function for pointer out event
707
814
  */
708
815
  onPointerOut?: ( event: PointerEvent< Element > ) => void;
816
+ /**
817
+ * Callback for Enter or Space on the point keyboard navigation has selected:
818
+ * the keyboard counterpart of a click. `index` is the point's position in its
819
+ * series and `key` the series label.
820
+ */
821
+ onDatumActivate?: ( params: {
822
+ datum: DataPoint | DataPointDate;
823
+ index: number;
824
+ key: string;
825
+ } ) => void;
709
826
  /**
710
827
  * Whether to show tooltips on hover. False by default.
711
828
  */