
META-INF.resources.components.quantity_selector.InputQuantitySelector.js Maven / Gradle / Ivy
/**
* SPDX-FileCopyrightText: (c) 2023 Liferay, Inc. https://liferay.com
* SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
*/
import ClayForm, {ClayInput} from '@clayui/form';
import {useIsMounted} from '@liferay/frontend-js-react-web';
import classNames from 'classnames';
import {debounce} from 'frontend-js-web';
import React, {forwardRef, useEffect, useRef, useState} from 'react';
import {CP_UNIT_OF_MEASURE_SELECTOR_CHANGED} from '../../utilities/eventsDefinitions';
import {
getMinQuantity,
getMultiple,
getNumberOfDecimals,
getProductMaxQuantity,
isMultiple,
} from '../../utilities/quantities';
import RulesPopover from './RulesPopover';
const getErrors = (
value,
min,
max,
step,
precision = 0,
allowEmptyValue = false
) => {
const errors = [];
if (getNumberOfDecimals(value) > precision) {
errors.push('decimals');
}
if (allowEmptyValue) {
if (value && value < min) {
errors.push('min');
}
}
else if (!value || value < min) {
errors.push('min');
}
if (max && value > max) {
errors.push('max');
}
if (step > 0 && !isMultiple(value, step, precision)) {
errors.push('multiple');
}
return errors;
};
const InputQuantitySelector = forwardRef(
(
{
alignment,
allowEmptyValue,
className,
disabled,
max,
min,
name,
namespace,
onUpdate,
quantity,
step,
unitOfMeasure,
...props
},
inputRef
) => {
const [inputProperties, setInputProperties] = useState({
currentUnitOfMeasure: unitOfMeasure,
max: unitOfMeasure
? getProductMaxQuantity(
max,
unitOfMeasure.incrementalOrderQuantity,
unitOfMeasure.precision
)
: getProductMaxQuantity(Math.ceil(max), Math.ceil(step)),
min: unitOfMeasure
? getMinQuantity(
min,
unitOfMeasure.incrementalOrderQuantity,
unitOfMeasure.precision
)
: getMinQuantity(Math.ceil(min), Math.ceil(step)),
quantity,
step: unitOfMeasure
? getMultiple(
unitOfMeasure.incrementalOrderQuantity,
step,
unitOfMeasure.precision
)
: Math.ceil(step),
});
const [showPopover, setShowPopover] = useState(false);
const [visibleErrors, setVisibleErrors] = useState(() =>
getErrors(
quantity,
unitOfMeasure ? min : Math.ceil(min),
unitOfMeasure ? max : Math.ceil(max),
unitOfMeasure
? getMultiple(
unitOfMeasure.incrementalOrderQuantity,
step,
unitOfMeasure.precision
)
: Math.ceil(step),
0,
allowEmptyValue
)
);
const isMounted = useIsMounted();
const debouncedSetVisibleErrorsRef = useRef(
debounce((newErrors) => {
if (isMounted()) {
setVisibleErrors(newErrors);
}
}, 500)
);
// eslint-disable-next-line react-hooks/exhaustive-deps
const handleUOMChanged = ({resetQuantity, unitOfMeasure}) => {
const setStateContext = ({
max,
min,
precision,
quantity,
step,
unitOfMeasure,
}) => {
setInputProperties((inputProperties) => ({
...inputProperties,
currentUnitOfMeasure: unitOfMeasure,
max: getProductMaxQuantity(max, step, precision),
min: getMinQuantity(min, step, precision),
step,
}));
onUpdate({
errors: getErrors(
quantity,
min,
max,
step,
precision,
allowEmptyValue
),
unitOfMeasure,
value: quantity,
});
};
if (unitOfMeasure) {
let quantity = inputProperties.quantity;
if (resetQuantity) {
quantity = Number(
getMinQuantity(
min,
unitOfMeasure.incrementalOrderQuantity,
unitOfMeasure.precision
)
);
setInputProperties((inputProperties) => ({
...inputProperties,
quantity,
}));
}
setStateContext({
max,
min,
precision: unitOfMeasure.precision,
quantity,
step:
unitOfMeasure.incrementalOrderQuantity === step
? unitOfMeasure.incrementalOrderQuantity
: getMultiple(
unitOfMeasure.incrementalOrderQuantity,
step,
unitOfMeasure.precision
),
unitOfMeasure,
});
}
else {
setStateContext({
max: Math.ceil(max),
min: Math.ceil(min),
precision: 0,
quantity,
step: Math.ceil(step),
unitOfMeasure: null,
});
}
};
useEffect(() => {
debouncedSetVisibleErrorsRef.current(() => {
if (inputProperties.currentUnitOfMeasure) {
return getErrors(
inputProperties.quantity,
min,
max,
inputProperties.step,
inputProperties.currentUnitOfMeasure.precision,
allowEmptyValue
);
}
else {
return getErrors(
inputProperties.quantity,
Math.ceil(min),
Math.ceil(max),
Math.ceil(inputProperties.step),
0,
allowEmptyValue
);
}
});
}, [allowEmptyValue, inputProperties, max, min]);
useEffect(() => {
Liferay.on(
`${namespace}${CP_UNIT_OF_MEASURE_SELECTOR_CHANGED}`,
handleUOMChanged
);
return () => {
Liferay.detach(
`${namespace}${CP_UNIT_OF_MEASURE_SELECTOR_CHANGED}`,
handleUOMChanged
);
};
}, [handleUOMChanged, namespace]);
useEffect(() => {
setInputProperties((prevState) => {
return {
...prevState,
quantity,
};
});
}, [quantity]);
return (
{
setShowPopover(false);
}}
onChange={({target}) => {
const numValue = Number(target.value);
const errors = getErrors(
numValue,
min,
max,
inputProperties.step,
inputProperties.currentUnitOfMeasure?.precision,
allowEmptyValue
);
setInputProperties((inputProperties) => ({
...inputProperties,
quantity: target.value,
}));
onUpdate({
errors,
unitOfMeasure: inputProperties.currentUnitOfMeasure,
value: numValue,
});
}}
onFocus={() => setShowPopover(true)}
ref={inputRef}
step={inputProperties.step > 0 ? inputProperties.step : ''}
type="number"
value={String(inputProperties.quantity || '')}
/>
{showPopover &&
(inputProperties.step > 0 ||
min > 0 ||
visibleErrors.includes('max')) && (
)}
);
}
);
InputQuantitySelector.defaultProps = {
allowEmptyValue: false,
max: null,
min: 1,
step: 1,
};
export default InputQuantitySelector;
© 2015 - 2025 Weber Informatics LLC | Privacy Policy