UNPKG

@mskcc/carbon-react

Version:

Carbon react components for the MSKCC DSM

497 lines (485 loc) 16 kB
/** * MSKCC 2021, 2024 */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js'); var iconsReact = require('@carbon/icons-react'); var cx = require('classnames'); var PropTypes = require('prop-types'); var React = require('react'); var useMergedRefs = require('../../internal/useMergedRefs.js'); var useNormalizedInputProps = require('../../internal/useNormalizedInputProps.js'); var usePrefix = require('../../internal/usePrefix.js'); var deprecate = require('../../prop-types/deprecate.js'); require('../FluidForm/FluidForm.js'); var FormContext = require('../FluidForm/FormContext.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx); var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes); var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var _Subtract, _Add; const translationIds = { 'increment.number': 'increment.number', 'decrement.number': 'decrement.number' }; const defaultTranslations = { [translationIds['increment.number']]: 'Increment number', [translationIds['decrement.number']]: 'Decrement number' }; const NumberInput = /*#__PURE__*/React__default["default"].forwardRef(function NumberInput(props, forwardRef) { const { allowEmpty = false, className: customClassName, disabled = false, disableWheel: disableWheelProp = false, defaultValue, helperText = '', hideLabel = false, hideSteppers, iconDescription, id, label, invalid = false, invalidText, light, max, min, onChange, onClick, onKeyUp, readOnly, size = 'md', step = 1, translateWithId: t = id => defaultTranslations[id], warn = false, warnText = '', value: controlledValue, ...rest } = props; const prefix = usePrefix.usePrefix(); const { isFluid } = React.useContext(FormContext.FormContext); const [isFocused, setIsFocused] = React.useState(false); const [value, setValue] = React.useState(() => { if (controlledValue !== undefined) { return controlledValue; } if (defaultValue !== undefined) { return defaultValue; } return 0; }); const [prevControlledValue, setPrevControlledValue] = React.useState(controlledValue); const inputRef = React.useRef(null); const ref = useMergedRefs.useMergedRefs([forwardRef, inputRef]); const numberInputClasses = cx__default["default"]({ [`${prefix}--number`]: true, [`${prefix}--number--helpertext`]: true, [`${prefix}--number--readonly`]: readOnly, [`${prefix}--number--light`]: light, [`${prefix}--number--nolabel`]: hideLabel, [`${prefix}--number--nosteppers`]: hideSteppers, [`${prefix}--number--${size}`]: size }); const isInputValid = getInputValidity({ allowEmpty, invalid, value, max, min }); const normalizedProps = useNormalizedInputProps.useNormalizedInputProps({ id, readOnly, disabled, invalid: !isInputValid, invalidText, warn, warnText }); const [incrementNumLabel, decrementNumLabel] = [t('increment.number'), t('decrement.number')]; const wrapperClasses = cx__default["default"](`${prefix}--number__input-wrapper`, { [`${prefix}--number__input-wrapper--warning`]: normalizedProps.warn }); const iconClasses = cx__default["default"]({ [`${prefix}--number__invalid`]: normalizedProps.invalid || normalizedProps.warn, [`${prefix}--number__invalid--warning`]: normalizedProps.warn }); if (controlledValue !== prevControlledValue) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion setValue(controlledValue); setPrevControlledValue(controlledValue); } let ariaDescribedBy = undefined; if (normalizedProps.invalid) { ariaDescribedBy = normalizedProps.invalidId; } if (normalizedProps.warn) { ariaDescribedBy = normalizedProps.warnId; } if (!normalizedProps.validation) { ariaDescribedBy = helperText ? normalizedProps.helperId : undefined; } function handleOnChange(event) { if (disabled) { return; } const state = { value: event.target.value, direction: value < event.target.value ? 'up' : 'down' }; setValue(state.value); if (onChange) { onChange(event, state); } } const handleFocus = evt => { if ('type' in evt.target && evt.target.type === 'button') { setIsFocused(false); } else { setIsFocused(evt.type === 'focus' ? true : false); } }; const outerElementClasses = cx__default["default"](`${prefix}--form-item`, { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion [customClassName]: !!customClassName, [`${prefix}--number-input--fluid--invalid`]: isFluid && normalizedProps.invalid, [`${prefix}--number-input--fluid--focus`]: isFluid && isFocused, [`${prefix}--number-input--fluid--disabled`]: isFluid && disabled }); const Icon = normalizedProps.icon; return /*#__PURE__*/React__default["default"].createElement("div", { className: outerElementClasses, onFocus: isFluid ? handleFocus : undefined, onBlur: isFluid ? handleFocus : undefined }, /*#__PURE__*/React__default["default"].createElement("div", { className: numberInputClasses, "data-invalid": normalizedProps.invalid ? true : undefined }, /*#__PURE__*/React__default["default"].createElement(Label, { disabled: normalizedProps.disabled, hideLabel: hideLabel, id: id, label: label }), /*#__PURE__*/React__default["default"].createElement("div", { className: wrapperClasses }, /*#__PURE__*/React__default["default"].createElement("input", _rollupPluginBabelHelpers["extends"]({}, rest, { "data-invalid": normalizedProps.invalid ? true : undefined, "aria-invalid": normalizedProps.invalid, "aria-describedby": ariaDescribedBy, disabled: normalizedProps.disabled, ref: ref, id: id, max: max, min: min, onClick: onClick, onChange: handleOnChange, onKeyUp: onKeyUp, onFocus: e => { if (disableWheelProp) { e.target.addEventListener('wheel', disableWheel); } if (rest.onFocus) { rest.onFocus(e); } }, onBlur: e => { if (disableWheelProp) { e.target.removeEventListener('wheel', disableWheel); } if (rest.onBlur) { rest.onBlur(e); } }, pattern: "[0-9]*", readOnly: readOnly, step: step, type: "number", value: value })), Icon ? /*#__PURE__*/React__default["default"].createElement(Icon, { className: iconClasses }) : null, !hideSteppers && /*#__PURE__*/React__default["default"].createElement("div", { className: `${prefix}--number__controls` }, /*#__PURE__*/React__default["default"].createElement("button", { "aria-label": decrementNumLabel || iconDescription, className: `${prefix}--number__control-btn down-icon`, disabled: disabled || readOnly, onClick: event => { const state = { value: clamp(max, min, parseInt(value) - step), direction: 'down' }; setValue(state.value); if (onChange) { onChange(event, state); } if (onClick) { onClick(event, state); } }, tabIndex: -1, title: decrementNumLabel || iconDescription, type: "button" }, _Subtract || (_Subtract = /*#__PURE__*/React__default["default"].createElement(iconsReact.Subtract, { className: "down-icon" }))), /*#__PURE__*/React__default["default"].createElement("div", { className: `${prefix}--number__rule-divider` }), /*#__PURE__*/React__default["default"].createElement("button", { "aria-label": incrementNumLabel || iconDescription, className: `${prefix}--number__control-btn up-icon`, disabled: disabled || readOnly, onClick: event => { const state = { value: clamp(max, min, parseInt(value) + step), direction: 'up' }; setValue(state.value); if (onChange) { onChange(event, state); } if (onClick) { onClick(event, state); } }, tabIndex: -1, title: incrementNumLabel || iconDescription, type: "button" }, _Add || (_Add = /*#__PURE__*/React__default["default"].createElement(iconsReact.Add, { className: "up-icon" }))), /*#__PURE__*/React__default["default"].createElement("div", { className: `${prefix}--number__rule-divider` }))), isFluid && /*#__PURE__*/React__default["default"].createElement("hr", { className: `${prefix}--number-input__divider` }), normalizedProps.validation ? normalizedProps.validation : /*#__PURE__*/React__default["default"].createElement(HelperText, { id: normalizedProps.helperId, disabled: disabled, description: helperText }))); }); NumberInput.propTypes = { /** * `true` to allow empty string. */ allowEmpty: PropTypes__default["default"].bool, /** * Specify an optional className to be applied to the wrapper node */ className: PropTypes__default["default"].string, /** * Optional starting value for uncontrolled state */ defaultValue: PropTypes__default["default"].oneOfType([PropTypes__default["default"].number, PropTypes__default["default"].string]), /** * Specify if the wheel functionality for the input should be disabled, or not */ disableWheel: PropTypes__default["default"].bool, /** * Specify if the control should be disabled, or not */ disabled: PropTypes__default["default"].bool, /** * Provide text that is used alongside the control label for additional help */ helperText: PropTypes__default["default"].node, /** * Specify whether you want the underlying label to be visually hidden */ hideLabel: PropTypes__default["default"].bool, /** * Specify whether you want the steppers to be hidden */ hideSteppers: PropTypes__default["default"].bool, /** * Provide a description for up/down icons that can be read by screen readers */ iconDescription: PropTypes__default["default"].string, /** * Specify a custom `id` for the input */ id: PropTypes__default["default"].string.isRequired, /** * Specify if the currently value is invalid. */ invalid: PropTypes__default["default"].bool, /** * Message which is displayed if the value is invalid. */ invalidText: PropTypes__default["default"].node, /** * Generic `label` that will be used as the textual representation of what * this field is for */ label: PropTypes__default["default"].node, /** * `true` to use the light version. */ light: deprecate["default"](PropTypes__default["default"].bool, 'The `light` prop for `NumberInput` is no longer needed and has ' + 'been deprecated in v11 in favor of the new `Layer` component. It will be moved in the next major release.'), /** * The maximum value. */ max: PropTypes__default["default"].number, /** * The minimum value. */ min: PropTypes__default["default"].number, /** * Provide an optional handler that is called when the internal state of * NumberInput changes. This handler is called with event and state info. * `(event, { value, direction }) => void` */ onChange: PropTypes__default["default"].func, /** * Provide an optional function to be called when the up/down button is clicked */ onClick: PropTypes__default["default"].func, /** * Provide an optional function to be called when a key is pressed in the number input */ onKeyUp: PropTypes__default["default"].func, /** * Specify if the component should be read-only */ readOnly: PropTypes__default["default"].bool, /** * Specify the size of the Number Input. */ size: PropTypes__default["default"].oneOf(['sm', 'md', 'lg']), /** * Specify how much the values should increase/decrease upon clicking on up/down button */ step: PropTypes__default["default"].number, /** * Provide custom text for the component for each translation id */ translateWithId: PropTypes__default["default"].func, /** * Specify the value of the input */ value: PropTypes__default["default"].oneOfType([PropTypes__default["default"].number, PropTypes__default["default"].string]), /** * Specify whether the control is currently in warning state */ warn: PropTypes__default["default"].bool, /** * Provide the text that is displayed when the control is in warning state */ warnText: PropTypes__default["default"].node }; function Label(_ref) { let { disabled, id, hideLabel, label } = _ref; const prefix = usePrefix.usePrefix(); const className = cx__default["default"]({ [`${prefix}--label`]: true, [`${prefix}--label--disabled`]: disabled, [`${prefix}--visually-hidden`]: hideLabel }); if (label) { return /*#__PURE__*/React__default["default"].createElement("label", { htmlFor: id, className: className }, label); } return null; } Label.propTypes = { disabled: PropTypes__default["default"].bool, hideLabel: PropTypes__default["default"].bool, id: PropTypes__default["default"].string, label: PropTypes__default["default"].node }; function HelperText(_ref2) { let { disabled, description, id } = _ref2; const prefix = usePrefix.usePrefix(); const className = cx__default["default"](`${prefix}--form__helper-text`, { [`${prefix}--form__helper-text--disabled`]: disabled }); if (description) { return /*#__PURE__*/React__default["default"].createElement("div", { id: id, className: className }, description); } return null; } HelperText.propTypes = { description: PropTypes__default["default"].node, disabled: PropTypes__default["default"].bool, id: PropTypes__default["default"].string }; /** * Determine if the given value is invalid based on the given max, min and * conditions like `allowEmpty`. If `invalid` is passed through, it will default * to false. * * @param {object} config * @param {boolean} config.allowEmpty * @param {boolean} config.invalid * @param {number} config.value * @param {number} config.max * @param {number} config.min * @returns {boolean} */ function getInputValidity(_ref3) { let { allowEmpty, invalid, value, max, min } = _ref3; if (invalid) { return false; } if (value === '') { return allowEmpty; } if (value > max || value < min) { return false; } return true; } /** * It prevents any wheel event from emitting. * * We want to prevent this input field from changing by the user accidentally * when the user scrolling up or down in a long form. So we prevent the default * behavior of wheel events when it is focused. * * Because React uses passive event handler by default, we can't just call * `preventDefault` in the `onWheel` event handler. So we have to get the input * ref and add our event handler manually. * * @see https://github.com/facebook/react/pull/19654 * @param {WheelEvent} e A wheel event. */ function disableWheel(e) { e.preventDefault(); } /** * Clamp the given value between the upper bound `max` and the lower bound `min` * * 16 digit min/max more precise than Infinity. Somewhere in 9 quadrillion, * there will be integer display issues at runtime. 9quad is a safe cutoff. * @param {number} max * @param {number} min * @param {number} value */ const boundLimit = 9000000000000000; // 16 digit, 9 quadrillion function clamp() { let max = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : boundLimit; let min = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -boundLimit; let value = arguments.length > 2 ? arguments[2] : undefined; return Math.min(max, Math.max(min, value)); } exports.NumberInput = NumberInput; exports.translationIds = translationIds;