@appbuckets/react-ui
Version:
Just Another React UI Framework
360 lines (354 loc) • 10.3 kB
JavaScript
'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;