@vertigis/react-ui 11.29.1 → 11.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ /// <reference types="react" />
2
+ import type { TextFieldProps } from "@mui/material/TextField";
3
+ import { type ColorInputProps } from "../ColorInput/ColorInput.js";
4
+ import type { FormControlProps } from "../FormControl/index.js";
5
+ export type FormLabelColorFieldProps = Omit<TextFieldProps, "variant" | "defaultValue" | "onFocus" | "onBlur" | "onChange" | "value" | "select" | "multiline" | "minRows" | "maxRows" | "SelectProps" | "rows"> & Pick<FormControlProps, "inlineHelpContent" | "inlineHelpUrl"> & {
6
+ defaultValue?: ColorInputProps["defaultValue"];
7
+ disabled?: ColorInputProps["disabled"];
8
+ onBlur?: ColorInputProps["onBlur"];
9
+ onChange?: ColorInputProps["onChange"];
10
+ onFocus?: ColorInputProps["onFocus"];
11
+ value: ColorInputProps["value"];
12
+ ColorInputProps?: ColorInputProps;
13
+ };
14
+ /**
15
+ * Like TextField, but uses a FormLabel instead of an InputLabel. Also allows
16
+ * for inline help.
17
+ */
18
+ declare const FormLabelColorField: import("react").ForwardRefExoticComponent<Pick<FormLabelColorFieldProps, "error" | "id" | "label" | "slot" | "style" | "title" | "type" | "results" | "size" | "role" | "resource" | "key" | "value" | "className" | "classes" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "hidden" | "lang" | "nonce" | "placeholder" | "spellCheck" | "tabIndex" | "translate" | "radioGroup" | "about" | "datatype" | "inlist" | "prefix" | "property" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "color" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "children" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "disabled" | "sx" | "margin" | "fullWidth" | "autoFocus" | "name" | "autoComplete" | "inputProps" | "inputRef" | "required" | "hiddenLabel" | "focused" | "InputProps" | "FormHelperTextProps" | "helperText" | "InputLabelProps" | "inlineHelpContent" | "inlineHelpUrl" | "ColorInputProps"> & import("react").RefAttributes<HTMLDivElement>>;
19
+ export default FormLabelColorField;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import ColorInput, {} from "../ColorInput/ColorInput.js";
4
+ import FormControl from "../FormControl/index.js";
5
+ import FormHelperText from "../FormHelperText/index.js";
6
+ import FormLabel from "../FormLabel/index.js";
7
+ import { useId } from "../utils/react.js";
8
+ /**
9
+ * Like TextField, but uses a FormLabel instead of an InputLabel. Also allows
10
+ * for inline help.
11
+ */
12
+ const FormLabelColorField = forwardRef(function FormLabelColorField(props, ref) {
13
+ const { defaultValue, FormHelperTextProps, fullWidth = false, helperText, id: idProp, label, onBlur, onChange, onFocus, placeholder, type, value, disabled, ColorInputProps, ...other } = props;
14
+ const id = useId(idProp);
15
+ const helperTextId = helperText && id ? `${id}-helper-text` : undefined;
16
+ const inputLabelId = label && id ? `${id}-label` : undefined;
17
+ return (_jsxs(FormControl, { fullWidth: fullWidth, ref: ref, disabled: disabled, ...other, children: [label && (_jsx(FormLabel, { htmlFor: id, id: inputLabelId, children: label })), helperText && (_jsx(FormHelperText, { id: helperTextId, ...FormHelperTextProps, children: helperText })), _jsx(ColorInput, { id: id, placeholder: placeholder, onBlur: onBlur, onFocus: onFocus, defaultValue: defaultValue, "aria-describedby": helperTextId, "aria-labelledby": inputLabelId, onChange: onChange, value: value, disabled: disabled, ...{
18
+ ...ColorInputProps,
19
+ // Workaround typings issue.
20
+ ref: ColorInputProps?.ref,
21
+ } })] }));
22
+ });
23
+ export default FormLabelColorField;
@@ -0,0 +1,2 @@
1
+ export * from "./FormLabelColorField.js";
2
+ export { default } from "./FormLabelColorField.js";
@@ -0,0 +1,2 @@
1
+ export * from "./FormLabelColorField.js";
2
+ export { default } from "./FormLabelColorField.js";
@@ -0,0 +1,32 @@
1
+ import type { TextFieldProps } from "@mui/material/TextField";
2
+ import { type FC } from "react";
3
+ import type { FormControlProps } from "../FormControl/index.js";
4
+ import { type NumberInputProps } from "../NumberInput/index.js";
5
+ /**
6
+ * Properties for FormLabelNumberField.
7
+ */
8
+ export type FormLabelNumberFieldProps = Omit<TextFieldProps, "aria-label" | "value" | "onChange" | "InputProps" | "select" | "nativeSelect" | "multiline" | "minRows" | "maxRows" | "SelectProps" | "rows" | "type" | "title" | "onError"> & Pick<FormControlProps, "inlineHelpContent" | "inlineHelpUrl"> & NumberInputProps & {
9
+ InputProps?: Omit<NumberInputProps, "ref">;
10
+ NumberInputComponent?: FC<NumberInputProps>;
11
+ /**
12
+ * A label to display as helpText when the number input value is out of
13
+ * range, relative to the min and max properties.
14
+ */
15
+ numberOutOfRangeErrorText?: string;
16
+ /**
17
+ * A label to display as helpText when the number input value is invalid
18
+ * (ie. NaN).
19
+ */
20
+ invalidNumberErrorText?: string;
21
+ /**
22
+ * A label to display as helpText when the number input value has more
23
+ * than the configured maxDecimalPlaces.
24
+ */
25
+ maxDecimalPlacesErrorText?: string;
26
+ };
27
+ /**
28
+ * Like TextField, but uses a FormLabel instead of an InputLabel, and uses
29
+ * NumberInput instead of a normal input.
30
+ */
31
+ declare const FormLabelNumberField: import("react").ForwardRefExoticComponent<Pick<FormLabelNumberFieldProps, "error" | "id" | "label" | "slot" | "style" | "title" | "results" | "size" | "role" | "resource" | "key" | "value" | "components" | "className" | "classes" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "hidden" | "lang" | "nonce" | "placeholder" | "spellCheck" | "tabIndex" | "translate" | "radioGroup" | "about" | "datatype" | "inlist" | "prefix" | "property" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "color" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "children" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "disabled" | "sx" | "margin" | "fullWidth" | "variant" | "autoFocus" | "name" | "autoComplete" | "componentsProps" | "disableInjectingGlobalStyles" | "endAdornment" | "inputComponent" | "inputProps" | "inputRef" | "multiline" | "readOnly" | "required" | "renderSuffix" | "rows" | "maxRows" | "minRows" | "slotProps" | "slots" | "startAdornment" | "hiddenLabel" | "disableUnderline" | "focused" | "InputProps" | "FormHelperTextProps" | "helperText" | "InputLabelProps" | "max" | "min" | "inlineHelpContent" | "inlineHelpUrl" | "onErrorEnd" | "allowUndefined" | "maxDecimalPlaces" | "correctOnBlur" | "NumberInputComponent" | "numberOutOfRangeErrorText" | "invalidNumberErrorText" | "maxDecimalPlacesErrorText"> & import("react").RefAttributes<HTMLDivElement>>;
32
+ export default FormLabelNumberField;
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useContext, useState } from "react";
3
+ import { forwardRef } from "react";
4
+ import FormControl from "../FormControl/index.js";
5
+ import FormHelperText from "../FormHelperText/index.js";
6
+ import FormLabel from "../FormLabel/index.js";
7
+ import { NumberFormatContext } from "../NumberFormatContext/NumberFormatContext.js";
8
+ import { DEFAULT_DECIMAL_PLACES, } from "../NumberInput/index.js";
9
+ import NumberInput from "../NumberInput/index.js";
10
+ import { useId } from "../utils/react.js";
11
+ /**
12
+ * Like TextField, but uses a FormLabel instead of an InputLabel, and uses
13
+ * NumberInput instead of a normal input.
14
+ */
15
+ const FormLabelNumberField = forwardRef(function FormLabelNumberField(props, ref) {
16
+ const { autoComplete, autoFocus = false, children, className, defaultValue, FormHelperTextProps, fullWidth = false, helperText, id: idProp, InputLabelProps, inputProps, InputProps, inputRef, label, name, NumberInputComponent, onBlur, onChange, onFocus, placeholder, value, min, max, numberOutOfRangeErrorText, invalidNumberErrorText, maxDecimalPlacesErrorText, error, onError, onErrorEnd, allowUndefined, maxDecimalPlaces = DEFAULT_DECIMAL_PLACES, correctOnBlur = true, ...other } = props;
17
+ const id = useId(idProp);
18
+ const helperTextId = helperText && id ? `${id}-helper-text` : undefined;
19
+ const inputLabelId = label && id ? `${id}-label` : undefined;
20
+ const FormNumberInput = NumberInputComponent ?? NumberInput;
21
+ const { parseNumber } = useContext(NumberFormatContext);
22
+ const [numberError, setNumberError] = useState();
23
+ const handleError = useCallback((numericValue, textValue, errorType) => {
24
+ if (errorType === "out-of-range") {
25
+ setNumberError(numberOutOfRangeErrorText ??
26
+ getNumberErrorText(parseNumber(textValue), min, max));
27
+ }
28
+ else if (errorType === "NaN") {
29
+ setNumberError(invalidNumberErrorText ?? "Value must be a valid number.");
30
+ }
31
+ else if (errorType === "max-decimals") {
32
+ setNumberError(maxDecimalPlacesErrorText ??
33
+ `Value must have at most ${maxDecimalPlaces} decimal places.`);
34
+ }
35
+ onError?.(numericValue, textValue, errorType);
36
+ }, [
37
+ onError,
38
+ max,
39
+ min,
40
+ parseNumber,
41
+ invalidNumberErrorText,
42
+ numberOutOfRangeErrorText,
43
+ maxDecimalPlaces,
44
+ maxDecimalPlacesErrorText,
45
+ ]);
46
+ const handleErrorEnd = useCallback((numericValue, textValue) => {
47
+ setNumberError(undefined);
48
+ onErrorEnd?.(numericValue, textValue);
49
+ }, [onErrorEnd]);
50
+ return (_jsxs(FormControl, { fullWidth: fullWidth, ref: ref, error: error || !!numberError, ...other, children: [label && (_jsx(FormLabel, { htmlFor: id, id: inputLabelId, children: label })), (helperText || numberError) && (_jsx(FormHelperText, { id: helperTextId, ...FormHelperTextProps, children: numberError ?? helperText })), _jsx(FormNumberInput, { "aria-describedby": helperTextId, autoComplete: autoComplete,
51
+ // eslint-disable-next-line jsx-a11y/no-autofocus
52
+ autoFocus: autoFocus, defaultValue: defaultValue, fullWidth: fullWidth, name: name, value: value, id: id, inputRef: inputRef, onBlur: onBlur, onChange: onChange, onFocus: onFocus, placeholder: placeholder, inputProps: inputProps, error: error, onError: handleError, onErrorEnd: handleErrorEnd, min: min, max: max, allowUndefined: allowUndefined, maxDecimalPlaces: maxDecimalPlaces, correctOnBlur: correctOnBlur, ...InputProps })] }));
53
+ });
54
+ function getNumberErrorText(val, min, max) {
55
+ const hasMax = typeof max === "number" && !isNaN(max);
56
+ const hasMin = typeof min === "number" && !isNaN(min);
57
+ const isBelow = hasMin && val < min;
58
+ const isAbove = hasMax && val > max;
59
+ if (isNaN(val) || (!isAbove && !isBelow)) {
60
+ return undefined;
61
+ }
62
+ else if (hasMin && hasMax) {
63
+ return `Value must be between ${min} and ${max}.`;
64
+ }
65
+ else if (hasMin) {
66
+ return `Value must be greater than ${min}.`;
67
+ }
68
+ else if (hasMax) {
69
+ return `Value must be less than ${max}.`;
70
+ }
71
+ else {
72
+ return undefined;
73
+ }
74
+ }
75
+ export default FormLabelNumberField;
@@ -0,0 +1,2 @@
1
+ export * from "./FormLabelNumberField.js";
2
+ export { default } from "./FormLabelNumberField.js";
@@ -0,0 +1,2 @@
1
+ export * from "./FormLabelNumberField.js";
2
+ export { default } from "./FormLabelNumberField.js";
@@ -0,0 +1,43 @@
1
+ import { type SliderProps } from "@mui/material";
2
+ import type { TextFieldProps } from "@mui/material/TextField";
3
+ import { type FC } from "react";
4
+ import type { FormControlProps } from "../FormControl/index.js";
5
+ import { type ErrorType, type NumberInputProps } from "../NumberInput/index.js";
6
+ export type FormLabelSliderFieldProps = Omit<TextFieldProps, "variant" | "Value" | "onChange" | "defaultValue" | "type" | "select" | "multiline" | "minRows" | "maxRows" | "SelectProps" | "rows" | "InputProps" | "inputProps" | "onError"> & Pick<FormControlProps, "inlineHelpContent" | "inlineHelpUrl"> & {
7
+ value?: number;
8
+ onChange?: (value: number) => void;
9
+ SliderProps?: SliderProps;
10
+ NumberInputComponent?: FC<NumberInputProps>;
11
+ InputProps?: Omit<NumberInputProps, "allowUndefined">;
12
+ inputProps?: NumberInputProps["inputProps"];
13
+ defaultValue?: number;
14
+ /**
15
+ * A label to display as helpText when the number input value is out of
16
+ * range, relative to the min and max properties.
17
+ */
18
+ numberOutOfRangeErrorText?: string;
19
+ /**
20
+ * A label to display as helpText when the number input value is invalid
21
+ * (ie. NaN).
22
+ */
23
+ invalidNumberErrorText?: string;
24
+ /**
25
+ * A label to display as helpText when the number input value has more
26
+ * than the configured maxDecimalPlaces.
27
+ */
28
+ maxDecimalPlacesErrorText?: string;
29
+ /**
30
+ * Executed when the input moves into an error state.
31
+ */
32
+ onError?: (value: number, textValue: string, type: ErrorType) => void;
33
+ /**
34
+ * Executed when the input moves out of an error state.
35
+ */
36
+ onErrorEnd?: (value: number, textValue: string) => void;
37
+ };
38
+ /**
39
+ * Like TextField, but uses a FormLabel instead of an InputLabel. Also allows
40
+ * for inline help.
41
+ */
42
+ declare const FormLabelSliderField: import("react").ForwardRefExoticComponent<Pick<FormLabelSliderFieldProps, "error" | "id" | "label" | "slot" | "style" | "title" | "results" | "size" | "role" | "resource" | "key" | "value" | "className" | "classes" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "hidden" | "lang" | "nonce" | "placeholder" | "spellCheck" | "tabIndex" | "translate" | "radioGroup" | "about" | "datatype" | "inlist" | "prefix" | "property" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "color" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "children" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "disabled" | "sx" | "margin" | "fullWidth" | "autoFocus" | "name" | "autoComplete" | "inputProps" | "inputRef" | "required" | "hiddenLabel" | "focused" | "InputProps" | "FormHelperTextProps" | "helperText" | "InputLabelProps" | "inlineHelpContent" | "inlineHelpUrl" | "onErrorEnd" | "NumberInputComponent" | "numberOutOfRangeErrorText" | "invalidNumberErrorText" | "maxDecimalPlacesErrorText" | "SliderProps"> & import("react").RefAttributes<HTMLDivElement>>;
43
+ export default FormLabelSliderField;
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Slider, useTheme } from "@mui/material";
3
+ import { Stack } from "@mui/material";
4
+ import { forwardRef, useCallback, useContext, useRef, useState } from "react";
5
+ import FormControl from "../FormControl/index.js";
6
+ import FormHelperText from "../FormHelperText/index.js";
7
+ import FormLabel from "../FormLabel/index.js";
8
+ import { NumberFormatContext } from "../NumberFormatContext/NumberFormatContext.js";
9
+ import NumberInput, { DEFAULT_DECIMAL_PLACES, } from "../NumberInput/index.js";
10
+ import { useId } from "../utils/react.js";
11
+ /**
12
+ * Like TextField, but uses a FormLabel instead of an InputLabel. Also allows
13
+ * for inline help.
14
+ */
15
+ const FormLabelSliderField = forwardRef(function FormLabelSliderField(props, ref) {
16
+ const { autoComplete, autoFocus = false, defaultValue, disabled, FormHelperTextProps, fullWidth = false, helperText, id: idProp, InputLabelProps, inputProps, InputProps, inputRef, label, name, onBlur, onChange, onFocus, onKeyDown, onMouseUp, placeholder, value, SliderProps, numberOutOfRangeErrorText, invalidNumberErrorText, onError, onErrorEnd, error, maxDecimalPlacesErrorText, NumberInputComponent, ...other } = props;
17
+ const inputId = `${useId(idProp)}-input`;
18
+ const sliderId = `${useId(idProp)}-slider`;
19
+ const helperTextId = helperText && inputId ? `${inputId}-helper-text` : undefined;
20
+ const inputLabelId = label && inputId ? `${inputId}-label` : undefined;
21
+ const inputReference = useRef();
22
+ const { typography } = useTheme();
23
+ const handleSliderChange = useCallback((event, value) => {
24
+ onChange?.(value);
25
+ }, [onChange]);
26
+ const max = SliderProps?.max ?? InputProps?.max ?? 100;
27
+ const min = SliderProps?.min ?? InputProps?.min ?? 0;
28
+ const { parseNumber } = useContext(NumberFormatContext);
29
+ const [numberError, setNumberError] = useState();
30
+ const maxDecimalPlaces = InputProps?.maxDecimalPlaces ?? DEFAULT_DECIMAL_PLACES;
31
+ const handleError = useCallback((numericValue, textValue, errorType) => {
32
+ if (errorType === "out-of-range") {
33
+ setNumberError(numberOutOfRangeErrorText ??
34
+ getNumberErrorText(parseNumber(textValue), min, max));
35
+ }
36
+ else if (errorType === "NaN") {
37
+ setNumberError(invalidNumberErrorText ?? "Value must be a valid number.");
38
+ }
39
+ else if (errorType === "max-decimals") {
40
+ setNumberError(maxDecimalPlacesErrorText ??
41
+ `Value must have at most ${maxDecimalPlaces} decimal places.`);
42
+ }
43
+ onError?.(numericValue, textValue, errorType);
44
+ }, [
45
+ onError,
46
+ max,
47
+ min,
48
+ parseNumber,
49
+ invalidNumberErrorText,
50
+ numberOutOfRangeErrorText,
51
+ maxDecimalPlaces,
52
+ maxDecimalPlacesErrorText,
53
+ ]);
54
+ const handleErrorEnd = useCallback((numericValue, textValue) => {
55
+ setNumberError(undefined);
56
+ onErrorEnd?.(numericValue, textValue);
57
+ }, [onErrorEnd]);
58
+ const FormNumberInput = NumberInputComponent ?? NumberInput;
59
+ const InputElement = (_jsx(FormNumberInput, { "aria-labelledby": inputLabelId, "aria-describedby": helperTextId, autoComplete: autoComplete,
60
+ // Disabling this linting rule since the intent is to forward it
61
+ // if it exists rather than set it.
62
+ //
63
+ // eslint-disable-next-line jsx-a11y/no-autofocus
64
+ autoFocus: autoFocus, defaultValue: defaultValue, fullWidth: fullWidth, id: inputId, inputProps: inputProps, inputRef: inputRef ?? inputReference, name: name, onChange: onChange, placeholder: placeholder, value: value, error: error, onError: handleError, onErrorEnd: handleErrorEnd, ...{ min, max, ...InputProps }, sx: { maxWidth: typography.pxToRem(74), ...InputProps?.sx } }));
65
+ return (_jsxs(FormControl, { fullWidth: fullWidth, ref: ref, disabled: disabled, error: error || !!numberError, ...other, children: [label && (_jsx(FormLabel, { htmlFor: inputId, id: inputLabelId, children: label })), (helperText || !!numberError) && (_jsx(FormHelperText, { id: helperTextId, ...FormHelperTextProps, children: numberError ?? helperText })), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Slider, { "aria-describedby": helperTextId, "aria-labelledby": inputLabelId, defaultValue: defaultValue, name: name, id: sliderId, value: value, onChange: handleSliderChange, disabled: disabled, ...{ min, max, ...SliderProps } }), InputElement] })] }));
66
+ });
67
+ function getNumberErrorText(val, min, max) {
68
+ const hasMax = typeof max === "number" && !isNaN(max);
69
+ const hasMin = typeof min === "number" && !isNaN(min);
70
+ const isBelow = hasMin && val < min;
71
+ const isAbove = hasMax && val > max;
72
+ if (isNaN(val) || (!isAbove && !isBelow)) {
73
+ return undefined;
74
+ }
75
+ else if (hasMin && hasMax) {
76
+ return `Value must be between ${min} and ${max}.`;
77
+ }
78
+ else if (hasMin) {
79
+ return `Value must be greater than ${min}.`;
80
+ }
81
+ else if (hasMax) {
82
+ return `Value must be less than ${max}.`;
83
+ }
84
+ else {
85
+ return undefined;
86
+ }
87
+ }
88
+ export default FormLabelSliderField;
@@ -0,0 +1,2 @@
1
+ export * from "./FormLabelSliderField.js";
2
+ export { default } from "./FormLabelSliderField.js";
@@ -0,0 +1,2 @@
1
+ export * from "./FormLabelSliderField.js";
2
+ export { default } from "./FormLabelSliderField.js";
@@ -0,0 +1,20 @@
1
+ /// <reference types="react" />
2
+ export interface NumberFormatContextProps {
3
+ /**
4
+ * Returns a stringified number that may include additional/alternative
5
+ * formatting. By default, this will attempt to directly convert a number to
6
+ * a string.
7
+ */
8
+ formatNumber: (n: number) => string;
9
+ /**
10
+ * Returns a number parsed from a string that may include
11
+ * additional/alternative formatting. By default, this will attempt to
12
+ * directly convert a string to a number.
13
+ */
14
+ parseNumber: (s: string) => number;
15
+ }
16
+ /**
17
+ * A React context for components that gives access to number formatting
18
+ * utilities.
19
+ */
20
+ export declare const NumberFormatContext: import("react").Context<NumberFormatContextProps>;
@@ -0,0 +1,15 @@
1
+ import { createContext } from "react";
2
+ const formatNumber = (n) => {
3
+ return `${n}`;
4
+ };
5
+ const parseNumber = (s) => {
6
+ return +s;
7
+ };
8
+ /**
9
+ * A React context for components that gives access to number formatting
10
+ * utilities.
11
+ */
12
+ export const NumberFormatContext = createContext({
13
+ formatNumber,
14
+ parseNumber,
15
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./NumberFormatContext.js";
2
+ export { NumberFormatContext as default } from "./NumberFormatContext.js";
@@ -0,0 +1,2 @@
1
+ export * from "./NumberFormatContext.js";
2
+ export { NumberFormatContext as default } from "./NumberFormatContext.js";
@@ -0,0 +1,57 @@
1
+ /// <reference types="react" />
2
+ import { type InputProps } from "@mui/material/Input";
3
+ export declare const DEFAULT_DECIMAL_PLACES = 16;
4
+ export type ErrorType = "NaN" | "out-of-range" | "max-decimals" | "unknown";
5
+ /**
6
+ * Properties for the `NumberInput` component.
7
+ */
8
+ export interface NumberInputProps extends Omit<InputProps, "type" | "onChange" | "value" | "onError"> {
9
+ /**
10
+ * Sets the value to show in the input for controlled components. If not
11
+ * specified, the input will be uncontrolled instead.
12
+ */
13
+ value?: number;
14
+ /**
15
+ * A callback to fire when selected values change.
16
+ */
17
+ onChange?: (value: number | undefined) => void;
18
+ /**
19
+ * The maximum value possible for the number input.
20
+ */
21
+ max?: number;
22
+ /**
23
+ * The minimum value possible for the number input.
24
+ */
25
+ min?: number;
26
+ /**
27
+ * Executed when the input moves into an error state.
28
+ */
29
+ onError?: (value: number, textValue: string, type: ErrorType) => void;
30
+ /**
31
+ * Executed when the input moves out of an error state.
32
+ */
33
+ onErrorEnd?: (value: number, textValue: string) => void;
34
+ /**
35
+ * Whether to consider an empty field ("") as valid. When this property is
36
+ * true and the field is set to an empty value, the onChange callback will
37
+ * be executed with an undefined argument value.
38
+ */
39
+ allowUndefined?: boolean;
40
+ /**
41
+ * The maximum number of decimal places. Must be a number between 0 and 16.
42
+ * Defaults to 16.
43
+ */
44
+ maxDecimalPlaces?: number;
45
+ /**
46
+ * Whether or not the input should automatically correct invalid values on
47
+ * blur to the last valid value. Defaults to true.
48
+ */
49
+ correctOnBlur?: boolean;
50
+ }
51
+ /**
52
+ * A number input component that leverages the NumberFormatContext for
53
+ * formatting and parsing. Default formatting and parsing can be overridden by
54
+ * wrapping this component in a NumberFormatContext.Provider.
55
+ */
56
+ declare const NumberInput: import("react").ForwardRefExoticComponent<Pick<NumberInputProps, "error" | "id" | "slot" | "style" | "title" | "results" | "size" | "role" | "resource" | "value" | "components" | "className" | "classes" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "hidden" | "lang" | "nonce" | "placeholder" | "spellCheck" | "tabIndex" | "translate" | "radioGroup" | "about" | "datatype" | "inlist" | "prefix" | "property" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "color" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "disabled" | "sx" | "margin" | "fullWidth" | "autoFocus" | "name" | "autoComplete" | "componentsProps" | "disableInjectingGlobalStyles" | "endAdornment" | "inputComponent" | "inputProps" | "inputRef" | "multiline" | "readOnly" | "required" | "renderSuffix" | "rows" | "maxRows" | "minRows" | "slotProps" | "slots" | "startAdornment" | "disableUnderline" | "max" | "min" | "onErrorEnd" | "allowUndefined" | "maxDecimalPlaces" | "correctOnBlur"> & import("react").RefAttributes<unknown>>;
57
+ export default NumberInput;
@@ -0,0 +1,143 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import Input, {} from "@mui/material/Input";
3
+ import { forwardRef, useCallback, useContext, useEffect, useState } from "react";
4
+ import { NumberFormatContext } from "../NumberFormatContext/index.js";
5
+ import { usePrevious } from "../utils/react.js";
6
+ export const DEFAULT_DECIMAL_PLACES = 16;
7
+ /**
8
+ * A number input component that leverages the NumberFormatContext for
9
+ * formatting and parsing. Default formatting and parsing can be overridden by
10
+ * wrapping this component in a NumberFormatContext.Provider.
11
+ */
12
+ const NumberInput = forwardRef(function NumberInput({ onChange, onBlur, value, defaultValue, max, min, error, onError, onErrorEnd, allowUndefined, correctOnBlur = true, maxDecimalPlaces = DEFAULT_DECIMAL_PLACES, ...props }, ref) {
13
+ const { formatNumber, parseNumber } = useContext(NumberFormatContext);
14
+ const [textValue, setTextValue] = useState(value === undefined ? "" : formatNumber(value));
15
+ const [numericValue, setNumericValue] = useState(value === undefined ? NaN : value);
16
+ const [lastValid, setLastValid] = useState(numericValue);
17
+ const isValidNaN = useCallback((val) => allowUndefined && !val?.trim(), [allowUndefined]);
18
+ const isOutOfRange = useCallback((val) => (typeof min === "number" && val < min) || (typeof max === "number" && val > max), [min, max]);
19
+ const isTooPrecise = useCallback((val) => getDecimalPlaces(val) > maxDecimalPlaces, [maxDecimalPlaces]);
20
+ const invalidError = isValidNaN(textValue)
21
+ ? false
22
+ : // If the user begins to type a negative number, do not present an invalid
23
+ // error.
24
+ textValue?.trim() !== "-" && parseNumber(textValue) !== numericValue;
25
+ const previousError = usePrevious(error || invalidError);
26
+ useEffect(() => {
27
+ if (!isNaN(numericValue) && !isOutOfRange(numericValue) && !isTooPrecise(numericValue)) {
28
+ setLastValid(numericValue);
29
+ }
30
+ }, [numericValue, isOutOfRange, isTooPrecise]);
31
+ useEffect(() => {
32
+ const e = error || invalidError;
33
+ if (e !== previousError) {
34
+ if (invalidError) {
35
+ const inputVal = parseNumber(textValue);
36
+ let type;
37
+ if (isNaN(numericValue)) {
38
+ type = "NaN";
39
+ }
40
+ else if (isOutOfRange(inputVal)) {
41
+ type = "out-of-range";
42
+ }
43
+ else if (isTooPrecise(inputVal)) {
44
+ type = "max-decimals";
45
+ }
46
+ else {
47
+ type = "unknown";
48
+ }
49
+ onError?.(numericValue, textValue, type);
50
+ }
51
+ else if (error) {
52
+ onError?.(numericValue, textValue, "unknown");
53
+ }
54
+ else {
55
+ onErrorEnd?.(numericValue, textValue);
56
+ }
57
+ }
58
+ }, [
59
+ error,
60
+ previousError,
61
+ numericValue,
62
+ textValue,
63
+ onError,
64
+ onErrorEnd,
65
+ invalidError,
66
+ isOutOfRange,
67
+ isTooPrecise,
68
+ parseNumber,
69
+ ]);
70
+ const reformat = useCallback(() => {
71
+ if ((isNaN(numericValue) && !isValidNaN(textValue)) || isOutOfRange(numericValue)) {
72
+ setNumericValue(lastValid);
73
+ setTextValue(formatNumber(lastValid));
74
+ onChange?.(lastValid);
75
+ }
76
+ else if (!isNaN(numericValue)) {
77
+ setTextValue(formatNumber(numericValue));
78
+ }
79
+ }, [formatNumber, isOutOfRange, isValidNaN, lastValid, numericValue, onChange, textValue]);
80
+ const correctValue = useCallback((val) => {
81
+ let newValue = val;
82
+ if (!isNaN(newValue) && typeof newValue === "number") {
83
+ if (typeof max === "number") {
84
+ newValue = Math.min(newValue, max);
85
+ }
86
+ if (typeof min === "number") {
87
+ newValue = Math.max(newValue, min);
88
+ }
89
+ const decimalPlaces = getDecimalPlaces(newValue);
90
+ const maxDecimals = Math.min(Math.max(0, maxDecimalPlaces), DEFAULT_DECIMAL_PLACES);
91
+ if (decimalPlaces > maxDecimals) {
92
+ newValue = +newValue.toFixed(maxDecimals);
93
+ }
94
+ }
95
+ return newValue;
96
+ }, [max, min, maxDecimalPlaces]);
97
+ useEffect(() => {
98
+ if (value !== undefined) {
99
+ // For controlled components, only update the text from the value if
100
+ // it's a valid number, and represents a number different from the
101
+ // one in the text box. Otherwise leave it alone so that it's not
102
+ // constantly reformatting as the user is typing.
103
+ if (!isNaN(value) && value !== numericValue) {
104
+ setTextValue(formatNumber(value));
105
+ }
106
+ setNumericValue(correctValue(value));
107
+ }
108
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- It's intentional to omit numericValue. We only want this to run when the incoming value changes, not when numericValue changes in handleChange().
109
+ }, [value, formatNumber, correctValue]);
110
+ const handleChange = useCallback(event => {
111
+ const value = event.currentTarget.value;
112
+ setTextValue(value);
113
+ if (isValidNaN(value)) {
114
+ setNumericValue(NaN);
115
+ onChange?.(undefined);
116
+ }
117
+ else {
118
+ const newValue = correctValue(parseNumber(value));
119
+ setNumericValue(newValue);
120
+ onChange?.(newValue);
121
+ }
122
+ }, [correctValue, onChange, parseNumber, isValidNaN]);
123
+ const handleBlur = useCallback((event) => {
124
+ if (correctOnBlur) {
125
+ reformat();
126
+ }
127
+ onBlur?.(event);
128
+ }, [correctOnBlur, reformat, onBlur]);
129
+ return (_jsx(Input, { ref: ref, type: "text", value: textValue, onChange: handleChange, onBlur: handleBlur, defaultValue: typeof defaultValue === "number" ? formatNumber(defaultValue) : undefined, "data-test": "NumberInput-container", error: error || invalidError, ...props }));
130
+ });
131
+ export default NumberInput;
132
+ function getDecimalPlaces(value) {
133
+ const text = value.toString();
134
+ if (text.includes("e-")) {
135
+ const [, exponent] = text.split("e-");
136
+ return parseInt(exponent, 10);
137
+ }
138
+ if (Math.floor(value) !== value) {
139
+ const [, decimals] = text.split(".");
140
+ return decimals.length ?? 0;
141
+ }
142
+ return 0;
143
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./NumberInput.js";
2
+ export { default } from "./NumberInput.js";
@@ -0,0 +1,2 @@
1
+ export * from "./NumberInput.js";
2
+ export { default } from "./NumberInput.js";