@automattic/charts 3.2.0 → 4.0.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 (109) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/TOKENS.md +53 -58
  3. package/dist/index.cjs +1012 -542
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +72 -29
  6. package/dist/index.d.cts +151 -179
  7. package/dist/index.d.ts +151 -179
  8. package/dist/index.js +1013 -544
  9. package/dist/index.js.map +1 -1
  10. package/package.json +6 -6
  11. package/src/charts/area-chart/area-chart.tsx +39 -25
  12. package/src/charts/area-chart/test/area-chart.test.tsx +4 -33
  13. package/src/charts/area-chart/test/tick-placement.test.tsx +55 -0
  14. package/src/charts/area-chart/test/tooltip-resolution-branches.test.tsx +86 -0
  15. package/src/charts/area-chart/test/tooltip-resolution.test.tsx +69 -0
  16. package/src/charts/area-chart/types.ts +4 -7
  17. package/src/charts/bar-chart/bar-chart.tsx +2 -1
  18. package/src/charts/bar-chart/private/test/use-bar-chart-options-formatting.test.tsx +69 -0
  19. package/src/charts/bar-chart/private/use-bar-chart-options.ts +27 -15
  20. package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -0
  21. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +33 -23
  22. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +138 -182
  23. package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +46 -0
  24. package/src/charts/geo-chart/geo-chart.module.scss +1 -0
  25. package/src/charts/geo-chart/geo-chart.tsx +5 -10
  26. package/src/charts/geo-chart/test/geo-chart.test.tsx +2 -2
  27. package/src/charts/heatmap-chart/heatmap-chart.module.scss +21 -16
  28. package/src/charts/heatmap-chart/heatmap-chart.tsx +45 -28
  29. package/src/charts/heatmap-chart/private/build-calendar-data.ts +3 -0
  30. package/src/charts/heatmap-chart/private/heatmap-legend.tsx +11 -4
  31. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +51 -12
  32. package/src/charts/heatmap-chart/types.ts +2 -2
  33. package/src/charts/leaderboard-chart/hooks/use-fitted-row-count.ts +2 -2
  34. package/src/charts/leaderboard-chart/hooks/use-leaderboard-legend-items.ts +3 -5
  35. package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +31 -19
  36. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +27 -17
  37. package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +14 -0
  38. package/src/charts/leaderboard-chart/test/use-leaderboard-legend-items.test.tsx +2 -9
  39. package/src/charts/line-chart/line-chart.module.scss +10 -18
  40. package/src/charts/line-chart/line-chart.tsx +93 -24
  41. package/src/charts/line-chart/private/line-chart-annotation.tsx +18 -19
  42. package/src/charts/line-chart/test/default-tooltip-formatting.test.tsx +74 -0
  43. package/src/charts/line-chart/test/line-chart.test.tsx +27 -3
  44. package/src/charts/line-chart/test/tick-placement.test.tsx +84 -0
  45. package/src/charts/line-chart/test/tooltip-resolution.test.tsx +140 -0
  46. package/src/charts/line-chart/types.ts +4 -1
  47. package/src/charts/pie-chart/pie-chart.module.scss +13 -1
  48. package/src/charts/pie-chart/pie-chart.tsx +30 -40
  49. package/src/charts/pie-chart/test/pie-chart.test.tsx +56 -0
  50. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +8 -3
  51. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +23 -37
  52. package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +50 -0
  53. package/src/charts/private/chart-layout/chart-layout.module.scss +3 -2
  54. package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +1 -1
  55. package/src/charts/private/test/axis-formatting.test.tsx +85 -0
  56. package/src/charts/private/test/time-axis-formatting.test.ts +109 -0
  57. package/src/charts/private/test/time-axis-options.test.ts +267 -0
  58. package/src/charts/private/test/time-axis-tick-invariants.test.ts +153 -0
  59. package/src/charts/private/test/time-axis-tick-values.test.ts +280 -0
  60. package/src/charts/private/test/time-axis.test.ts +9 -69
  61. package/src/charts/private/time-axis-options.ts +106 -0
  62. package/src/charts/private/time-axis.ts +318 -136
  63. package/src/charts/private/x-zoom/x-zoom.module.scss +3 -3
  64. package/src/charts/private/xy-plot/xy-plot.module.scss +8 -0
  65. package/src/charts/sparkline/test/sparkline.test.tsx +29 -56
  66. package/src/charts/sparkline/types.ts +1 -1
  67. package/src/components/legend/private/base-legend.module.scss +9 -5
  68. package/src/components/tooltip/accessible-tooltip.tsx +13 -15
  69. package/src/components/tooltip/base-tooltip.module.scss +3 -3
  70. package/src/components/tooltip/private/bounded-tooltip.tsx +172 -0
  71. package/src/components/tooltip/test/accessible-tooltip.test.tsx +2 -3
  72. package/src/components/tooltip/test/bounded-tooltip.test.tsx +129 -0
  73. package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +279 -0
  74. package/src/components/tooltip/xy-chart-tooltip.tsx +288 -0
  75. package/src/components/trend-indicator/trend-indicator.module.scss +2 -2
  76. package/src/hooks/test/use-xychart-theme.test.tsx +16 -3
  77. package/src/hooks/use-xychart-theme.ts +25 -9
  78. package/src/index.ts +4 -0
  79. package/src/providers/chart-context/global-charts-provider.tsx +37 -38
  80. package/src/providers/chart-context/hooks/use-chart-formatting.ts +22 -0
  81. package/src/providers/chart-context/index.ts +1 -0
  82. package/src/providers/chart-context/private/catalog-pointers.ts +42 -0
  83. package/src/providers/chart-context/private/series-palette.ts +3 -13
  84. package/src/providers/chart-context/test/{sparse-theme.test.ts → catalog-pointers.test.ts} +22 -10
  85. package/src/providers/chart-context/test/chart-context.test.tsx +177 -792
  86. package/src/providers/chart-context/test/use-global-charts-theme.test.tsx +7 -25
  87. package/src/providers/chart-context/themes.ts +7 -52
  88. package/src/providers/chart-context/types.ts +3 -0
  89. package/src/styles/chart-scope.scss +39 -62
  90. package/src/styles/test/chart-scope.test.ts +19 -47
  91. package/src/test-utils/series-fixtures.ts +31 -0
  92. package/src/types.ts +79 -166
  93. package/src/utils/bucket-info.ts +87 -0
  94. package/src/utils/color-utils.ts +0 -27
  95. package/src/utils/date-formatting.ts +174 -0
  96. package/src/utils/index.ts +3 -0
  97. package/src/utils/test/bucket-info.test.ts +185 -0
  98. package/src/utils/test/color-utils.test.ts +1 -48
  99. package/src/utils/test/date-formatting.test.ts +89 -0
  100. package/src/utils/test/get-styles.test.ts +10 -7
  101. package/src/utils/test/merge-themes.test.ts +18 -46
  102. package/src/visx/types.ts +29 -2
  103. package/src/hooks/test/use-xychart-theme-provider-override.test.tsx +0 -28
  104. package/src/providers/chart-context/private/theme-override-vars.ts +0 -131
  105. package/src/providers/chart-context/private/with-catalog-pointers.ts +0 -93
  106. package/src/providers/chart-context/test/theme-override-vars-guard.test.tsx +0 -29
  107. package/src/providers/chart-context/test/theme-override-vars.test.ts +0 -183
  108. package/src/providers/chart-context/test/with-catalog-pointers.test.ts +0 -104
  109. package/src/styles/test/wpds-fallbacks.test.ts +0 -131
@@ -22,21 +22,12 @@ describe( 'useGlobalChartsTheme', () => {
22
22
 
23
23
  describe( 'with GlobalChartsProvider', () => {
24
24
  it( 'should return merged theme when provider has custom theme', () => {
25
- const customTheme = { colors: [ '#FF0000', '#00FF00', '#0000FF' ] };
26
- const wrapper = createWrapper( customTheme );
25
+ const wrapper = createWrapper( { tickLength: 9 } );
27
26
 
28
27
  const { result } = renderHook( () => useGlobalChartsTheme(), { wrapper } );
29
28
 
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
- ] );
38
- // Other properties should still come from default theme
39
- expect( result.current.backgroundColor ).toBe( defaultTheme.backgroundColor );
29
+ expect( result.current.tickLength ).toBe( 9 );
30
+ // Everything the consumer did not set still comes from the default theme.
40
31
  expect( result.current.gridStyles ).toEqual( defaultTheme.gridStyles );
41
32
  } );
42
33
 
@@ -46,13 +37,12 @@ describe( 'useGlobalChartsTheme', () => {
46
37
  const { result } = renderHook( () => useGlobalChartsTheme(), { wrapper } );
47
38
 
48
39
  expect( result.current ).toEqual( defaultTheme );
49
- expect( result.current.colors ).toBe( defaultTheme.colors );
50
40
  } );
51
41
  } );
52
42
 
53
43
  describe( 'theme stability', () => {
54
44
  it( 'should return the same theme object when provider theme does not change', () => {
55
- const customTheme = { colors: [ '#FF0000' ] };
45
+ const customTheme = { tickLength: 9 };
56
46
  const wrapper = createWrapper( customTheme );
57
47
 
58
48
  const { result, rerender } = renderHook( () => useGlobalChartsTheme(), { wrapper } );
@@ -68,20 +58,12 @@ describe( 'useGlobalChartsTheme', () => {
68
58
 
69
59
  describe( 'edge cases', () => {
70
60
  it( 'should handle null/undefined theme values gracefully', () => {
71
- const customTheme = { colors: undefined };
72
- const wrapper = createWrapper( customTheme );
61
+ const customTheme = { tickLength: undefined };
62
+ const wrapper = createWrapper( customTheme as unknown as Partial< ChartTheme > );
73
63
 
74
64
  const { result } = renderHook( () => useGlobalChartsTheme(), { wrapper } );
75
65
 
76
- // Should still return a complete theme with undefined colors
77
- expect( result.current.backgroundColor ).toBe( defaultTheme.backgroundColor );
78
- expect( result.current.colors ).toBeUndefined();
79
- } );
80
-
81
- it( 'should not throw when GlobalChartsContext is not available', () => {
82
- expect( () => {
83
- renderHook( () => useGlobalChartsTheme() );
84
- } ).not.toThrow();
66
+ expect( result.current.gridStyles ).toEqual( defaultTheme.gridStyles );
85
67
  } );
86
68
  } );
87
69
  } );
@@ -1,38 +1,24 @@
1
- import { SERIES_PALETTE_POINTERS } from './private/series-palette';
2
1
  import type { CompleteChartTheme } from '../../types';
3
2
 
4
3
  /**
5
- * Default theme configuration
4
+ * Default theme configuration: the shape and spacing a consumer can override.
5
+ *
6
+ * For a color, set the matching `--a8c-charts-color-*` role in CSS instead; the `var()`
7
+ * chains that deliver those live in `private/catalog-pointers.ts`.
6
8
  */
7
9
  const defaultTheme: CompleteChartTheme = {
8
- backgroundColor: 'var(--a8c-charts-color-background, #fff)',
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.
11
- labelTextColor: 'var(--a8c-charts-color-label-on-fill, #FFFFFF)',
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 ],
17
10
  gridStyles: {
18
- stroke: 'var(--a8c-charts-color-grid, #dbdbdb)',
19
11
  strokeWidth: 1,
20
12
  },
21
13
  tickLength: 4,
22
- gridColor: '',
23
- gridColorDark: '',
24
14
  xTickLineStyles: {
25
- stroke: 'var(--a8c-charts-color-tick, #dbdbdb)',
26
15
  strokeWidth: 1,
27
16
  },
28
17
  xAxisLineStyles: {
29
- stroke: 'var(--a8c-charts-color-axis, #dbdbdb)',
30
18
  strokeWidth: 1,
31
19
  },
32
20
  legend: {
33
- labelStyles: {
34
- color: 'var(--a8c-charts-color-label, #1e1e1e)',
35
- },
21
+ labelStyles: {},
36
22
  containerStyles: {},
37
23
  shapeStyles: [],
38
24
  },
@@ -44,42 +30,12 @@ const defaultTheme: CompleteChartTheme = {
44
30
  // elements for axis labels and ticks. Setting `inherit` lets SVG text
45
31
  // pick up the host application's font-family via normal CSS inheritance.
46
32
  svgLabelSmall: {
47
- fill: 'var(--a8c-charts-color-label-axis, #1e1e1e)',
48
33
  fontFamily: 'inherit',
49
34
  },
50
35
  svgLabelBig: { fontFamily: 'inherit' },
51
- annotationStyles: {
52
- label: {
53
- anchorLineStroke: 'var(--a8c-charts-color-annotation, #1e1e1e)',
54
- backgroundFill: 'var(--a8c-charts-color-surface, #fff)',
55
- },
56
- connector: {
57
- stroke: 'var(--a8c-charts-color-annotation, #1e1e1e)',
58
- },
59
- circleSubject: {
60
- stroke: 'transparent',
61
- fill: 'var(--a8c-charts-color-annotation, #1e1e1e)',
62
- radius: 5,
63
- },
64
- },
65
- geoChart: {
66
- featureFillColor: 'var(--a8c-charts-color-surface-secondary, #f4f4f4)',
67
- },
36
+ annotationStyles: {},
68
37
  leaderboardChart: {
69
- rowGap: 12,
70
- columnGap: 4,
71
38
  labelSpacing: 'xs',
72
- // [negative, neutral, positive]
73
- deltaColors: [
74
- 'var(--a8c-charts-color-trend-down, #cc1818)',
75
- 'var(--a8c-charts-color-trend-neutral, #707070)',
76
- 'var(--a8c-charts-color-trend-up, #008030)',
77
- ],
78
- },
79
- conversionFunnelChart: {
80
- backgroundColor: 'var(--a8c-charts-color-surface-secondary, #f4f4f4)',
81
- positiveChangeColor: 'var(--a8c-charts-color-trend-up, #008030)',
82
- negativeChangeColor: 'var(--a8c-charts-color-trend-down, #cc1818)',
83
39
  },
84
40
  lineChart: {
85
41
  lineStyles: {
@@ -101,8 +57,7 @@ const defaultTheme: CompleteChartTheme = {
101
57
  margin: { top: 2, right: 2, bottom: 2, left: 2 },
102
58
  strokeWidth: 1.5,
103
59
  },
104
- // `primaryColor` is left unset so it falls back to the palette's `colors[0]`. The compact
105
- // 11px square / 2px gap is the contribution-graph rhythm, which has no WPDS dimension.
60
+ // The compact 11px square / 2px gap is the contribution-graph rhythm, which has no WPDS dimension.
106
61
  heatmapChart: {
107
62
  compactCellGap: 2,
108
63
  compactCellSize: 11,
@@ -2,6 +2,7 @@ import { CSSProperties, ReactNode } from 'react';
2
2
  import type { BaseLegendItem } from '../../components/legend';
3
3
  import type {
4
4
  BarStyles,
5
+ ChartFormatting,
5
6
  ChartType,
6
7
  CompleteChartTheme,
7
8
  DataPointPercentage,
@@ -37,6 +38,8 @@ export interface GlobalChartsContextValue {
37
38
  unregisterChart: ( id: string ) => void;
38
39
  getChartData: ( id: string ) => ChartRegistration | undefined;
39
40
  theme: CompleteChartTheme;
41
+ /** Locale and time zone every date the charts print is rendered in. */
42
+ formatting: ChartFormatting;
40
43
  getElementStyles: ( params: GetElementStylesParams ) => ElementStyles;
41
44
  // Series visibility management shared by charts, legends, and programmatic controls.
42
45
  toggleSeriesVisibility: ( chartId: string, seriesLabel: string ) => void;
@@ -10,16 +10,6 @@
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
-
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.
22
- // `src/styles/test/chart-scope.test.ts` pins which roles carry the layer.
23
13
  :where(.a8c-charts-scope) {
24
14
  // The series palette. GlobalChartsProvider resolves these five slots at its
25
15
  // wrapper and seeds ColorCache with whatever resolves; a slot that resolves
@@ -35,58 +25,42 @@
35
25
  // admin color. Leading with the admin color costs nothing:
36
26
  // @wordpress/theme writes --wp-admin-theme-color from a provider's own
37
27
  // 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);
55
- --a8c-charts-color-grid: var(--a8c-charts-color-grid-theme, var(--wpds-color-stroke-surface-neutral, #dbdbdb));
56
- --a8c-charts-color-axis: var(--a8c-charts-color-axis-theme, var(--wpds-color-stroke-surface-neutral, #dbdbdb));
57
- --a8c-charts-color-tick: var(--a8c-charts-color-tick-theme, var(--wpds-color-stroke-surface-neutral, #dbdbdb));
58
- --a8c-charts-color-label: var(--wpds-color-foreground-content-neutral, #1e1e1e);
59
- --a8c-charts-color-label-secondary: var(--wpds-color-foreground-content-neutral-weak, #707070);
60
- --a8c-charts-color-label-inverse: var(--wpds-color-foreground-interactive-neutral-strong, #f0f0f0);
61
- // White label text sits on arbitrary series colors, so it has no WPDS
62
- // content-foreground equivalent.
63
- --a8c-charts-color-label-on-fill: var(--a8c-charts-color-label-on-fill-theme, #fff);
28
+ --a8c-charts-color-series-1: var(--wp-admin-theme-color, var(--wpds-color-foreground-interactive-brand));
29
+ --a8c-charts-color-grid: var(--wpds-color-stroke-surface-neutral);
30
+ --a8c-charts-color-axis-x: var(--wpds-color-stroke-surface-neutral);
31
+ --a8c-charts-color-tick-x: var(--wpds-color-stroke-surface-neutral);
32
+ // The y axis carries labels only until one of these is declared. `none`
33
+ // rather than `transparent` keeps the line out of hit-testing too.
34
+ --a8c-charts-color-axis-y: none;
35
+ --a8c-charts-color-tick-y: none;
36
+ --a8c-charts-color-label: var(--wpds-color-foreground-content-neutral);
37
+ --a8c-charts-color-label-secondary: var(--wpds-color-foreground-content-neutral-weak);
38
+ // Label text on a filled surface — a pie segment, a heatmap cell.
39
+ --a8c-charts-color-label-inverse: var(--wpds-color-foreground-interactive-neutral-strong);
64
40
  // The plate behind a pie-slice label. Transparent by default, so a label
65
41
  // 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);
67
- // The SVG axis and tick labels. `svgLabelSmall.fill` publishes THIS role, so
68
- // deriving from --a8c-charts-color-label is safe and wanted: the theme field
69
- // moves only these labels, while setting the broad role in CSS still moves
70
- // every label at once. (--a8c-charts-color-surface cannot derive the same
71
- // way — see its comment; there the theme field owns the broad role.)
72
- --a8c-charts-color-label-axis: var(--a8c-charts-color-label-axis-theme, var(--a8c-charts-color-label));
73
- --a8c-charts-color-annotation: var(--wpds-color-foreground-content-neutral, #1e1e1e);
74
- --a8c-charts-color-trend-up: var(--wpds-color-foreground-content-success-weak, #008030);
75
- --a8c-charts-color-trend-down: var(--wpds-color-foreground-content-error-weak, #cc1818);
76
- --a8c-charts-color-trend-neutral: var(--wpds-color-foreground-content-neutral-weak, #707070);
77
- --a8c-charts-color-background: var(--a8c-charts-color-background-theme, var(--wpds-color-background-surface-neutral-strong, #fff));
78
- // Surfaces that float over the chart — tooltips, the annotation label, the
79
- // zoom-reset control. Same WPDS token as --a8c-charts-color-background, and
80
- // deliberately NOT derived from it: `backgroundColor` publishes the
81
- // background role, so deriving would let that theme field repaint these too.
82
- // Sibling of the background role, not a child of it.
83
- --a8c-charts-color-surface: var(--wpds-color-background-surface-neutral-strong, #fff);
84
- --a8c-charts-color-surface-secondary: var(--wpds-color-background-surface-neutral-weak, #f4f4f4);
85
- --a8c-charts-color-track: var(--wpds-color-background-track-neutral-weak, #f0f0f0);
42
+ // The literal is load-bearing: a `fill` reading a fallback-less var() on an
43
+ // undeclared property drops the declaration and paints black instead.
44
+ --a8c-charts-color-label-background: transparent;
45
+ // The SVG axis and tick labels. Narrower than --a8c-charts-color-label and
46
+ // derived from it, so setting the broad role moves every label at once while
47
+ // setting this one moves only the axis.
48
+ --a8c-charts-color-label-axis: var(--a8c-charts-color-label);
49
+ --a8c-charts-color-annotation: var(--wpds-color-foreground-content-neutral);
50
+ --a8c-charts-color-trend-up: var(--wpds-color-foreground-content-success-weak);
51
+ --a8c-charts-color-trend-down: var(--wpds-color-foreground-content-error-weak);
52
+ --a8c-charts-color-trend-neutral: var(--wpds-color-foreground-content-neutral-weak);
53
+ --a8c-charts-color-background: var(--wpds-color-background-surface-neutral-strong);
54
+ // Surfaces floating over the chart — tooltips, the annotation label, the
55
+ // zoom-reset control. Same WPDS token as --a8c-charts-color-background but
56
+ // deliberately not derived from it, so either can move without the other.
57
+ --a8c-charts-color-surface: var(--wpds-color-background-surface-neutral-strong);
58
+ --a8c-charts-color-surface-secondary: var(--wpds-color-background-surface-neutral-weak);
59
+ --a8c-charts-color-track: var(--wpds-color-background-track-neutral-weak);
86
60
  // Translucent dark surface; no WPDS token carries this.
87
61
  --a8c-charts-color-tooltip-surface: rgb(0 0 0 / 85%);
88
- --a8c-charts-color-zoom-selection: var(--wpds-color-background-interactive-brand-strong, var(--wp-admin-theme-color, #3858e9));
89
- --a8c-charts-color-zoom-selection-stroke: var(--wpds-color-stroke-interactive-brand, var(--wp-admin-theme-color, #3858e9));
62
+ --a8c-charts-color-zoom-selection: var(--wpds-color-background-interactive-brand-strong);
63
+ --a8c-charts-color-zoom-selection-stroke: var(--wpds-color-stroke-interactive-brand);
90
64
 
91
65
  // @wordpress/theme 1.0.0 removed the whole --wpds-elevation-* group while the
92
66
  // elevation model is redefined (gutenberg#80099). These carry the removed spec
@@ -102,10 +76,13 @@
102
76
  0 6px 6px 0 #00000008,
103
77
  0 8px 8px 0 #00000005;
104
78
 
105
- --a8c-charts-motion-duration-entrance: var(--wpds-motion-duration-xl, 400ms);
106
- --a8c-charts-motion-easing-entrance: var(--wpds-motion-easing-expressive, cubic-bezier(0.25, 0, 0, 1));
107
- --a8c-charts-border-radius-bar: var(--wpds-border-radius-md, 4px);
108
- --a8c-charts-border-radius-cell: var(--wpds-border-radius-sm, 2px);
79
+ --a8c-charts-motion-duration-entrance: var(--wpds-motion-duration-xl);
80
+ --a8c-charts-motion-easing-entrance: var(--wpds-motion-easing-expressive);
81
+ --a8c-charts-border-radius-bar: var(--wpds-border-radius-md);
82
+ --a8c-charts-border-radius-cell: var(--wpds-border-radius-sm);
109
83
  // Pill shape; no WPDS radius reaches it.
110
84
  --a8c-charts-border-radius-leaderboard-bar: 9999px;
85
+
86
+ --a8c-charts-dimension-leaderboard-row-gap: var(--wpds-dimension-gap-md);
87
+ --a8c-charts-dimension-leaderboard-column-gap: var(--wpds-dimension-gap-xs);
111
88
  }
@@ -1,13 +1,11 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import {
4
- THEME_LAYERED_ROLES,
5
- themeLayerVar,
6
- } from '../../providers/chart-context/private/theme-override-vars';
7
3
 
8
4
  const stylesheet = readFileSync( join( __dirname, '..', 'chart-scope.scss' ), 'utf8' );
9
5
  const tokensDoc = readFileSync( join( __dirname, '..', '..', '..', 'TOKENS.md' ), 'utf8' );
10
6
 
7
+ const normalize = ( value: string ): string => value.replace( /\s+/g, ' ' ).trim();
8
+
11
9
  type Entry = {
12
10
  /** The custom property this one reads, or null when the value is a literal. */
13
11
  reads: string | null;
@@ -15,26 +13,6 @@ type Entry = {
15
13
  fallback: string | null;
16
14
  };
17
15
 
18
- const normalize = ( value: string ): string => value.replace( /\s+/g, ' ' ).trim();
19
-
20
- /**
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
- *
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
- *
25
- * @param name - The declared property name.
26
- * @param value - Its normalized value.
27
- * @return The value with a leading `var(<name>-theme, … )` wrapper removed, or the empty string where that wrapper carried no fallback.
28
- */
29
- function stripThemeLayer( name: string, value: string ): string {
30
- const layered = new RegExp(
31
- `^var\\(\\s*${ themeLayerVar( name ) }\\s*(?:,\\s*(.*))?\\)$`,
32
- 's'
33
- ).exec( value );
34
-
35
- return layered ? ( layered[ 1 ] ?? '' ).trim() : value;
36
- }
37
-
38
16
  /**
39
17
  * Parses each catalog declaration in the stylesheet into the property it reads and the fallback it carries. Splitting the `var()` arguments at the *first* comma keeps a fallback that is itself a function call intact — `cubic-bezier(0.25, 0, 0, 1)` would otherwise be truncated at its first argument.
40
18
  *
@@ -57,13 +35,7 @@ function parseStylesheet(): Map< string, Entry > {
57
35
  }
58
36
 
59
37
  const name = declaration.slice( 0, separator ).trim();
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
- }
38
+ const value = normalize( declaration.slice( separator + 1 ) );
67
39
 
68
40
  const wrapped = /^var\(\s*(.*)\)$/s.exec( value );
69
41
 
@@ -145,24 +117,24 @@ describe( 'chart scope catalog', () => {
145
117
  } );
146
118
 
147
119
  // The tables in TOKENS.md restate the stylesheet for consumers who only have the
148
- // published package. This is the check that stops the two drifting apart.
149
- it.each( [ ...declared.keys() ] )( 'documents %s with the value it is declared with', token => {
150
- expect( documented.get( token ) ).toEqual( declared.get( token ) );
120
+ // published package. Mapping (what a role reads) must match the source. The
121
+ // Fallback column for `--wpds-*` mappings is injected at build time, so only
122
+ // literals and `--a8c-charts-*` derivations are compared as fallbacks.
123
+ it.each( [ ...declared.keys() ] )( 'documents %s with the mapping it is declared with', token => {
124
+ expect( documented.get( token )?.reads ).toEqual( declared.get( token )?.reads );
151
125
  } );
152
126
 
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.
154
- it.each( THEME_LAYERED_ROLES )( 'declares %s reading its theme layer first', role => {
155
- expect( stylesheet ).toMatch(
156
- new RegExp( `${ role }:\\s*var\\(\\s*${ themeLayerVar( role ) }\\s*[,)]` )
157
- );
158
- } );
159
-
160
- it( 'gives a theme layer to the overridable roles and to nothing else', () => {
161
- const layered = [ ...declared.keys() ].filter( role =>
162
- new RegExp( `${ role }:\\s*var\\(\\s*${ themeLayerVar( role ) }\\s*[,)]` ).test( stylesheet )
163
- );
164
-
165
- expect( layered.sort() ).toEqual( [ ...THEME_LAYERED_ROLES ].sort() );
127
+ it.each(
128
+ [ ...declared.keys() ].filter( token => {
129
+ const entry = declared.get( token );
130
+
131
+ return (
132
+ entry?.reads?.startsWith( '--wpds-' ) !== true &&
133
+ entry?.fallback?.includes( '--wpds-' ) !== true
134
+ );
135
+ } )
136
+ )( 'documents %s with the fallback it is declared with', token => {
137
+ expect( documented.get( token )?.fallback ).toEqual( declared.get( token )?.fallback );
166
138
  } );
167
139
 
168
140
  it( 'scopes the catalog to :where(.a8c-charts-scope) rather than :root', () => {
@@ -0,0 +1,31 @@
1
+ import type { SeriesData } from '../types';
2
+
3
+ const series = ( start: string, count: number, stepInHours: number ): SeriesData[] => [
4
+ {
5
+ label: 'views',
6
+ data: Array.from( { length: count }, ( _, index ) => ( {
7
+ date: new Date( new Date( start ).getTime() + index * stepInHours * 60 * 60 * 1000 ),
8
+ value: index,
9
+ } ) ),
10
+ },
11
+ ];
12
+
13
+ /**
14
+ * One series of hourly points starting at `start`, `count` long.
15
+ *
16
+ * @param start - ISO instant of the first point.
17
+ * @param count - Number of points.
18
+ * @return A single-series fixture.
19
+ */
20
+ export const hourlySeries = ( start: string, count: number ): SeriesData[] =>
21
+ series( start, count, 1 );
22
+
23
+ /**
24
+ * One series of daily points starting at `start`, `count` long.
25
+ *
26
+ * @param start - ISO instant of the first point.
27
+ * @param count - Number of points.
28
+ * @return A single-series fixture.
29
+ */
30
+ export const dailySeries = ( start: string, count: number ): SeriesData[] =>
31
+ series( start, count, 24 );