UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

374 lines (373 loc) 13.9 kB
"use client"; import _extends from "@babel/runtime-corejs3/helpers/esm/extends"; import React, { useContext, useMemo, useCallback, useEffect, useRef } from 'react'; import { InputMasked, Button } from "../../../../components/index.js"; import { format } from "../../../../components/number-format/NumberUtils.js"; import SharedContext from "../../../../shared/Context.js"; import FieldBlockContext from "../../FieldBlock/FieldBlockContext.js"; import classnames from 'classnames'; import FieldBlock from "../../FieldBlock/index.js"; import { useFieldProps } from "../../hooks/index.js"; import { pickSpacingProps } from "../../../../components/flex/utils.js"; import { clamp } from "../../../../shared/helpers/clamp.js"; import DataContext from "../../DataContext/Context.js"; import * as z from 'zod'; const defaultMinimum = Number.MIN_SAFE_INTEGER; const defaultMaximum = Number.MAX_SAFE_INTEGER; function NumberComponent(props) { var _props$width, _props$innerRef, _dataContext$props2, _sharedContext$transl, _sharedContext$transl2; const dataContext = useContext(DataContext); const fieldBlockContext = useContext(FieldBlockContext); const sharedContext = useContext(SharedContext); const locale = sharedContext === null || sharedContext === void 0 ? void 0 : sharedContext.locale; const validateContinuouslyRef = useRef(props === null || props === void 0 ? void 0 : props.validateContinuously); const { currency, currencyDisplay, percent, mask, step = 1, decimalLimit = 12, allowNegative = true, disallowLeadingZeroes = false, prefix: prefixProp, suffix: suffixProp, showStepControls } = props; const schema = useMemo(() => { var _props$schema; return ((_props$schema = props.schema) !== null && _props$schema !== void 0 ? _props$schema : p => { const formatValidationValue = value => { const formatOptions = { locale }; if (p.currency) { formatOptions.currency = p.currency; } if (p.percent) { formatOptions.percent = true; } if (p.decimalLimit !== undefined) { formatOptions.decimals = p.decimalLimit; } return format(value, formatOptions); }; return z.number().nullish().superRefine((val, ctx) => { if (val === null || val === undefined) { return; } if ((p.minimum === undefined || p.minimum < defaultMinimum) && val < defaultMinimum) { ctx.addIssue({ code: 'too_small', minimum: defaultMinimum, type: 'number', inclusive: true, message: 'NumberField.errorMinimum', messageValues: { minimum: formatValidationValue(defaultMinimum) }, origin: 'number', locale }); } if ((p.maximum === undefined || p.maximum > defaultMaximum) && val > defaultMaximum) { ctx.addIssue({ code: 'too_big', maximum: defaultMaximum, type: 'number', inclusive: true, message: 'NumberField.errorMaximum', messageValues: { maximum: formatValidationValue(defaultMaximum) }, origin: 'number', locale }); } if (p.minimum !== undefined && val < p.minimum) { ctx.addIssue({ code: 'too_small', minimum: p.minimum, type: 'number', inclusive: true, message: 'NumberField.errorMinimum', messageValues: { minimum: formatValidationValue(p.minimum) }, origin: 'number', locale }); } if (p.maximum !== undefined && val > p.maximum) { ctx.addIssue({ code: 'too_big', maximum: p.maximum, type: 'number', inclusive: true, message: 'NumberField.errorMaximum', messageValues: { maximum: formatValidationValue(p.maximum) }, origin: 'number', locale }); } if (p.exclusiveMinimum !== undefined && val <= p.exclusiveMinimum) { ctx.addIssue({ code: 'too_small', minimum: p.exclusiveMinimum, type: 'number', inclusive: false, message: 'NumberField.errorExclusiveMinimum', messageValues: { exclusiveMinimum: formatValidationValue(p.exclusiveMinimum) }, origin: 'number', exclusiveMinimum: p.exclusiveMinimum, locale }); } if (p.exclusiveMaximum !== undefined && val >= p.exclusiveMaximum) { ctx.addIssue({ code: 'too_big', maximum: p.exclusiveMaximum, type: 'number', inclusive: false, message: 'NumberField.errorExclusiveMaximum', messageValues: { exclusiveMaximum: formatValidationValue(p.exclusiveMaximum) }, origin: 'number', exclusiveMaximum: p.exclusiveMaximum, locale }); } if (p.multipleOf !== undefined && val % p.multipleOf !== 0) { ctx.addIssue({ code: 'custom', message: 'NumberField.errorMultipleOf', messageValues: { multipleOf: formatValidationValue(p.multipleOf) }, origin: 'number', multipleOf: p.multipleOf, locale }); } }); } ); }, [props.schema, props.minimum, props.maximum, props.exclusiveMinimum, props.exclusiveMaximum, props.multipleOf, props.currency, props.percent, props.decimalLimit, locale]); const toInput = useCallback(external => { if (external === undefined || external === null) { return null; } if (typeof external !== 'number' || isNaN(external)) { return ''; } return external; }, []); const fromInput = useCallback(({ value, numberValue }) => { if (value === '') { return props.emptyValue; } if (allowNegative && Object.is(numberValue, -0)) { return props.emptyValue; } return numberValue; }, [props.emptyValue, allowNegative]); const ref = useRef(); const preparedProps = { valueType: 'number', validateContinuously: validateContinuouslyRef.current, ...props, schema, toInput, fromInput, width: (_props$width = props.width) !== null && _props$width !== void 0 ? _props$width : fieldBlockContext !== null && fieldBlockContext !== void 0 && fieldBlockContext.composition ? 'stretch' : 'medium', innerRef: (_props$innerRef = props.innerRef) !== null && _props$innerRef !== void 0 ? _props$innerRef : ref }; const { id, name, className, innerRef, inputClassName, autoComplete, placeholder, value, startWith = null, minimum = defaultMinimum, maximum = defaultMaximum, disabled, htmlAttributes, hasError, size, width, align, handleFocus, handleBlur, handleChange, setDisplayValue } = useFieldProps(preparedProps); useEffect(() => { var _innerRef$current; setDisplayValue((_innerRef$current = innerRef.current) === null || _innerRef$current === void 0 ? void 0 : _innerRef$current.value); }, [innerRef, setDisplayValue, value]); const { handleSubmit } = dataContext !== null && dataContext !== void 0 ? dataContext : {}; const onKeyDownHandler = useCallback(e => { var _dataContext$props; const { event } = e; if (dataContext !== null && dataContext !== void 0 && (_dataContext$props = dataContext.props) !== null && _dataContext$props !== void 0 && _dataContext$props.isolate && event.key === 'Enter') { var _event$preventDefault; handleSubmit(); (_event$preventDefault = event.preventDefault) === null || _event$preventDefault === void 0 || _event$preventDefault.call(event); } if (!showStepControls) { return; } let numberValue = null; switch (event.key) { case 'ArrowUp': numberValue = clamp((value !== null && value !== void 0 ? value : startWith) + step, minimum, maximum); break; case 'ArrowDown': numberValue = clamp((value !== null && value !== void 0 ? value : startWith) - step, minimum, maximum); break; } if (numberValue !== null) { event.persist(); event.preventDefault(); handleChange({ numberValue }); } }, [dataContext === null || dataContext === void 0 || (_dataContext$props2 = dataContext.props) === null || _dataContext$props2 === void 0 ? void 0 : _dataContext$props2.isolate, handleChange, handleSubmit, maximum, minimum, showStepControls, startWith, step, value]); const onChangeHandler = useCallback(args => { handleChange(args); if (typeof (args === null || args === void 0 ? void 0 : args.numberValue) === 'number') { if (args.numberValue > defaultMaximum || args.numberValue < defaultMinimum) { handleBlur(); } } }, [handleChange, handleBlur]); const fieldBlockProps = { forId: id, className: classnames("dnb-forms-field-number dnb-input__border--tokens", className), contentClassName: classnames('dnb-forms-field-number__contents', showStepControls && 'dnb-forms-field-number__contents--has-controls', hasError && 'dnb-input__status--error', disabled && 'dnb-input--disabled'), width: (width === 'stretch' || fieldBlockContext !== null && fieldBlockContext !== void 0 && fieldBlockContext.composition) && !showStepControls ? width : undefined, contentWidth: width !== false ? width : undefined, ...pickSpacingProps(props) }; const increaseClickHandler = useCallback(() => { handleChange({ numberValue: clamp((value !== null && value !== void 0 ? value : startWith) + step, minimum, maximum) }); }, [handleChange, maximum, minimum, startWith, step, value]); const increaseProps = showStepControls && { 'aria-hidden': true, className: 'dnb-button--control-after', variant: 'secondary', icon: 'add', size: size || 'small', tabIndex: -1, disabled: disabled || value >= maximum, onClick: increaseClickHandler, title: sharedContext === null || sharedContext === void 0 || (_sharedContext$transl = sharedContext.translation.Slider.addTitle) === null || _sharedContext$transl === void 0 ? void 0 : _sharedContext$transl.replace('%s', String(value + step)) }; const decreaseClickHandler = useCallback(() => { handleChange({ numberValue: clamp((value !== null && value !== void 0 ? value : startWith) - step, minimum, maximum) }); }, [handleChange, maximum, minimum, startWith, step, value]); const decreaseProps = showStepControls && { ...increaseProps, className: 'dnb-button--control-before', icon: 'subtract', size: size || 'small', disabled: disabled || value <= minimum, onClick: decreaseClickHandler, title: sharedContext === null || sharedContext === void 0 || (_sharedContext$transl2 = sharedContext.translation.Slider.subtractTitle) === null || _sharedContext$transl2 === void 0 ? void 0 : _sharedContext$transl2.replace('%s', String(value - step)) }; const prefix = typeof prefixProp === 'function' ? prefixProp(value) : prefixProp; const suffix = typeof suffixProp === 'function' ? suffixProp(value) : suffixProp; const maskProps = useMemo(() => { const mask_options = { prefix, suffix, decimalLimit, allowNegative, disallowLeadingZeroes }; if (currency) { return { as_currency: currency, mask_options, currency_mask: { currencyDisplay, decimalLimit } }; } if (percent) { return { as_percent: percent, mask_options }; } return { mask, as_number: mask ? undefined : true, number_mask: mask ? undefined : mask_options }; }, [currency, currencyDisplay, decimalLimit, mask, percent, prefix, suffix, allowNegative, disallowLeadingZeroes]); const ariaParams = showStepControls && { role: 'spinbutton', 'aria-valuemin': String(minimum), 'aria-valuemax': String(maximum), 'aria-valuenow': String(value), 'aria-valuetext': String(value) }; const inputProps = { id, name, inner_ref: innerRef, autoComplete, className: classnames(`dnb-forms-field-number__input dnb-input--${size}`, inputClassName), step: showStepControls ? step : undefined, placeholder, value, align: showStepControls ? 'center' : align, onKeyDown: onKeyDownHandler, onPaste: handleBlur, onFocus: handleFocus, onBlur: handleBlur, onChange: onChangeHandler, disabled, status: hasError ? 'error' : undefined, stretch: Boolean(width), ...maskProps, ...htmlAttributes, ...ariaParams }; if (showStepControls) { return React.createElement(FieldBlock, _extends({}, fieldBlockProps, { asFieldset: false }), React.createElement("span", { className: "dnb-input__border dnb-input__border--root" }, React.createElement(Button, decreaseProps), React.createElement(InputMasked, inputProps), React.createElement(Button, increaseProps))); } return React.createElement(FieldBlock, _extends({}, fieldBlockProps, { asFieldset: false }), React.createElement(InputMasked, inputProps)); } NumberComponent._supportsSpacingProps = true; export default NumberComponent; //# sourceMappingURL=Number.js.map