mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Format numeric entities with integer value and step as integer instead of float (#14112)
This commit is contained in:
parent
d445bf2505
commit
66ed1b18be
@ -9,7 +9,11 @@ import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
|
|||||||
import { formatDate } from "../datetime/format_date";
|
import { formatDate } from "../datetime/format_date";
|
||||||
import { formatDateTime } from "../datetime/format_date_time";
|
import { formatDateTime } from "../datetime/format_date_time";
|
||||||
import { formatTime } from "../datetime/format_time";
|
import { formatTime } from "../datetime/format_time";
|
||||||
import { formatNumber, isNumericFromAttributes } from "../number/format_number";
|
import {
|
||||||
|
formatNumber,
|
||||||
|
getNumberFormatOptions,
|
||||||
|
isNumericFromAttributes,
|
||||||
|
} from "../number/format_number";
|
||||||
import { blankBeforePercent } from "../translations/blank_before_percent";
|
import { blankBeforePercent } from "../translations/blank_before_percent";
|
||||||
import { LocalizeFunc } from "../translations/localize";
|
import { LocalizeFunc } from "../translations/localize";
|
||||||
import { computeDomain } from "./compute_domain";
|
import { computeDomain } from "./compute_domain";
|
||||||
@ -70,7 +74,11 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
: attributes.unit_of_measurement === "%"
|
: attributes.unit_of_measurement === "%"
|
||||||
? blankBeforePercent(locale) + "%"
|
? blankBeforePercent(locale) + "%"
|
||||||
: ` ${attributes.unit_of_measurement}`;
|
: ` ${attributes.unit_of_measurement}`;
|
||||||
return `${formatNumber(state, locale)}${unit}`;
|
return `${formatNumber(
|
||||||
|
state,
|
||||||
|
locale,
|
||||||
|
getNumberFormatOptions({ state, attributes } as HassEntity)
|
||||||
|
)}${unit}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = computeDomain(entityId);
|
const domain = computeDomain(entityId);
|
||||||
@ -143,7 +151,12 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
domain === "number" ||
|
domain === "number" ||
|
||||||
domain === "input_number"
|
domain === "input_number"
|
||||||
) {
|
) {
|
||||||
return formatNumber(state, locale);
|
// Format as an integer if the value and step are integers
|
||||||
|
return formatNumber(
|
||||||
|
state,
|
||||||
|
locale,
|
||||||
|
getNumberFormatOptions({ state, attributes } as HassEntity)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// state of button is a timestamp
|
// state of button is a timestamp
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import {
|
||||||
|
HassEntity,
|
||||||
|
HassEntityAttributeBase,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
|
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
|
||||||
import { round } from "./round";
|
import { round } from "./round";
|
||||||
|
|
||||||
@ -9,9 +12,9 @@ import { round } from "./round";
|
|||||||
export const isNumericState = (stateObj: HassEntity): boolean =>
|
export const isNumericState = (stateObj: HassEntity): boolean =>
|
||||||
isNumericFromAttributes(stateObj.attributes);
|
isNumericFromAttributes(stateObj.attributes);
|
||||||
|
|
||||||
export const isNumericFromAttributes = (attributes: {
|
export const isNumericFromAttributes = (
|
||||||
[key: string]: any;
|
attributes: HassEntityAttributeBase
|
||||||
}): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
|
): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
|
||||||
|
|
||||||
export const numberFormatToLocale = (
|
export const numberFormatToLocale = (
|
||||||
localeOptions: FrontendLocaleData
|
localeOptions: FrontendLocaleData
|
||||||
@ -81,12 +84,29 @@ export const formatNumber = (
|
|||||||
}`;
|
}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current entity state should be formatted as an integer based on the `state` and `step` attribute and returns the appropriate `Intl.NumberFormatOptions` object with `maximumFractionDigits` set
|
||||||
|
* @param entityState The state object of the entity
|
||||||
|
* @returns An `Intl.NumberFormatOptions` object with `maximumFractionDigits` set to 0, or `undefined`
|
||||||
|
*/
|
||||||
|
export const getNumberFormatOptions = (
|
||||||
|
entityState: HassEntity
|
||||||
|
): Intl.NumberFormatOptions | undefined => {
|
||||||
|
if (
|
||||||
|
Number.isInteger(Number(entityState.attributes?.step)) &&
|
||||||
|
Number.isInteger(Number(entityState.state))
|
||||||
|
) {
|
||||||
|
return { maximumFractionDigits: 0 };
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates default options for Intl.NumberFormat
|
* Generates default options for Intl.NumberFormat
|
||||||
* @param num The number to be formatted
|
* @param num The number to be formatted
|
||||||
* @param options The Intl.NumberFormatOptions that should be included in the returned options
|
* @param options The Intl.NumberFormatOptions that should be included in the returned options
|
||||||
*/
|
*/
|
||||||
const getDefaultFormatOptions = (
|
export const getDefaultFormatOptions = (
|
||||||
num: string | number,
|
num: string | number,
|
||||||
options?: Intl.NumberFormatOptions
|
options?: Intl.NumberFormatOptions
|
||||||
): Intl.NumberFormatOptions => {
|
): Intl.NumberFormatOptions => {
|
||||||
@ -102,7 +122,8 @@ const getDefaultFormatOptions = (
|
|||||||
// Keep decimal trailing zeros if they are present in a string numeric value
|
// Keep decimal trailing zeros if they are present in a string numeric value
|
||||||
if (
|
if (
|
||||||
!options ||
|
!options ||
|
||||||
(!options.minimumFractionDigits && !options.maximumFractionDigits)
|
(options.minimumFractionDigits === undefined &&
|
||||||
|
options.maximumFractionDigits === undefined)
|
||||||
) {
|
) {
|
||||||
const digits = num.indexOf(".") > -1 ? num.split(".")[1].length : 0;
|
const digits = num.indexOf(".") > -1 ? num.split(".")[1].length : 0;
|
||||||
defaultOptions.minimumFractionDigits = digits;
|
defaultOptions.minimumFractionDigits = digits;
|
||||||
|
@ -16,6 +16,7 @@ import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
|||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import {
|
import {
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
getNumberFormatOptions,
|
||||||
isNumericState,
|
isNumericState,
|
||||||
} from "../../common/number/format_number";
|
} from "../../common/number/format_number";
|
||||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||||
@ -149,7 +150,11 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
entityState.state === UNAVAILABLE
|
entityState.state === UNAVAILABLE
|
||||||
? "—"
|
? "—"
|
||||||
: isNumericState(entityState)
|
: isNumericState(entityState)
|
||||||
? formatNumber(entityState.state, this.hass!.locale)
|
? formatNumber(
|
||||||
|
entityState.state,
|
||||||
|
this.hass!.locale,
|
||||||
|
getNumberFormatOptions(entityState)
|
||||||
|
)
|
||||||
: computeStateDisplay(
|
: computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
entityState,
|
entityState,
|
||||||
|
@ -17,6 +17,7 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
|
|||||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||||
import {
|
import {
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
getNumberFormatOptions,
|
||||||
isNumericState,
|
isNumericState,
|
||||||
} from "../../../common/number/format_number";
|
} from "../../../common/number/format_number";
|
||||||
import { iconColorCSS } from "../../../common/style/icon_color_css";
|
import { iconColorCSS } from "../../../common/style/icon_color_css";
|
||||||
@ -147,7 +148,11 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
|||||||
)
|
)
|
||||||
: this.hass.localize("state.default.unknown")
|
: this.hass.localize("state.default.unknown")
|
||||||
: isNumericState(stateObj) || this._config.unit
|
: isNumericState(stateObj) || this._config.unit
|
||||||
? formatNumber(stateObj.state, this.hass.locale)
|
? formatNumber(
|
||||||
|
stateObj.state,
|
||||||
|
this.hass.locale,
|
||||||
|
getNumberFormatOptions(stateObj)
|
||||||
|
)
|
||||||
: computeStateDisplay(
|
: computeStateDisplay(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { assert } from "chai";
|
import { assert } from "chai";
|
||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
import { formatNumber } from "../../../src/common/number/format_number";
|
import {
|
||||||
|
formatNumber,
|
||||||
|
getDefaultFormatOptions,
|
||||||
|
getNumberFormatOptions,
|
||||||
|
} from "../../../src/common/number/format_number";
|
||||||
import {
|
import {
|
||||||
FrontendLocaleData,
|
FrontendLocaleData,
|
||||||
NumberFormat,
|
NumberFormat,
|
||||||
@ -63,4 +68,80 @@ describe("formatNumber", () => {
|
|||||||
"1,234.50"
|
"1,234.50"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Sets only the maximumFractionDigits format option when none are provided for a number value", () => {
|
||||||
|
assert.deepEqual(getDefaultFormatOptions(1234.5), {
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Sets minimumFractionDigits and maximumFractionDigits to '2' when none are provided for a string numeric value with two decimal places", () => {
|
||||||
|
assert.deepEqual(getDefaultFormatOptions("1234.50"), {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Merges default format options (minimumFractionDigits and maximumFractionDigits) and non-default format options for a string numeric value with two decimal places", () => {
|
||||||
|
assert.deepEqual(getDefaultFormatOptions("1234.50", { currency: "USD" }), {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
currency: "USD",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Sets maximumFractionDigits when that is the only format option provided", () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
getDefaultFormatOptions("1234.50", { maximumFractionDigits: 0 }),
|
||||||
|
{
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Sets maximumFractionDigits to '2' and minimumFractionDigits to the provided value when only minimumFractionDigits is provided", () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
getDefaultFormatOptions("1234.50", { minimumFractionDigits: 1 }),
|
||||||
|
{
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Sets maximumFractionDigits to '0' when the state value and step are integers", () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
getNumberFormatOptions({
|
||||||
|
state: "3.0",
|
||||||
|
attributes: { step: 1 },
|
||||||
|
} as unknown as HassEntity),
|
||||||
|
{
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Does not set any Intl.NumberFormatOptions when the step is not an integer", () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
getNumberFormatOptions({
|
||||||
|
state: "3.0",
|
||||||
|
attributes: { step: 0.5 },
|
||||||
|
} as unknown as HassEntity),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Does not set any Intl.NumberFormatOptions when the state value is not an integer", () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
getNumberFormatOptions({ state: "3.5" } as unknown as HassEntity),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Does not set any Intl.NumberFormatOptions when there is no step attribute", () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
getNumberFormatOptions({ state: "3.0" } as unknown as HassEntity),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user