fix(charts): align every chart tooltip on the borderless soft-shadow card

One visual contract for all three D3 tooltip surfaces, matching the
design reference: p-4, rounded-2xl, shadow-xl, no edge ring in light
mode. Dark mode keeps a 1px alpha-white ring since a shadow alone
disappears against dark surfaces.

- goal_projection_chart_controller drops its hand-copied class string
  (it still carried the old bordered recipe — the drift this util
  exists to prevent) and builds its two lines through the shared
  factory: secondary date line, tabular value line.
- New content conventions exported alongside the container contract:
  context line = text-xs text-secondary, values = font-medium
  tabular-nums. Time-series and sankey adopt them.
- Sankey node titles now escape before .html(); user-named categories
  were previously interpolated raw into the tooltip markup.
This commit is contained in:
Guillem Arias
2026-06-05 09:16:34 +02:00
parent d033200226
commit a3eb33b8a6
4 changed files with 44 additions and 15 deletions

View File

@@ -520,9 +520,18 @@ export default class extends Controller {
#showTooltip(event, value, percentage, title = null) {
if (!this.tooltip) this.#createTooltip();
// Node titles are user-named categories; escape them since this goes
// through .html() (the previous interpolation injected them raw).
const esc = (s) =>
String(s).replace(
/[&<>"']/g,
(c) =>
({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c],
);
const valueLine = `<span class="font-medium tabular-nums">${this.#formatCurrency(value)}</span> <span class="text-secondary">(${percentage || 0}%)</span>`;
const content = title
? `${title}<br/>${this.#formatCurrency(value)} (${percentage || 0}%)`
: `${this.#formatCurrency(value)} (${percentage || 0}%)`;
? `<div class="text-xs text-secondary mb-1">${esc(title)}</div><div>${valueLine}</div>`
: valueLine;
const isInDialog = !!this.element.closest("dialog");
const x = isInDialog ? event.clientX : event.pageX;