UNPKG

@appbuckets/react-ui

Version:
360 lines (354 loc) 10.3 kB
'use strict'; var tslib = require('tslib'); var React = require('react'); var reactUiCore = require('@appbuckets/react-ui-core'); var formatters = require('@appbuckets/formatters'); require('../BucketTheme/BucketTheme.js'); var BucketContext = require('../BucketTheme/BucketContext.js'); var Input = require('../Input/Input.js'); var removeNumberFormatting = require('./lib/removeNumberFormatting.js'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty( n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; }, } ); } }); } n['default'] = e; return Object.freeze(n); } var React__namespace = /*#__PURE__*/ _interopNamespace(React); /* -------- * Component Render * -------- */ var NumericInput = React__namespace.forwardRef(function (receivedProps, ref) { var props = BucketContext.useWithDefaultProps('numericInput', receivedProps); var /** Functional Props */ allowNegative = props.allowNegative, userDefinedDefaultValue = props.defaultValue, max = props.max, min = props.min, userDefinedValue = props.value, /** Number formatter props */ decimalSeparator = props.decimalSeparator, flexibleDecimals = props.flexibleDecimals, minPrecision = props.minPrecision, pattern = props.pattern, precision = props.precision, prefix = props.prefix, suffix = props.suffix, thousandSeparator = props.thousandSeparator, /** User defined handlers */ userDefinedOnBlurHandler = props.onBlur, userDefinedOnChangeHandler = props.onChange; props.onKeyDown; var userDefinedOnFocusHandler = props.onFocus, /** All other field props */ restFieldProps = tslib.__rest(props, [ 'allowNegative', 'defaultValue', 'max', 'min', 'value', 'decimalSeparator', 'flexibleDecimals', 'minPrecision', 'pattern', 'precision', 'prefix', 'suffix', 'thousandSeparator', 'onBlur', 'onChange', 'onKeyDown', 'onFocus', ]); /* -------- * Internal Handlers and Helpers * -------- */ var formatNumber = React__namespace.useCallback( function (num) { /** If number is invalid, return an empty string */ if (num === undefined || num === null || Number.isNaN(num)) { return ''; } /** Return the formatted number */ return formatters.formatNumber(num, { decimalSeparator: decimalSeparator, flexibleDecimals: flexibleDecimals, minPrecision: minPrecision, pattern: pattern, precision: precision, prefix: prefix, suffix: suffix, thousandSeparator: thousandSeparator, }); }, [ decimalSeparator, flexibleDecimals, minPrecision, pattern, precision, prefix, suffix, thousandSeparator, ] ); /* -------- * Internal Helpers * -------- */ var getSafeNumber = React__namespace.useCallback( function (baseNumber) { /** If number is invalid, return null */ if (typeof baseNumber !== 'number' || Number.isNaN(baseNumber)) { return null; } /** If negative number are not allowed and number is negative, return null */ if (!allowNegative && baseNumber < 0) { return 0; } /** If a min limit exists, and number is inferior, return the min limit */ if (typeof min === 'number' && baseNumber < min) { return min; } /** If a max limit exists, and number is superior, return the max limit */ if (typeof max === 'number' && baseNumber > max) { return max; } return baseNumber; }, [allowNegative, max, min] ); /* -------- * Internal State * -------- */ var defaultValue = userDefinedDefaultValue === undefined ? undefined : getSafeNumber(userDefinedDefaultValue); var userValue = userDefinedValue === undefined ? undefined : getSafeNumber(userDefinedValue); var _a = tslib.__read( reactUiCore.useAutoControlledValue(null, { defaultProp: defaultValue, prop: userValue, }), 2 ), value = _a[0], trySetValue = _a[1]; var _b = tslib.__read( React__namespace.useState( (value !== null && value !== void 0 ? value : '').toString() ), 2 ), inputValue = _b[0], setInputValue = _b[1]; var _c = tslib.__read(React__namespace.useState(false), 2), isFocused = _c[0], setIsFocused = _c[1]; var formattedInputValue = React__namespace.useMemo( function () { return formatNumber(value); }, [formatNumber, value] ); var allowedKeys = React__namespace.useMemo( function () { /** Insert base keys */ var keys = tslib.__spreadArray( tslib.__spreadArray([], tslib.__read('0123456789'.split('')), false), [ /** Append functional keys */ 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Backspace', 'Delete', 'Enter', 'Tab', ], false ); /** If negative number are allowed, append minus symbol */ if (allowNegative) { keys.push('-'); } /** If decimalSeparator exists, add it */ if (decimalSeparator) { keys.push(decimalSeparator); } return keys; }, [decimalSeparator, allowNegative] ); // ---- // Asserting Formatted Value is Visualized // -- // Some libraries (as React Hook Form) will // set the value using internal dispatcher and // the ref object. // This produce an unexpected behaviour resulting // in an invalid formatted input. // Use a render effect to avoid this // ---- /** Get Refs and ref Handler */ var inputRef = React__namespace.useRef(null); var handleRef = reactUiCore.useForkRef(inputRef, ref); /** Register an effect */ reactUiCore.useEnhancedEffect(function () { /** Assert only on not focused element */ if (!isFocused && inputRef.current !== null) { inputRef.current.value = formattedInputValue; } }); /* -------- * Handlers * -------- */ var handleInputBlur = function (e) { /** Set input has not focused */ setIsFocused(false); /** If a user defined handler, call it */ if (userDefinedOnBlurHandler) { userDefinedOnBlurHandler( e, tslib.__assign(tslib.__assign({}, props), { value: value }) ); } }; var handleInputChange = function (e, inputProps) { /** Get the new Value */ var inputValueFromEvent = inputProps.value; /** Get the value without formatting, if exists */ var rawNumber = getSafeNumber( removeNumberFormatting.removeNumberFormatting( inputValueFromEvent, decimalSeparator ) ); /** If an user defined onChange handler exists, call it */ if (userDefinedOnChangeHandler) { userDefinedOnChangeHandler( null, tslib.__assign(tslib.__assign({}, props), { value: rawNumber }) ); } /** Set the input value */ setInputValue( inputValueFromEvent !== null && inputValueFromEvent !== void 0 ? inputValueFromEvent : '' ); /** Try to set the new state */ trySetValue(rawNumber); }; var handleInputKeyDown = function (e) { var _a; /** Get the key pressed */ var key = e.key, ctrlKey = e.ctrlKey; /** If key is not present into allowed keys, prevent inserting */ if (!allowedKeys.includes(key)) { e.preventDefault(); return; } /** If key is the decimal separator, but a decimals separator already exists, prevent */ if ( decimalSeparator && key === decimalSeparator && inputValue.indexOf(decimalSeparator) !== -1 ) { e.preventDefault(); return; } /** Use arrow to increase / decrease value */ if (key === 'ArrowDown' || key === 'ArrowUp') { /** Set the inc value */ var incValue = ctrlKey ? 10 : 1; /** Set the new value */ var newValue = getSafeNumber( key === 'ArrowDown' ? (value !== null && value !== void 0 ? value : 0) - incValue : (value !== null && value !== void 0 ? value : 0) + incValue ); /** If a user defined onChange handler exists, call it */ if (userDefinedOnChangeHandler) { userDefinedOnChangeHandler( null, tslib.__assign(tslib.__assign({}, props), { value: newValue }) ); } /** Set the input value */ setInputValue( (_a = newValue === null || newValue === void 0 ? void 0 : newValue.toString()) !== null && _a !== void 0 ? _a : '' ); /** Update value */ trySetValue(newValue); } }; var handleInputFocus = function (e) { var _a, _b; /** Set the new input value */ setInputValue( (_b = (_a = removeNumberFormatting.removeNumberFormatting( formattedInputValue, decimalSeparator )) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '' ); /** Set component has focused */ setIsFocused(true); /** If a user defined handler exists, call it */ if (userDefinedOnFocusHandler) { userDefinedOnFocusHandler( e, tslib.__assign(tslib.__assign({}, props), { value: value }) ); } }; /* -------- * Component Render * -------- */ return React__namespace.createElement( Input, tslib.__assign({}, restFieldProps, { ref: handleRef, value: isFocused ? inputValue : formattedInputValue, onBlur: handleInputBlur, onChange: handleInputChange, onKeyDown: handleInputKeyDown, onFocus: handleInputFocus, }) ); }); NumericInput.displayName = 'NumericInput'; module.exports = NumericInput;