@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
package/src/index.ts CHANGED
@@ -32,6 +32,7 @@ export {
32
32
  GlobalChartsProvider,
33
33
  useGlobalChartsContext,
34
34
  useGlobalChartsTheme,
35
+ useChartFormatting,
35
36
  GlobalChartsContext,
36
37
  defaultTheme,
37
38
  useChartScopeElement,
@@ -55,6 +56,7 @@ export type {
55
56
  ChartTheme,
56
57
  CompleteChartTheme,
57
58
  AxisOptions,
59
+ ChartFormatting,
58
60
  TickResolution,
59
61
  ScaleOptions,
60
62
  LegendItemStyles,
@@ -126,6 +128,7 @@ export type { SparklineProps, GradientConfig, SparklineDataPoint } from './chart
126
128
 
127
129
  // Utilities
128
130
  export {
131
+ getBucketInfo,
129
132
  parseAsLocalDate,
130
133
  formatMetricValue,
131
134
  formatPercentage,
@@ -134,3 +137,4 @@ export {
134
137
  } from './utils';
135
138
  export * from './utils/color-utils';
136
139
  export type { MetricValueType } from './utils';
140
+ export type { BucketInfo } from './types';
@@ -21,25 +21,39 @@ import {
21
21
  resolveCssVariable,
22
22
  normalizeColorToHex,
23
23
  } from '../../utils';
24
+ import { sanitizeFormatting } from '../../utils/date-formatting';
24
25
  // Imported from the module rather than the `chart-scope` barrel: the barrel also pulls `use-standalone-scope-class`, which imports `GlobalChartsContext` back from this file. That cycle resolves today only because the binding is read lazily inside the hook body.
25
26
  import { ChartScopeContext } from '../chart-scope/chart-scope-context';
26
27
  import { getChartColor, type ColorCache } from './private/get-chart-color';
27
28
  import { SERIES_PALETTE_POINTERS } from './private/series-palette';
28
- import { themeOverrideVars } from './private/theme-override-vars';
29
- import { withCatalogPointers } from './private/with-catalog-pointers';
30
29
  import { defaultTheme } from './themes';
31
30
  import type { GlobalChartsContextValue, ChartRegistration } from './types';
32
31
  import type { ChartTheme, CompleteChartTheme } from '../../types';
33
- import type { CSSProperties, FC, ReactNode } from 'react';
32
+ import type { FC, ReactNode } from 'react';
34
33
 
35
34
  export const GlobalChartsContext = createContext< GlobalChartsContextValue | null >( null );
36
35
 
37
36
  export interface GlobalChartsProviderProps {
38
37
  children: ReactNode;
39
38
  theme?: Partial< ChartTheme >;
39
+ /**
40
+ * BCP-47 language tag every date label is rendered in, e.g. `de-DE`.
41
+ * Defaults to the viewer's browser locale.
42
+ */
43
+ locale?: string;
44
+ /**
45
+ * IANA time zone every date label is dated in, e.g. `Asia/Tokyo`.
46
+ * Defaults to the viewer's browser time zone.
47
+ */
48
+ timeZone?: string;
40
49
  }
41
50
 
42
- export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { children, theme } ) => {
51
+ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
52
+ children,
53
+ theme,
54
+ locale,
55
+ timeZone,
56
+ } ) => {
43
57
  const [ charts, setCharts ] = useState< Map< string, ChartRegistration > >( () => new Map() );
44
58
  // Track hidden series per chart: chartId -> Set<seriesLabel>
45
59
  const [ hiddenSeries, setHiddenSeries ] = useState< Map< string, Set< string > > >(
@@ -59,20 +73,11 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { childre
59
73
  setScopeNode( node );
60
74
  }, [] );
61
75
 
62
- // themeOverrideVars reads the raw `theme` prop, never `providerTheme` — feeding it the restored theme below would make an overridden role's pointer look like a self-reference and drop the var (see themeOverrideVars' own doc comment).
63
- const { vars: overrideVars, roles: overriddenRoles } = useMemo(
64
- () => themeOverrideVars( theme ),
76
+ const providerTheme: CompleteChartTheme = useMemo(
77
+ () => ( theme ? mergeThemes( defaultTheme, theme ) : defaultTheme ),
65
78
  [ theme ]
66
79
  );
67
80
 
68
- const providerTheme: CompleteChartTheme = useMemo( () => {
69
- if ( ! theme ) {
70
- return defaultTheme;
71
- }
72
-
73
- return withCatalogPointers( mergeThemes( defaultTheme, theme ), overriddenRoles );
74
- }, [ theme, overriddenRoles ] );
75
-
76
81
  // Cache expensive color computations that only change when theme colors change
77
82
  // Using useState + useLayoutEffect instead of useMemo to ensure CSS variables
78
83
  // in <style> tags are applied to the DOM before we try to resolve them
@@ -88,14 +93,10 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { childre
88
93
  // Useful for animations that should only run after the color palette is resolved
89
94
  const [ isColorPaletteResolved, setIsColorPaletteResolved ] = useState( false );
90
95
 
91
- // Compute color cache after DOM is updated (so CSS variables are available)
92
- // Resolves CSS variables from the wrapper element's scope to handle scoped variables
93
- // Note: Only re-runs when providerTheme changes, not when wrapper element changes.
94
- // This is intentional, as wrapperRef is expected to be stable for the lifetime of the provider.
95
- // A remount is not a gap in that: effects always run on mount, so a new instance resolves
96
- // against its own node. Only a node swap *within* one instance would go unseen, and the catalog
97
- // is declared on the `.a8c-charts-scope` class rather than on a particular element, so the
98
- // replacement carries the same computed values anyway.
96
+ // A layout effect rather than a memo: the catalog reaches the wrapper as a stylesheet, which
97
+ // must be applied before `getComputedStyle` can answer. Mount only — the slots are a fixed
98
+ // manifest and `wrapperRef` is stable for the provider's life.
99
+
99
100
  useLayoutEffect( () => {
100
101
  setIsColorPaletteResolved( false );
101
102
  const resolvedColors: string[] = [];
@@ -104,13 +105,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { childre
104
105
  let minHue = 360;
105
106
  let maxHue = 0;
106
107
 
107
- // Resolved from the theme rather than from `SERIES_PALETTE_POINTERS`, and it has to be. In a
108
- // browser the two are equivalent — both name the same slots, and the wrapper's theme-layer
109
- // vars answer either. But `withCatalogPointers` puts the consumer's own color in each
110
- // pointer's terminal position, and that literal is the only carrier for the palette where
111
- // `getComputedStyle` resolves nothing: SSR and jsdom. Walking the manifest instead makes
112
- // every consumer palette collapse to the catalog seed there.
113
- for ( const color of providerTheme.colors ?? SERIES_PALETTE_POINTERS ) {
108
+ for ( const color of SERIES_PALETTE_POINTERS ) {
114
109
  // Normalize color to hex format, handling CSS variables, RGB, HSL, etc.
115
110
  // This uses normalizeColorToHex which resolves CSS variables and converts
116
111
  // rgb(), rgba(), hsl() formats to hex
@@ -145,7 +140,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { childre
145
140
  minHue,
146
141
  maxHue,
147
142
  } );
148
- }, [ providerTheme ] );
143
+ }, [] );
149
144
 
150
145
  useEffect( () => {
151
146
  if ( colorCache.colors.length > 0 ) {
@@ -157,12 +152,8 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { childre
157
152
  () => new Map()
158
153
  );
159
154
 
160
- // Reset group color mappings when the resolved palette changes.
161
- //
162
- // Keyed on the resolved colors rather than on `providerTheme.colors`, which holds five
163
- // catalog pointers and so does not move with a `theme.colors` change — a consumer's colors
164
- // reach the palette through the theme-layer vars on the wrapper. Keying on content also stops
165
- // a consumer passing an inline `theme` object from resetting the map on every render.
155
+ // Keyed on the resolved colors rather than the cache object, so a consumer passing an inline
156
+ // `theme` cannot reset the map on every render.
166
157
  const paletteKey = colorCache.colors.join( ',' );
167
158
 
168
159
  useEffect( () => {
@@ -358,6 +349,12 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { childre
358
349
  [ hiddenSeries ]
359
350
  );
360
351
 
352
+ // Held as one object so a chart's formatting memos key on a single stable reference.
353
+ const formatting = useMemo(
354
+ () => sanitizeFormatting( { locale, timeZone } ),
355
+ [ locale, timeZone ]
356
+ );
357
+
361
358
  const value: GlobalChartsContextValue = useMemo(
362
359
  () => ( {
363
360
  charts,
@@ -365,6 +362,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { childre
365
362
  unregisterChart,
366
363
  getChartData,
367
364
  theme: providerTheme,
365
+ formatting,
368
366
  getElementStyles,
369
367
  toggleSeriesVisibility,
370
368
  setSeriesVisibility,
@@ -381,6 +379,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { childre
381
379
  unregisterChart,
382
380
  getChartData,
383
381
  providerTheme,
382
+ formatting,
384
383
  getElementStyles,
385
384
  toggleSeriesVisibility,
386
385
  setSeriesVisibility,
@@ -399,7 +398,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { childre
399
398
  ref={ setWrapperNode }
400
399
  className={ CHART_SCOPE_CLASS }
401
400
  data-testid="charts-scope"
402
- style={ { display: 'contents', ...overrideVars } as CSSProperties }
401
+ style={ { display: 'contents' } }
403
402
  >
404
403
  <ChartScopeContext.Provider value={ scopeNode }>{ children }</ChartScopeContext.Provider>
405
404
  </div>
@@ -0,0 +1,22 @@
1
+ import { useContext } from 'react';
2
+ import { GlobalChartsContext } from '../global-charts-provider';
3
+ import type { ChartFormatting } from '../../../types';
4
+
5
+ // A stable reference, so a chart mounted without a provider doesn't rebuild its
6
+ // formatting memos on every render.
7
+ const RUNTIME_FORMATTING: ChartFormatting = {};
8
+
9
+ /**
10
+ * The locale and time zone dates are rendered in, as set on `GlobalChartsProvider`.
11
+ *
12
+ * Falls back to the runtime's own rather than throwing: charts render outside a
13
+ * provider, and a host that sets neither gets exactly the browser-default
14
+ * behavior this package had before the context existed.
15
+ *
16
+ * @return The host's formatting context.
17
+ */
18
+ export const useChartFormatting = (): ChartFormatting => {
19
+ const context = useContext( GlobalChartsContext );
20
+
21
+ return context?.formatting ?? RUNTIME_FORMATTING;
22
+ };
@@ -3,6 +3,7 @@ export { useGlobalChartsContext } from './hooks/use-global-charts-context';
3
3
  export { useChartId } from './hooks/use-chart-id';
4
4
  export { useChartRegistration } from './hooks/use-chart-registration';
5
5
  export { useGlobalChartsTheme } from './hooks/use-global-charts-theme';
6
+ export { useChartFormatting } from './hooks/use-chart-formatting';
6
7
  export type {
7
8
  GlobalChartsContextValue,
8
9
  ChartRegistration,
@@ -0,0 +1,42 @@
1
+ import { SERIES_PALETTE_POINTERS } from './series-palette';
2
+
3
+ /*
4
+ * The `var()` chain for each color JS has to hand to something. Nothing here is resolved in JS —
5
+ * the chain lands on the element; the terminal literal covers SSR and jsdom. See TOKENS.md.
6
+ */
7
+ export const CATALOG_POINTERS = {
8
+ background: 'var(--a8c-charts-color-background, #fff)',
9
+ labelAxis: 'var(--a8c-charts-color-label-axis, #1e1e1e)',
10
+ grid: 'var(--a8c-charts-color-grid, #dbdbdb)',
11
+ // The y pair resolves to `none`: that axis carries labels only until a consumer declares them.
12
+ axisX: 'var(--a8c-charts-color-axis-x, #dbdbdb)',
13
+ tickX: 'var(--a8c-charts-color-tick-x, #dbdbdb)',
14
+ axisY: 'var(--a8c-charts-color-axis-y, none)',
15
+ tickY: 'var(--a8c-charts-color-tick-y, none)',
16
+ annotation: 'var(--a8c-charts-color-annotation, #1e1e1e)',
17
+ surface: 'var(--a8c-charts-color-surface, #fff)',
18
+ surfaceSecondary: 'var(--a8c-charts-color-surface-secondary, #f4f4f4)',
19
+ // Only for `renderMainMetric`, which hands a consumer's own markup something to paint with.
20
+ trendUp: 'var(--a8c-charts-color-trend-up, #008030)',
21
+ trendDown: 'var(--a8c-charts-color-trend-down, #cc1818)',
22
+ series: SERIES_PALETTE_POINTERS,
23
+ } as const;
24
+
25
+ /**
26
+ * The annotation parts visx paints, in the shape `@visx/annotation` takes. `radius`
27
+ * rides along as the base the theme and the per-datum styles merge onto.
28
+ */
29
+ export const ANNOTATION_POINTERS = {
30
+ label: {
31
+ anchorLineStroke: CATALOG_POINTERS.annotation,
32
+ backgroundFill: CATALOG_POINTERS.surface,
33
+ },
34
+ connector: {
35
+ stroke: CATALOG_POINTERS.annotation,
36
+ },
37
+ circleSubject: {
38
+ stroke: 'transparent',
39
+ fill: CATALOG_POINTERS.annotation,
40
+ radius: 5,
41
+ },
42
+ } as const;
@@ -1,19 +1,9 @@
1
1
  /**
2
- * The series palette's slot manifest.
3
- *
4
- * Which slots exist is a fact about the catalog `chart-scope.scss` emits, not a theme value, so it
5
- * is declared here rather than in `themes.ts`, which now derives `defaultTheme.colors` from it.
6
- *
7
- * That is as far as the separation goes, and the limit is worth knowing before the field is
8
- * deleted. `GlobalChartsProvider` still resolves `providerTheme.colors`, not this manifest,
9
- * because `withCatalogPointers` parks the consumer's own color in each pointer's terminal position
10
- * and that literal is the palette's only carrier where `getComputedStyle` resolves nothing — SSR
11
- * and jsdom. Swapping the provider onto this manifest passes in a browser and collapses every
12
- * consumer palette to the catalog seed under jsdom; 24 tests say so. Removing `colors` therefore
13
- * needs a different home for that literal first, not just a change of source here.
2
+ * The series palette's slot manifest. Which slots exist is a fact about the catalog
3
+ * `chart-scope.scss` emits, not a theme value, so it is declared here rather than in `themes.ts`.
14
4
  */
15
5
 
16
- /** How many series-palette slots the catalog emits. `theme.colors` entries past this are ignored. */
6
+ /** How many series-palette slots the catalog emits. */
17
7
  export const SERIES_SLOT_COUNT = 5;
18
8
 
19
9
  /**
@@ -1,3 +1,4 @@
1
+ import { ANNOTATION_POINTERS, CATALOG_POINTERS } from '../private/catalog-pointers';
1
2
  import { defaultTheme } from '../themes';
2
3
 
3
4
  const flatten = ( value: unknown ): string[] => {
@@ -13,32 +14,31 @@ const flatten = ( value: unknown ): string[] => {
13
14
  return [];
14
15
  };
15
16
 
16
- describe( 'defaultTheme', () => {
17
- const strings = flatten( defaultTheme );
17
+ const pointerStrings = [ ...flatten( CATALOG_POINTERS ), ...flatten( ANNOTATION_POINTERS ) ];
18
18
 
19
+ describe( 'catalog pointers', () => {
19
20
  it( 'holds no --wpds-* mapping — that lives only in the catalog stylesheet', () => {
20
- expect( strings.filter( value => value.includes( '--wpds-' ) ) ).toEqual( [] );
21
+ expect( pointerStrings.filter( value => value.includes( '--wpds-' ) ) ).toEqual( [] );
21
22
  } );
22
23
 
23
- it( 'points every themed color at an --a8c-charts-* catalog role', () => {
24
- const varStrings = strings.filter( value => value.startsWith( 'var(' ) );
24
+ it( 'points every color at an --a8c-charts-* catalog role', () => {
25
+ const varStrings = pointerStrings.filter( value => value.startsWith( 'var(' ) );
25
26
 
26
27
  expect( varStrings.length ).toBeGreaterThan( 0 );
27
28
  expect( varStrings.every( value => value.startsWith( 'var(--a8c-charts-' ) ) ).toBe( true );
28
29
  } );
29
30
 
30
- // The palette slots past the first are the exception, below: a terminal literal there would be a color nobody chose.
31
- it( 'gives every catalog pointer a terminal literal for the SSR and jsdom paths', () => {
32
- const varStrings = strings
31
+ // Palette slots past the first are the exception: a literal there would be a color nobody chose.
32
+ it( 'gives every pointer a terminal literal for the SSR and jsdom paths', () => {
33
+ const varStrings = pointerStrings
33
34
  .filter( value => value.startsWith( 'var(--a8c-charts-' ) )
34
35
  .filter( value => ! /^var\( *--a8c-charts-color-series-[2-9] *\)$/.test( value ) );
35
36
 
36
37
  expect( varStrings.every( value => value.includes( ',' ) ) ).toBe( true );
37
38
  } );
38
39
 
39
- // Only slot 1 seeds a palette. The rest resolve to nothing until a consumer sets them, and the provider drops what resolves to nothing, so the palette compacts instead of repeating one color.
40
40
  it( 'leaves the palette slots past the first with no default of their own', () => {
41
- expect( defaultTheme.colors ).toEqual( [
41
+ expect( CATALOG_POINTERS.series ).toEqual( [
42
42
  'var(--a8c-charts-color-series-1, #3858e9)',
43
43
  'var(--a8c-charts-color-series-2)',
44
44
  'var(--a8c-charts-color-series-3)',
@@ -47,3 +47,15 @@ describe( 'defaultTheme', () => {
47
47
  ] );
48
48
  } );
49
49
  } );
50
+
51
+ describe( 'defaultTheme', () => {
52
+ // A color has one route, a `--a8c-charts-color-*` role set in CSS. One reappearing
53
+ // here would be a second.
54
+ it( 'holds no color at all', () => {
55
+ const colorish = flatten( defaultTheme ).filter( value =>
56
+ /^(#|rgb|hsl|var\(--a8c-charts-color)/.test( value.trim() )
57
+ );
58
+
59
+ expect( colorish ).toEqual( [] );
60
+ } );
61
+ } );