@flux-ui/statistics 3.0.0-next.71 → 3.0.0-next.73

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.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Escapes a value for safe interpolation into HTML text content.
3
+ */
4
+ export declare function escapeHtml(value: unknown): string;
5
+ /**
6
+ * Escapes a value for safe interpolation into a double-quoted HTML attribute
7
+ * or inline style value. Prevents attribute breakout from untrusted input.
8
+ */
9
+ export declare function escapeAttr(value: unknown): string;
@@ -1,4 +1,5 @@
1
1
  export * from './colors';
2
+ export * from './html';
2
3
  export * from './icons';
3
4
  export * from './options';
4
5
  export * from './series';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flux-ui/statistics",
3
3
  "description": "Statistics components for the Flux UI library.",
4
- "version": "3.0.0-next.71",
4
+ "version": "3.0.0-next.73",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/basmilius",
@@ -49,11 +49,11 @@
49
49
  "types": "./dist/index.d.ts",
50
50
  "sideEffects": false,
51
51
  "dependencies": {
52
- "@basmilius/common": "^3.35.0",
53
- "@basmilius/utils": "^3.35.0",
54
- "@flux-ui/components": "3.0.0-next.71",
55
- "@flux-ui/internals": "3.0.0-next.71",
56
- "@flux-ui/types": "3.0.0-next.71",
52
+ "@basmilius/common": "^3.37.0",
53
+ "@basmilius/utils": "^3.37.0",
54
+ "@flux-ui/components": "3.0.0-next.73",
55
+ "@flux-ui/internals": "3.0.0-next.73",
56
+ "@flux-ui/types": "3.0.0-next.73",
57
57
  "clsx": "^2.1.1"
58
58
  },
59
59
  "peerDependencies": {
@@ -63,7 +63,7 @@
63
63
  "vue-i18n": "^11.4.4"
64
64
  },
65
65
  "devDependencies": {
66
- "@basmilius/vite-preset": "^3.35.0",
66
+ "@basmilius/vite-preset": "^3.37.0",
67
67
  "@types/lodash-es": "^4.17.12",
68
68
  "@types/node": "^25.9.1",
69
69
  "@vitejs/plugin-vue": "^6.0.7",
@@ -71,6 +71,6 @@
71
71
  "sass-embedded": "^1.100.0",
72
72
  "typescript": "^6.0.3",
73
73
  "vite": "^8.0.14",
74
- "vue-tsc": "^3.3.1"
74
+ "vue-tsc": "^3.3.2"
75
75
  }
76
76
  }
@@ -0,0 +1,25 @@
1
+ const HTML_ESCAPES: Record<string, string> = {
2
+ '&': '&amp;',
3
+ '<': '&lt;',
4
+ '>': '&gt;',
5
+ '"': '&quot;',
6
+ '\'': '&#39;'
7
+ };
8
+
9
+ const HTML_PATTERN = /[&<>"']/g;
10
+ const ATTR_PATTERN = /[&<>"]/g;
11
+
12
+ /**
13
+ * Escapes a value for safe interpolation into HTML text content.
14
+ */
15
+ export function escapeHtml(value: unknown): string {
16
+ return String(value ?? '').replace(HTML_PATTERN, char => HTML_ESCAPES[char]);
17
+ }
18
+
19
+ /**
20
+ * Escapes a value for safe interpolation into a double-quoted HTML attribute
21
+ * or inline style value. Prevents attribute breakout from untrusted input.
22
+ */
23
+ export function escapeAttr(value: unknown): string {
24
+ return String(value ?? '').replace(ATTR_PATTERN, char => HTML_ESCAPES[char]);
25
+ }
package/src/util/icons.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { iconRegistry } from '@flux-ui/components';
2
2
  import type { FluxIconName } from '@flux-ui/types';
3
+ import { escapeAttr } from './html';
3
4
 
4
5
  export function renderIconSvg(name: FluxIconName | undefined, color: string, size: number = 14): string {
5
6
  if (!name) {
@@ -14,7 +15,8 @@ export function renderIconSvg(name: FluxIconName | undefined, color: string, siz
14
15
 
15
16
  const [width, height, , , pathData] = icon;
16
17
  const paths = Array.isArray(pathData) ? pathData : [pathData];
17
- const pathElements = paths.map(d => `<path d="${d}" fill="${color}"/>`).join('');
18
+ const safeColor = escapeAttr(color);
19
+ const pathElements = paths.map(d => `<path d="${escapeAttr(d)}" fill="${safeColor}"/>`).join('');
18
20
 
19
21
  return `<svg viewBox="0 0 ${width} ${height}" width="${size}" height="${size}" focusable="false" aria-hidden="true">${pathElements}</svg>`;
20
22
  }
package/src/util/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './colors';
2
+ export * from './html';
2
3
  export * from './icons';
3
4
  export * from './options';
4
5
  export * from './series';
@@ -1,4 +1,5 @@
1
1
  import { formatNumber } from '@basmilius/utils';
2
+ import { escapeAttr, escapeHtml } from '../html';
2
3
  import { renderIconSvg } from '../icons';
3
4
  import type { ChartTooltipValueFormatter, SharedTooltipItem, TooltipParam, TooltipStyleClasses, Translator } from './types';
4
5
 
@@ -15,7 +16,7 @@ export function renderTooltip(
15
16
  }
16
17
 
17
18
  const titleHtml = title
18
- ? `<div class="${styles.statisticsChartTooltipTitle}">${title}</div>`
19
+ ? `<div class="${styles.statisticsChartTooltipTitle}">${escapeHtml(title)}</div>`
19
20
  : '';
20
21
 
21
22
  const hasActive = activeIndex !== -1;
@@ -25,16 +26,17 @@ export function renderTooltip(
25
26
  const activeClass = isActive ? ` ${styles.isActive}` : '';
26
27
  const translatedName = item.name ? t(String(item.name)) : '';
27
28
 
29
+ const safeColor = escapeAttr(item.color);
28
30
  const marker = item.icon
29
- ? `<div class="${styles.statisticsChartTooltipSeriesIcon}${activeClass}" style="color: ${item.color}">${renderIconSvg(item.icon, item.color, 14)}</div>`
30
- : `<div class="${styles.statisticsChartTooltipSeriesColor}${activeClass}" style="background: ${item.color}"></div>`;
31
+ ? `<div class="${styles.statisticsChartTooltipSeriesIcon}${activeClass}" style="color: ${safeColor}">${renderIconSvg(item.icon, item.color, 14)}</div>`
32
+ : `<div class="${styles.statisticsChartTooltipSeriesColor}${activeClass}" style="background: ${safeColor}"></div>`;
31
33
 
32
34
  const display = valueFormatter ? valueFormatter(item.value, item) : formatValue(item.value);
33
35
 
34
36
  return `
35
37
  ${marker}
36
- <div class="${styles.statisticsChartTooltipSeriesName}${activeClass}">${translatedName}</div>
37
- <div class="${styles.statisticsChartTooltipSeriesValue}${activeClass}">${display}</div>
38
+ <div class="${styles.statisticsChartTooltipSeriesName}${activeClass}">${escapeHtml(translatedName)}</div>
39
+ <div class="${styles.statisticsChartTooltipSeriesValue}${activeClass}">${escapeHtml(display)}</div>
38
40
  `;
39
41
  }).join('');
40
42