@itwin/quantity-formatting-react
Version:
React components and utilities for quantity formatting
204 lines • 11.6 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import * as React from "react";
import { IconButton, Input, Label, Select } from "@itwin/itwinui-react";
import { SvgHelpCircularHollow } from "@itwin/itwinui-icons-react";
import { useTranslation } from "../../../useTranslation.js";
import { getUnitName } from "./misc/UnitDescr.js";
import "../FormatPanel.scss";
async function getUnitConversionData(possibleUnits, toUnit, unitsProvider) {
const unitConversionEntries = possibleUnits.map(async (unit) => {
const conversion = await unitsProvider.getConversion(unit, toUnit);
return { conversion, unitProps: unit };
});
return unitConversionEntries;
}
async function getPossibleUnits(parentUnit, unitsProvider, ensureCompatibleComposite) {
const phenomenon = parentUnit.phenomenon;
const possibleUnits = await unitsProvider.getUnitsByFamily(phenomenon);
if (!ensureCompatibleComposite)
return possibleUnits;
const conversionPromises = await getUnitConversionData(possibleUnits, parentUnit, unitsProvider);
const conversionEntries = await Promise.all(conversionPromises);
// sort the entries so the best potential sub unit will be the first one in the array
return conversionEntries
.filter((entry) => entry.unitProps.system === parentUnit.system &&
entry.conversion.factor < 1)
.sort((a, b) => b.conversion.factor - a.conversion.factor)
.map((value) => value.unitProps);
}
function UnitDescr(props) {
const { name, label, parentUnitName, index, onUnitChange, onLabelChange, readonly, unitsProvider, } = props;
const { translate } = useTranslation();
const [unitOptions, setUnitOptions] = React.useState([
{ value: `${name}:${label}`, label: getUnitName(name) },
]);
const [currentUnit, setCurrentUnit] = React.useState({ name, label });
const unitSelectorId = React.useId();
const labelInputId = React.useId();
React.useEffect(() => {
let disposed = false;
const fetchAllowableUnitSelections = async () => {
try {
const currentUnitProps = await unitsProvider.findUnitByName(name);
const parentUnit = await unitsProvider.findUnitByName(parentUnitName ? parentUnitName : name);
if (parentUnit && currentUnitProps) {
let potentialSubUnit;
const potentialUnits = await getPossibleUnits(parentUnit, unitsProvider, index !== 0);
if (index < 3) {
const potentialSubUnits = await getPossibleUnits(currentUnitProps, unitsProvider, true);
if (potentialSubUnits.length)
potentialSubUnit = potentialSubUnits[0];
}
const options = potentialUnits.length > 0
? potentialUnits
.map((unitValue) => {
return {
value: `${unitValue.name}:${unitValue.label}`,
label: getUnitName(unitValue.name),
};
})
.sort((a, b) => a.label.localeCompare(b.label))
: [
{
value: `${currentUnitProps.name}:${currentUnitProps.label}`,
label: getUnitName(name),
},
];
if (potentialSubUnit) {
// construct an entry that will provide the name and label of the unit to add
options.push({
value: `ADDSUBUNIT:${potentialSubUnit.name}:${potentialSubUnit.label}`,
label: translate("QuantityFormat:labels.addSubUnit"),
});
}
if (index !== 0) {
options.push({
value: "REMOVEUNIT",
label: translate("QuantityFormat:labels.removeUnit"),
});
}
if (disposed)
return;
setUnitOptions(options);
setCurrentUnit(currentUnitProps);
}
}
catch (error) {
// Fallback to current unit if there's an error
console.warn("Failed to load unit options:", error);
if (disposed)
return;
setUnitOptions([
{ value: `${name}:${label}`, label: getUnitName(name) },
]);
}
};
void fetchAllowableUnitSelections();
return () => {
disposed = true;
};
}, [index, label, name, parentUnitName, translate, unitsProvider]);
const handleOnLabelChange = React.useCallback((e) => {
e.preventDefault();
onLabelChange(e.target.value, index);
}, [index, onLabelChange]);
return (_jsxs("div", { className: "quantityFormat--formatInlineRow", children: [_jsx(Select, { id: unitSelectorId, options: unitOptions, "data-testid": `unit-${currentUnit.name}`, value: `${currentUnit.name}:${currentUnit.label}`, onChange: (newValue) => onUnitChange(newValue, index), disabled: readonly, size: "small", className: "quantityFormat--unitSelect" }), _jsx(Input, { id: labelInputId, "data-testid": `unit-label-${currentUnit.name}`, value: label, onChange: handleOnLabelChange, size: "small", disabled: readonly, className: "quantityFormat--unitInput" })] }));
}
/** Component to show/edit Units used for Quantity Formatting.
* @internal
*/
export function FormatUnits(props) {
const { initialFormat, persistenceUnit, unitsProvider, onUnitsChange } = props;
const { translate } = useTranslation();
const initialFormatRef = React.useRef(initialFormat);
const [formatProps, setFormatProps] = React.useState(initialFormat);
const compositeSpacerSelectorId = React.useId();
React.useEffect(() => {
if (initialFormatRef.current !== initialFormat) {
initialFormatRef.current = initialFormat;
setFormatProps(initialFormat);
}
}, [initialFormat]);
const handleSetFormatProps = React.useCallback((newProps) => {
setFormatProps(newProps);
onUnitsChange(newProps);
}, [onUnitsChange]);
const handleUnitLabelChange = React.useCallback((newLabel, index) => {
if (formatProps.composite &&
formatProps.composite.units.length > index &&
index >= 0) {
const units = formatProps.composite.units.map((entry, ndx) => {
if (index === ndx)
return { name: entry.name, label: newLabel };
else
return entry;
});
const composite = { ...formatProps.composite, units };
const newFormatProps = { ...formatProps, composite };
handleSetFormatProps(newFormatProps);
}
}, [formatProps, handleSetFormatProps]);
const handleUnitChange = React.useCallback((newUnit, index) => {
const unitParts = newUnit.split(/:/);
if (unitParts[0] === "REMOVEUNIT") {
if (formatProps.composite && formatProps.composite.units.length > 1) {
const units = [...formatProps.composite.units];
units.pop();
const composite = { ...formatProps.composite, units };
const newFormatProps = { ...formatProps, composite };
handleSetFormatProps(newFormatProps);
}
}
else if (unitParts[0] === "ADDSUBUNIT") {
const units = formatProps.composite && formatProps.composite.units.length
? [
...formatProps.composite.units,
{ name: unitParts[1], label: unitParts[2] },
]
: [{ name: unitParts[1], label: unitParts[2] }];
const composite = { ...formatProps.composite, units };
const newFormatProps = { ...formatProps, composite };
handleSetFormatProps(newFormatProps);
}
else {
if (formatProps.composite &&
formatProps.composite.units.length > index &&
index >= 0) {
const units = formatProps.composite.units.map((entry, ndx) => {
if (index === ndx)
return { name: unitParts[0], label: unitParts[1] };
else
return entry;
});
const composite = { ...formatProps.composite, units };
const newFormatProps = { ...formatProps, composite };
handleSetFormatProps(newFormatProps);
}
else if (!formatProps.composite) {
const composite = {
units: [{ name: unitParts[0], label: unitParts[1] }],
};
const newFormatProps = { ...formatProps, composite };
handleSetFormatProps(newFormatProps);
}
}
}, [formatProps, handleSetFormatProps]);
const handleOnSpacerChange = React.useCallback((e) => {
if (formatProps.composite) {
const spacerValue = e.target.value.length ? e.target.value[0] : ""; // spacer can only be empty or a single character
const composite = { ...formatProps.composite, spacer: spacerValue };
handleSetFormatProps({ ...formatProps, composite });
}
}, [formatProps, handleSetFormatProps]);
return (_jsxs(_Fragment, { children: [formatProps.composite?.units
? formatProps.composite.units.map((value, index) => (_jsx(UnitDescr, { name: value.name, label: value.label ?? "", parentUnitName: index > 0
? formatProps.composite.units[index - 1].name
: undefined, unitsProvider: unitsProvider, index: index, onUnitChange: handleUnitChange, onLabelChange: handleUnitLabelChange, readonly: index < formatProps.composite.units.length - 1 }, value.name)))
: persistenceUnit && (_jsx(UnitDescr, { name: persistenceUnit.name, label: persistenceUnit.label, unitsProvider: unitsProvider, index: 0, onUnitChange: handleUnitChange, onLabelChange: handleUnitLabelChange }, persistenceUnit.name)), formatProps.composite?.units &&
formatProps.composite.units.length > 1 && (_jsxs("div", { className: "quantityFormat--formatInlineRow", children: [_jsxs(Label, { displayStyle: "inline", htmlFor: compositeSpacerSelectorId, children: [translate("QuantityFormat:labels.compositeSpacer"), _jsx(IconButton, { className: "format-help-tooltip", size: "small", styleType: "borderless", label: translate("QuantityFormat:labels.compositeSpacerDescription"), children: _jsx(SvgHelpCircularHollow, {}) })] }), _jsx(Input, { id: compositeSpacerSelectorId, value: formatProps.composite.spacer ?? "", onChange: handleOnSpacerChange, size: "small" })] }))] }));
}
//# sourceMappingURL=FormatUnits.js.map