@classic-homes/charts-core 0.1.36 → 0.1.37

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.
@@ -1,4 +1,4 @@
1
- import { colors, semanticColors, borderRadius, fontFamily } from '@classic-homes/theme-tokens';
1
+ import { semanticColors, colors, borderRadius, fontFamily } from '@classic-homes/theme-tokens';
2
2
 
3
3
  // src/theme/generator.ts
4
4
  function generateLightTheme() {
@@ -270,8 +270,43 @@ function generateDarkTheme() {
270
270
  var lightTheme = generateLightTheme();
271
271
  var darkTheme = generateDarkTheme();
272
272
 
273
- // src/theme/index.ts
273
+ // src/theme/constants.ts
274
274
  var THEME_LIGHT = "classic-light";
275
275
  var THEME_DARK = "classic-dark";
276
276
 
277
- export { THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme };
277
+ // src/theme/semantic.ts
278
+ var lightChartColors = {
279
+ series: lightTheme.color,
280
+ foreground: semanticColors.light.foreground,
281
+ mutedForeground: semanticColors.light.mutedForeground,
282
+ axisPointerLabel: semanticColors.light.mutedForeground,
283
+ divider: colors.gray[600],
284
+ success: semanticColors.light.success,
285
+ destructive: semanticColors.light.destructive
286
+ };
287
+ var darkChartColors = {
288
+ series: darkTheme.color,
289
+ foreground: semanticColors.dark.foreground,
290
+ mutedForeground: semanticColors.dark.mutedForeground,
291
+ // mutedForeground (gray[400]) is too light under the white label text
292
+ axisPointerLabel: colors.gray[600],
293
+ divider: colors.gray[700],
294
+ success: semanticColors.dark.success,
295
+ destructive: semanticColors.dark.destructive
296
+ };
297
+ function getChartColors(themeName) {
298
+ return themeName === THEME_DARK ? darkChartColors : lightChartColors;
299
+ }
300
+ function colorWithAlpha(color, alpha) {
301
+ let hex = /^#([\da-f]{3}|[\da-f]{6})$/i.exec(color)?.[1];
302
+ if (!hex) return color;
303
+ if (hex.length === 3) {
304
+ hex = hex.split("").map((c) => c + c).join("");
305
+ }
306
+ const r = parseInt(hex.slice(0, 2), 16);
307
+ const g = parseInt(hex.slice(2, 4), 16);
308
+ const b = parseInt(hex.slice(4, 6), 16);
309
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
310
+ }
311
+
312
+ export { THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme };
package/dist/index.cjs CHANGED
@@ -272,10 +272,45 @@ function generateDarkTheme() {
272
272
  var lightTheme = generateLightTheme();
273
273
  var darkTheme = generateDarkTheme();
274
274
 
275
- // src/theme/index.ts
275
+ // src/theme/constants.ts
276
276
  var THEME_LIGHT = "classic-light";
277
277
  var THEME_DARK = "classic-dark";
278
278
 
279
+ // src/theme/semantic.ts
280
+ var lightChartColors = {
281
+ series: lightTheme.color,
282
+ foreground: themeTokens.semanticColors.light.foreground,
283
+ mutedForeground: themeTokens.semanticColors.light.mutedForeground,
284
+ axisPointerLabel: themeTokens.semanticColors.light.mutedForeground,
285
+ divider: themeTokens.colors.gray[600],
286
+ success: themeTokens.semanticColors.light.success,
287
+ destructive: themeTokens.semanticColors.light.destructive
288
+ };
289
+ var darkChartColors = {
290
+ series: darkTheme.color,
291
+ foreground: themeTokens.semanticColors.dark.foreground,
292
+ mutedForeground: themeTokens.semanticColors.dark.mutedForeground,
293
+ // mutedForeground (gray[400]) is too light under the white label text
294
+ axisPointerLabel: themeTokens.colors.gray[600],
295
+ divider: themeTokens.colors.gray[700],
296
+ success: themeTokens.semanticColors.dark.success,
297
+ destructive: themeTokens.semanticColors.dark.destructive
298
+ };
299
+ function getChartColors(themeName) {
300
+ return themeName === THEME_DARK ? darkChartColors : lightChartColors;
301
+ }
302
+ function colorWithAlpha(color, alpha) {
303
+ let hex = /^#([\da-f]{3}|[\da-f]{6})$/i.exec(color)?.[1];
304
+ if (!hex) return color;
305
+ if (hex.length === 3) {
306
+ hex = hex.split("").map((c) => c + c).join("");
307
+ }
308
+ const r = parseInt(hex.slice(0, 2), 16);
309
+ const g = parseInt(hex.slice(2, 4), 16);
310
+ const b = parseInt(hex.slice(4, 6), 16);
311
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
312
+ }
313
+
279
314
  // src/utils/format.ts
280
315
  function formatNumber(value, options) {
281
316
  return new Intl.NumberFormat("en-US", options).format(value);
@@ -305,7 +340,18 @@ function abbreviateNumber(value, decimals = 1) {
305
340
  abbreviated /= 1e3;
306
341
  suffixIndex++;
307
342
  }
308
- return abbreviated.toFixed(decimals).replace(/\.0+$/, "") + suffixes[suffixIndex];
343
+ if (Math.abs(Number(abbreviated.toFixed(decimals))) >= 1e3 && suffixIndex < suffixes.length - 1) {
344
+ abbreviated /= 1e3;
345
+ suffixIndex++;
346
+ }
347
+ let fixed = abbreviated.toFixed(decimals);
348
+ if (fixed.includes(".")) {
349
+ fixed = fixed.replace(/\.?0+$/, "");
350
+ }
351
+ return fixed + suffixes[suffixIndex];
352
+ }
353
+ function escapeHtml(value) {
354
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
309
355
  }
310
356
  function createTooltipFormatter(options = {}) {
311
357
  const { showSeriesName = true, valueFormatter = formatNumber } = options;
@@ -320,9 +366,9 @@ function createTooltipFormatter(options = {}) {
320
366
  const formatted = valueFormatter(value);
321
367
  const percent = param.percent ? ` (${param.percent.toFixed(1)}%)` : "";
322
368
  if (showSeriesName && param.seriesName) {
323
- return `${param.seriesName}: ${formatted}${percent}`;
369
+ return `${escapeHtml(param.seriesName)}: ${escapeHtml(formatted)}${percent}`;
324
370
  }
325
- return `${param.name}: ${formatted}${percent}`;
371
+ return `${escapeHtml(param.name)}: ${escapeHtml(formatted)}${percent}`;
326
372
  }).join("<br/>");
327
373
  };
328
374
  }
@@ -376,6 +422,9 @@ function generatePieChartDescription(title, data, isDonut = false) {
376
422
  const total = data.reduce((sum, slice) => sum + slice.value, 0);
377
423
  const chartType = isDonut ? "Donut" : "Pie";
378
424
  let description = `${title}. ${chartType} chart with ${data.length} segments`;
425
+ if (total === 0) {
426
+ return `${description}. All segments have a value of 0`;
427
+ }
379
428
  const sorted = [...data].sort((a, b) => b.value - a.value);
380
429
  const topSegments = sorted.slice(0, 3);
381
430
  const percentages = topSegments.map((segment) => {
@@ -396,8 +445,8 @@ function generateScatterChartDescription(title, data) {
396
445
  return description;
397
446
  }
398
447
  function generateDataTable(headers, rows) {
399
- const headerRow = headers.map((h) => `<th scope="col">${h}</th>`).join("");
400
- const bodyRows = rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("");
448
+ const headerRow = headers.map((h) => `<th scope="col">${escapeHtml(h)}</th>`).join("");
449
+ const bodyRows = rows.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`).join("");
401
450
  return `
402
451
  <table class="sr-only">
403
452
  <thead><tr>${headerRow}</tr></thead>
@@ -406,11 +455,35 @@ function generateDataTable(headers, rows) {
406
455
  `;
407
456
  }
408
457
 
458
+ // src/utils/merge.ts
459
+ var ALWAYS_REPLACE = ["series", "graphic"];
460
+ function countComponents(value) {
461
+ if (value == null || value === false) return 0;
462
+ if (Array.isArray(value)) return value.filter((item) => item != null).length;
463
+ return 1;
464
+ }
465
+ function getReplaceMerge(prev, next, isComponentType) {
466
+ const types = new Set(ALWAYS_REPLACE);
467
+ if (prev) {
468
+ const before = prev;
469
+ const after = next;
470
+ for (const key of Object.keys(before)) {
471
+ if (countComponents(before[key]) > countComponents(after[key])) {
472
+ types.add(key);
473
+ }
474
+ }
475
+ }
476
+ return [...types].filter((type) => isComponentType(type));
477
+ }
478
+
409
479
  exports.THEME_DARK = THEME_DARK;
410
480
  exports.THEME_LIGHT = THEME_LIGHT;
411
481
  exports.abbreviateNumber = abbreviateNumber;
482
+ exports.colorWithAlpha = colorWithAlpha;
412
483
  exports.createTooltipFormatter = createTooltipFormatter;
484
+ exports.darkChartColors = darkChartColors;
413
485
  exports.darkTheme = darkTheme;
486
+ exports.escapeHtml = escapeHtml;
414
487
  exports.formatCurrency = formatCurrency;
415
488
  exports.formatDate = formatDate;
416
489
  exports.formatNumber = formatNumber;
@@ -422,4 +495,7 @@ exports.generateLightTheme = generateLightTheme;
422
495
  exports.generateLineChartDescription = generateLineChartDescription;
423
496
  exports.generatePieChartDescription = generatePieChartDescription;
424
497
  exports.generateScatterChartDescription = generateScatterChartDescription;
498
+ exports.getChartColors = getChartColors;
499
+ exports.getReplaceMerge = getReplaceMerge;
500
+ exports.lightChartColors = lightChartColors;
425
501
  exports.lightTheme = lightTheme;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { EChartsTheme, THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme } from './theme/index.cjs';
1
+ export { ChartSemanticColors, EChartsTheme, THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme } from './theme/index.cjs';
2
2
  import { BarChartData, LineChartData, PieSliceData, ScatterChartData } from './types/index.cjs';
3
3
  export { AreaChartData, AreaChartProps, AreaSeriesData, BarChartProps, BarSeriesData, BaseChartProps, BaseEventParams, CandlestickChartProps, CandlestickData, CandlestickEventParams, CandlestickItem, DataPointEventParams, DonutChartProps, FunnelChartProps, FunnelSliceData, GaugeChartProps, HeatmapChartProps, HeatmapData, HeatmapEventParams, LineChartProps, LineSeriesData, PieChartProps, RadarChartData, RadarChartProps, RadarIndicator, RadarSeriesData, SankeyChartProps, SankeyData, SankeyEventParams, SankeyLink, SankeyNode, ScatterChartProps, ScatterSeriesData, SliceEventParams, TreemapChartProps, TreemapEventParams, TreemapLevel, TreemapNode } from './types/index.cjs';
4
4
 
@@ -25,6 +25,11 @@ declare function formatDate(date: Date | string, options?: Intl.DateTimeFormatOp
25
25
  * Abbreviate large numbers (e.g., 1000 -> 1K, 1000000 -> 1M)
26
26
  */
27
27
  declare function abbreviateNumber(value: number, decimals?: number): string;
28
+ /**
29
+ * Escape a value for interpolation into HTML. ECharts renders the string a
30
+ * function tooltip formatter returns as innerHTML without encoding it.
31
+ */
32
+ declare function escapeHtml(value: unknown): string;
28
33
  /**
29
34
  * Create a tooltip formatter for ECharts
30
35
  */
@@ -64,4 +69,26 @@ declare function generateScatterChartDescription(title: string, data: ScatterCha
64
69
  */
65
70
  declare function generateDataTable(headers: string[], rows: (string | number)[][]): string;
66
71
 
67
- export { BarChartData, LineChartData, PieSliceData, ScatterChartData, abbreviateNumber, createTooltipFormatter, formatCurrency, formatDate, formatNumber, formatPercent, generateBarChartDescription, generateDataTable, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription };
72
+ /**
73
+ * Helpers for applying a complete ECharts option on top of a previous one.
74
+ */
75
+ /**
76
+ * Work out the `replaceMerge` list for `chart.setOption(next, { replaceMerge })`
77
+ * when `next` is a complete option that supersedes `prev`.
78
+ *
79
+ * `notMerge: true` would also drop removed series and components, but it
80
+ * rebuilds every component, so legend selection, dataZoom ranges and other
81
+ * interaction state reset on each data update. Instead this replaces only:
82
+ *
83
+ * - `series` and `graphic`, so removed series, mark lines and centre labels
84
+ * disappear;
85
+ * - any component type that `next` has fewer of than `prev` (a hidden legend
86
+ * or tooltip, a dropped volume grid and its axes).
87
+ *
88
+ * Everything else is merged, so its state survives. ECharts rejects types it
89
+ * has no component class for, so only types for which `isComponentType`
90
+ * returns true are listed; pass `echarts.ComponentModel.hasClass`.
91
+ */
92
+ declare function getReplaceMerge(prev: object | null | undefined, next: object, isComponentType: (mainType: string) => boolean): string[];
93
+
94
+ export { BarChartData, LineChartData, PieSliceData, ScatterChartData, abbreviateNumber, createTooltipFormatter, escapeHtml, formatCurrency, formatDate, formatNumber, formatPercent, generateBarChartDescription, generateDataTable, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, getReplaceMerge };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { EChartsTheme, THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme } from './theme/index.js';
1
+ export { ChartSemanticColors, EChartsTheme, THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme } from './theme/index.js';
2
2
  import { BarChartData, LineChartData, PieSliceData, ScatterChartData } from './types/index.js';
3
3
  export { AreaChartData, AreaChartProps, AreaSeriesData, BarChartProps, BarSeriesData, BaseChartProps, BaseEventParams, CandlestickChartProps, CandlestickData, CandlestickEventParams, CandlestickItem, DataPointEventParams, DonutChartProps, FunnelChartProps, FunnelSliceData, GaugeChartProps, HeatmapChartProps, HeatmapData, HeatmapEventParams, LineChartProps, LineSeriesData, PieChartProps, RadarChartData, RadarChartProps, RadarIndicator, RadarSeriesData, SankeyChartProps, SankeyData, SankeyEventParams, SankeyLink, SankeyNode, ScatterChartProps, ScatterSeriesData, SliceEventParams, TreemapChartProps, TreemapEventParams, TreemapLevel, TreemapNode } from './types/index.js';
4
4
 
@@ -25,6 +25,11 @@ declare function formatDate(date: Date | string, options?: Intl.DateTimeFormatOp
25
25
  * Abbreviate large numbers (e.g., 1000 -> 1K, 1000000 -> 1M)
26
26
  */
27
27
  declare function abbreviateNumber(value: number, decimals?: number): string;
28
+ /**
29
+ * Escape a value for interpolation into HTML. ECharts renders the string a
30
+ * function tooltip formatter returns as innerHTML without encoding it.
31
+ */
32
+ declare function escapeHtml(value: unknown): string;
28
33
  /**
29
34
  * Create a tooltip formatter for ECharts
30
35
  */
@@ -64,4 +69,26 @@ declare function generateScatterChartDescription(title: string, data: ScatterCha
64
69
  */
65
70
  declare function generateDataTable(headers: string[], rows: (string | number)[][]): string;
66
71
 
67
- export { BarChartData, LineChartData, PieSliceData, ScatterChartData, abbreviateNumber, createTooltipFormatter, formatCurrency, formatDate, formatNumber, formatPercent, generateBarChartDescription, generateDataTable, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription };
72
+ /**
73
+ * Helpers for applying a complete ECharts option on top of a previous one.
74
+ */
75
+ /**
76
+ * Work out the `replaceMerge` list for `chart.setOption(next, { replaceMerge })`
77
+ * when `next` is a complete option that supersedes `prev`.
78
+ *
79
+ * `notMerge: true` would also drop removed series and components, but it
80
+ * rebuilds every component, so legend selection, dataZoom ranges and other
81
+ * interaction state reset on each data update. Instead this replaces only:
82
+ *
83
+ * - `series` and `graphic`, so removed series, mark lines and centre labels
84
+ * disappear;
85
+ * - any component type that `next` has fewer of than `prev` (a hidden legend
86
+ * or tooltip, a dropped volume grid and its axes).
87
+ *
88
+ * Everything else is merged, so its state survives. ECharts rejects types it
89
+ * has no component class for, so only types for which `isComponentType`
90
+ * returns true are listed; pass `echarts.ComponentModel.hasClass`.
91
+ */
92
+ declare function getReplaceMerge(prev: object | null | undefined, next: object, isComponentType: (mainType: string) => boolean): string[];
93
+
94
+ export { BarChartData, LineChartData, PieSliceData, ScatterChartData, abbreviateNumber, createTooltipFormatter, escapeHtml, formatCurrency, formatDate, formatNumber, formatPercent, generateBarChartDescription, generateDataTable, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, getReplaceMerge };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme } from './chunk-ZRG25JNX.js';
1
+ export { THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme } from './chunk-JM72YHZD.js';
2
2
 
3
3
  // src/utils/format.ts
4
4
  function formatNumber(value, options) {
@@ -29,7 +29,18 @@ function abbreviateNumber(value, decimals = 1) {
29
29
  abbreviated /= 1e3;
30
30
  suffixIndex++;
31
31
  }
32
- return abbreviated.toFixed(decimals).replace(/\.0+$/, "") + suffixes[suffixIndex];
32
+ if (Math.abs(Number(abbreviated.toFixed(decimals))) >= 1e3 && suffixIndex < suffixes.length - 1) {
33
+ abbreviated /= 1e3;
34
+ suffixIndex++;
35
+ }
36
+ let fixed = abbreviated.toFixed(decimals);
37
+ if (fixed.includes(".")) {
38
+ fixed = fixed.replace(/\.?0+$/, "");
39
+ }
40
+ return fixed + suffixes[suffixIndex];
41
+ }
42
+ function escapeHtml(value) {
43
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
33
44
  }
34
45
  function createTooltipFormatter(options = {}) {
35
46
  const { showSeriesName = true, valueFormatter = formatNumber } = options;
@@ -44,9 +55,9 @@ function createTooltipFormatter(options = {}) {
44
55
  const formatted = valueFormatter(value);
45
56
  const percent = param.percent ? ` (${param.percent.toFixed(1)}%)` : "";
46
57
  if (showSeriesName && param.seriesName) {
47
- return `${param.seriesName}: ${formatted}${percent}`;
58
+ return `${escapeHtml(param.seriesName)}: ${escapeHtml(formatted)}${percent}`;
48
59
  }
49
- return `${param.name}: ${formatted}${percent}`;
60
+ return `${escapeHtml(param.name)}: ${escapeHtml(formatted)}${percent}`;
50
61
  }).join("<br/>");
51
62
  };
52
63
  }
@@ -100,6 +111,9 @@ function generatePieChartDescription(title, data, isDonut = false) {
100
111
  const total = data.reduce((sum, slice) => sum + slice.value, 0);
101
112
  const chartType = isDonut ? "Donut" : "Pie";
102
113
  let description = `${title}. ${chartType} chart with ${data.length} segments`;
114
+ if (total === 0) {
115
+ return `${description}. All segments have a value of 0`;
116
+ }
103
117
  const sorted = [...data].sort((a, b) => b.value - a.value);
104
118
  const topSegments = sorted.slice(0, 3);
105
119
  const percentages = topSegments.map((segment) => {
@@ -120,8 +134,8 @@ function generateScatterChartDescription(title, data) {
120
134
  return description;
121
135
  }
122
136
  function generateDataTable(headers, rows) {
123
- const headerRow = headers.map((h) => `<th scope="col">${h}</th>`).join("");
124
- const bodyRows = rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("");
137
+ const headerRow = headers.map((h) => `<th scope="col">${escapeHtml(h)}</th>`).join("");
138
+ const bodyRows = rows.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`).join("");
125
139
  return `
126
140
  <table class="sr-only">
127
141
  <thead><tr>${headerRow}</tr></thead>
@@ -130,4 +144,25 @@ function generateDataTable(headers, rows) {
130
144
  `;
131
145
  }
132
146
 
133
- export { abbreviateNumber, createTooltipFormatter, formatCurrency, formatDate, formatNumber, formatPercent, generateBarChartDescription, generateDataTable, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription };
147
+ // src/utils/merge.ts
148
+ var ALWAYS_REPLACE = ["series", "graphic"];
149
+ function countComponents(value) {
150
+ if (value == null || value === false) return 0;
151
+ if (Array.isArray(value)) return value.filter((item) => item != null).length;
152
+ return 1;
153
+ }
154
+ function getReplaceMerge(prev, next, isComponentType) {
155
+ const types = new Set(ALWAYS_REPLACE);
156
+ if (prev) {
157
+ const before = prev;
158
+ const after = next;
159
+ for (const key of Object.keys(before)) {
160
+ if (countComponents(before[key]) > countComponents(after[key])) {
161
+ types.add(key);
162
+ }
163
+ }
164
+ }
165
+ return [...types].filter((type) => isComponentType(type));
166
+ }
167
+
168
+ export { abbreviateNumber, createTooltipFormatter, escapeHtml, formatCurrency, formatDate, formatNumber, formatPercent, generateBarChartDescription, generateDataTable, generateLineChartDescription, generatePieChartDescription, generateScatterChartDescription, getReplaceMerge };
@@ -272,13 +272,52 @@ function generateDarkTheme() {
272
272
  var lightTheme = generateLightTheme();
273
273
  var darkTheme = generateDarkTheme();
274
274
 
275
- // src/theme/index.ts
275
+ // src/theme/constants.ts
276
276
  var THEME_LIGHT = "classic-light";
277
277
  var THEME_DARK = "classic-dark";
278
278
 
279
+ // src/theme/semantic.ts
280
+ var lightChartColors = {
281
+ series: lightTheme.color,
282
+ foreground: themeTokens.semanticColors.light.foreground,
283
+ mutedForeground: themeTokens.semanticColors.light.mutedForeground,
284
+ axisPointerLabel: themeTokens.semanticColors.light.mutedForeground,
285
+ divider: themeTokens.colors.gray[600],
286
+ success: themeTokens.semanticColors.light.success,
287
+ destructive: themeTokens.semanticColors.light.destructive
288
+ };
289
+ var darkChartColors = {
290
+ series: darkTheme.color,
291
+ foreground: themeTokens.semanticColors.dark.foreground,
292
+ mutedForeground: themeTokens.semanticColors.dark.mutedForeground,
293
+ // mutedForeground (gray[400]) is too light under the white label text
294
+ axisPointerLabel: themeTokens.colors.gray[600],
295
+ divider: themeTokens.colors.gray[700],
296
+ success: themeTokens.semanticColors.dark.success,
297
+ destructive: themeTokens.semanticColors.dark.destructive
298
+ };
299
+ function getChartColors(themeName) {
300
+ return themeName === THEME_DARK ? darkChartColors : lightChartColors;
301
+ }
302
+ function colorWithAlpha(color, alpha) {
303
+ let hex = /^#([\da-f]{3}|[\da-f]{6})$/i.exec(color)?.[1];
304
+ if (!hex) return color;
305
+ if (hex.length === 3) {
306
+ hex = hex.split("").map((c) => c + c).join("");
307
+ }
308
+ const r = parseInt(hex.slice(0, 2), 16);
309
+ const g = parseInt(hex.slice(2, 4), 16);
310
+ const b = parseInt(hex.slice(4, 6), 16);
311
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
312
+ }
313
+
279
314
  exports.THEME_DARK = THEME_DARK;
280
315
  exports.THEME_LIGHT = THEME_LIGHT;
316
+ exports.colorWithAlpha = colorWithAlpha;
317
+ exports.darkChartColors = darkChartColors;
281
318
  exports.darkTheme = darkTheme;
282
319
  exports.generateDarkTheme = generateDarkTheme;
283
320
  exports.generateLightTheme = generateLightTheme;
321
+ exports.getChartColors = getChartColors;
322
+ exports.lightChartColors = lightChartColors;
284
323
  exports.lightTheme = lightTheme;
@@ -133,11 +133,42 @@ declare const lightTheme: EChartsTheme;
133
133
  declare const darkTheme: EChartsTheme;
134
134
 
135
135
  /**
136
- * Chart theme exports
136
+ * Semantic chart colours
137
+ *
138
+ * Colours that chart components set per series or per element (and so cannot
139
+ * come from the registered ECharts theme), derived from the design tokens.
140
+ */
141
+ interface ChartSemanticColors {
142
+ /** Series palette, in the same order as the ECharts theme `color` array */
143
+ series: string[];
144
+ /** Primary text, e.g. a donut's centre value */
145
+ foreground: string;
146
+ /** Secondary text and marks: labels, gauge ticks, volume bars */
147
+ mutedForeground: string;
148
+ /** Background of axis-pointer labels (rendered with white text) */
149
+ axisPointerLabel: string;
150
+ /** Borders between top-level blocks, e.g. treemap groups */
151
+ divider: string;
152
+ /** Positive values */
153
+ success: string;
154
+ /** Negative values */
155
+ destructive: string;
156
+ }
157
+ declare const lightChartColors: ChartSemanticColors;
158
+ declare const darkChartColors: ChartSemanticColors;
159
+ /**
160
+ * Get the semantic chart colours for a registered theme name
161
+ * (`THEME_LIGHT` / `THEME_DARK`). Unknown names fall back to light.
162
+ */
163
+ declare function getChartColors(themeName: string): ChartSemanticColors;
164
+ /**
165
+ * Apply an alpha channel to a hex colour (`#rgb` or `#rrggbb`), returning
166
+ * `rgba(...)`. Any other colour string is returned unchanged.
137
167
  */
168
+ declare function colorWithAlpha(color: string, alpha: number): string;
138
169
 
139
170
  /** Theme name constants */
140
171
  declare const THEME_LIGHT = "classic-light";
141
172
  declare const THEME_DARK = "classic-dark";
142
173
 
143
- export { type EChartsTheme, THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme };
174
+ export { type ChartSemanticColors, type EChartsTheme, THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme };
@@ -133,11 +133,42 @@ declare const lightTheme: EChartsTheme;
133
133
  declare const darkTheme: EChartsTheme;
134
134
 
135
135
  /**
136
- * Chart theme exports
136
+ * Semantic chart colours
137
+ *
138
+ * Colours that chart components set per series or per element (and so cannot
139
+ * come from the registered ECharts theme), derived from the design tokens.
140
+ */
141
+ interface ChartSemanticColors {
142
+ /** Series palette, in the same order as the ECharts theme `color` array */
143
+ series: string[];
144
+ /** Primary text, e.g. a donut's centre value */
145
+ foreground: string;
146
+ /** Secondary text and marks: labels, gauge ticks, volume bars */
147
+ mutedForeground: string;
148
+ /** Background of axis-pointer labels (rendered with white text) */
149
+ axisPointerLabel: string;
150
+ /** Borders between top-level blocks, e.g. treemap groups */
151
+ divider: string;
152
+ /** Positive values */
153
+ success: string;
154
+ /** Negative values */
155
+ destructive: string;
156
+ }
157
+ declare const lightChartColors: ChartSemanticColors;
158
+ declare const darkChartColors: ChartSemanticColors;
159
+ /**
160
+ * Get the semantic chart colours for a registered theme name
161
+ * (`THEME_LIGHT` / `THEME_DARK`). Unknown names fall back to light.
162
+ */
163
+ declare function getChartColors(themeName: string): ChartSemanticColors;
164
+ /**
165
+ * Apply an alpha channel to a hex colour (`#rgb` or `#rrggbb`), returning
166
+ * `rgba(...)`. Any other colour string is returned unchanged.
137
167
  */
168
+ declare function colorWithAlpha(color: string, alpha: number): string;
138
169
 
139
170
  /** Theme name constants */
140
171
  declare const THEME_LIGHT = "classic-light";
141
172
  declare const THEME_DARK = "classic-dark";
142
173
 
143
- export { type EChartsTheme, THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme };
174
+ export { type ChartSemanticColors, type EChartsTheme, THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme };
@@ -1 +1 @@
1
- export { THEME_DARK, THEME_LIGHT, darkTheme, generateDarkTheme, generateLightTheme, lightTheme } from '../chunk-ZRG25JNX.js';
1
+ export { THEME_DARK, THEME_LIGHT, colorWithAlpha, darkChartColors, darkTheme, generateDarkTheme, generateLightTheme, getChartColors, lightChartColors, lightTheme } from '../chunk-JM72YHZD.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/charts-core",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Core utilities and theme for Classic Theme charts",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -31,7 +31,8 @@
31
31
  "build": "tsup",
32
32
  "dev": "tsup --watch",
33
33
  "clean": "node ../../scripts/rm.mjs dist",
34
- "typecheck": "tsc --noEmit"
34
+ "typecheck": "tsc --noEmit",
35
+ "test": "vitest run"
35
36
  },
36
37
  "dependencies": {
37
38
  "@classic-homes/theme-tokens": "*"