278 files

This commit is contained in:
Home Assistant Version Control
2026-09-13 20:38:58 +00:00
parent d7a0a08372
commit 1b4f5f68c6
278 changed files with 36892 additions and 7150 deletions
@@ -8,6 +8,15 @@ import { BUNDLE_VERSION } from "./helpers/bundle-version";
// derives the real symbol from const.BUDGET_CURRENCIES[DEFAULT_BUDGET_CURRENCY].
export const DEFAULT_CURRENCY_SYMBOL = "€";
/** The display symbol of a currency-bearing response block (`budget` in
* settings / stats, a BudgetStatus) — the ONE fallback rule. Seven call
* sites had forked into "€" (panel) vs "" (cards, complete dialog), so the
* cards rendered a bare "12" where the panel showed "12 €" (DRY audit
* 2026-09-12). */
export function currencySymbolOf(budget: { currency_symbol?: string | null } | null | undefined): string {
return budget?.currency_symbol || DEFAULT_CURRENCY_SYMBOL;
}
// Moved to the dependency-free status-constants.ts so the dashboard strategy
// bundle can share them without pulling Lit in; re-exported here so every
// existing `from "./styles"` import keeps working.
@@ -208,6 +217,25 @@ interface ProfilePrefs {
}
const _w = window as unknown as { __msDateTimePrefs?: ProfilePrefs };
const DT_PREFS: ProfilePrefs = (_w.__msDateTimePrefs ??= {});
/** Currency display prefs shared across bundles (a window singleton like the
* date/time prefs): the global `currency_decimals` setting. Synced from every
* response that carries a currency block (settings, budget_status,
* statistics) so every amount — KPIs, budgets, costs, printables — rounds
* the same way. Default 0 = whole numbers. */
const MONEY_PREFS: { decimals?: number } = ((_w as unknown as { __msMoneyPrefs?: { decimals?: number } }).__msMoneyPrefs ??= {});
export function setCurrencyDecimals(decimals: number | null | undefined): void {
MONEY_PREFS.decimals = typeof decimals === "number" && Number.isInteger(decimals) && decimals >= 0 && decimals <= 3 ? decimals : 0;
}
export function currencyDecimals(): number {
return MONEY_PREFS.decimals ?? 0;
}
/** Take the decimals from a currency-bearing response block, when present. */
export function syncCurrencyDecimals(src: { currency_decimals?: number | null } | null | undefined): void {
if (src && src.currency_decimals !== undefined && src.currency_decimals !== null) setCurrencyDecimals(Number(src.currency_decimals));
}
/** Feed HA's per-user date/time/number formats into the formatters below.
* `country` is the SERVER's configured country (#140): with the profile
@@ -263,10 +291,12 @@ export function formatNumber(n: number, lang?: string, digits?: number | Intl.Nu
}
}
/** Amount + currency symbol ("12.50 €"; the symbol is optional) — every cost
* figure in the UI and the printables goes through here. */
export function formatCost(amount: number, symbol?: string, lang?: string, digits = 2): string {
const num = formatNumber(amount, lang, digits);
/** Amount + currency symbol ("13 €" / "12.50 €"; the symbol is optional) —
* every cost figure in the UI and the printables goes through here. The
* fraction digits come from the global `currency_decimals` setting (default
* 0) unless a caller pins them. */
export function formatCost(amount: number, symbol?: string, lang?: string, digits?: number): string {
const num = formatNumber(amount, lang, digits ?? currencyDecimals());
return symbol ? `${num} ${symbol}` : num;
}
@@ -358,6 +388,14 @@ export function formatInterval(
return `${intervalDays} ${t("unit_" + (unit || "days"), lang)}`;
}
/** A duration in minutes, e.g. "45 min" — profile-aware number plus the
* localised unit. Five surfaces carried their own `${x} min` / `${x}m`
* (DRY round 2026-09; tripwired in profile-format-single-source.test.ts). */
export function formatDuration(minutes: number | null | undefined, lang?: string): string {
if (minutes === null || minutes === undefined) return "—";
return `${formatNumber(minutes, lang, { maximumFractionDigits: 1 })} ${t("minutes_short", lang)}`;
}
/** Localized weekday name (0=Mon … 6=Sun) via Intl — 2024-01-01 is a Monday.
* Single source for the dialog selectors AND formatRecurrence (DRY).
*
@@ -713,6 +751,14 @@ export const sharedStyles = css`
contain-intrinsic-size: auto 48px;
}
.history-entry:last-child { border-bottom: none; }
/* Dense variant (quick-actions dialog, overview "recent activities"):
no status-icon column, tighter padding, smaller type. */
.history-entry.compact {
padding: 6px 0;
font-size: 13px;
contain-intrinsic-size: auto 40px;
}
.history-entry.compact .history-readings { font-size: 12px; }
.history-icon {
flex-shrink: 0;