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 { formatDateTime } from "../datetime/format_date_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 { LocalizeFunc } from "../translations/localize";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
|
@ -70,7 +74,11 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||
: attributes.unit_of_measurement === "%"
|
||||
? blankBeforePercent(locale) + "%"
|
||||
: ` ${attributes.unit_of_measurement}`;
|
||||
return `${formatNumber(state, locale)}${unit}`;
|
||||
return `${formatNumber(
|
||||
state,
|
||||
locale,
|
||||
getNumberFormatOptions({ state, attributes } as HassEntity)
|
||||
)}${unit}`;
|
||||
}
|
||||
|
||||
const domain = computeDomain(entityId);
|
||||
|
@ -143,7 +151,12 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||
domain === "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
|
||||
|
|
|
@ -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 { round } from "./round";
|
||||
|
||||
|
@ -9,9 +12,9 @@ import { round } from "./round";
|
|||
export const isNumericState = (stateObj: HassEntity): boolean =>
|
||||
isNumericFromAttributes(stateObj.attributes);
|
||||
|
||||
export const isNumericFromAttributes = (attributes: {
|
||||
[key: string]: any;
|
||||
}): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
|
||||
export const isNumericFromAttributes = (
|
||||
attributes: HassEntityAttributeBase
|
||||
): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
|
||||
|
||||
export const numberFormatToLocale = (
|
||||
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
|
||||
* @param num The number to be formatted
|
||||
* @param options The Intl.NumberFormatOptions that should be included in the returned options
|
||||
*/
|
||||
const getDefaultFormatOptions = (
|
||||
export const getDefaultFormatOptions = (
|
||||
num: string | number,
|
||||
options?: Intl.NumberFormatOptions
|
||||
): Intl.NumberFormatOptions => {
|
||||
|
@ -102,7 +122,8 @@ const getDefaultFormatOptions = (
|
|||
// Keep decimal trailing zeros if they are present in a string numeric value
|
||||
if (
|
||||
!options ||
|
||||
(!options.minimumFractionDigits && !options.maximumFractionDigits)
|
||||
(options.minimumFractionDigits === undefined &&
|
||||
options.maximumFractionDigits === undefined)
|
||||
) {
|
||||
const digits = num.indexOf(".") > -1 ? num.split(".")[1].length : 0;
|
||||
defaultOptions.minimumFractionDigits = digits;
|
||||
|
|
|
@ -16,6 +16,7 @@ import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
|||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import {
|
||||
formatNumber,
|
||||
getNumberFormatOptions,
|
||||
isNumericState,
|
||||
} from "../../common/number/format_number";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
|
@ -149,7 +150,11 @@ export class HaStateLabelBadge extends LitElement {
|
|||
entityState.state === UNAVAILABLE
|
||||
? "—"
|
||||
: isNumericState(entityState)
|
||||
? formatNumber(entityState.state, this.hass!.locale)
|
||||
? formatNumber(
|
||||
entityState.state,
|
||||
this.hass!.locale,
|
||||
getNumberFormatOptions(entityState)
|
||||
)
|
||||
: computeStateDisplay(
|
||||
this.hass!.localize,
|
||||
entityState,
|
||||
|
|
|
@ -17,6 +17,7 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
|
|||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import {
|
||||
formatNumber,
|
||||
getNumberFormatOptions,
|
||||
isNumericState,
|
||||
} from "../../../common/number/format_number";
|
||||
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")
|
||||
: isNumericState(stateObj) || this._config.unit
|
||||
? formatNumber(stateObj.state, this.hass.locale)
|
||||
? formatNumber(
|
||||
stateObj.state,
|
||||
this.hass.locale,
|
||||
getNumberFormatOptions(stateObj)
|
||||
)
|
||||
: computeStateDisplay(
|
||||
this.hass.localize,
|
||||
stateObj,
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
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 {
|
||||
FrontendLocaleData,
|
||||
NumberFormat,
|
||||
|
@ -63,4 +68,80 @@ describe("formatNumber", () => {
|
|||
"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…
Reference in New Issue