UNPKG

@itwin/quantity-formatting-react

Version:

React components and utilities for quantity formatting

204 lines 11.6 kB
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