UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

514 lines (513 loc) 17.7 kB
"use strict"; "use client"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = TextMask; var _includes = _interopRequireDefault(require("core-js-pure/stable/instance/includes.js")); var _react = require("react"); var _react2 = require("@maskito/react"); var _core = require("@maskito/core"); var _kit = require("@maskito/kit"); var _InputModeNumber = _interopRequireDefault(require("./text-mask/InputModeNumber.js")); var _jsxRuntime = require("react/jsx-runtime"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function TextMask(props) { const { inputElement, inputRef, mask: rawMask, value, showMask, optionsEnhancer, ghostPlaceholder, stripValue, allowOverflow, overwriteMode, ...rest } = props; const localRef = (0, _react.useRef)(null); const [inputMode] = (0, _react.useState)(() => new _InputModeNumber.default()); (0, _react.useEffect)(() => () => inputMode.remove(), [inputMode]); const maskParams = typeof rawMask === 'object' && rawMask !== null && 'maskParams' in rawMask ? rawMask.maskParams : undefined; const separatorTokens = (0, _react.useMemo)(() => getSeparatorTokens(rawMask), [rawMask]); const options = (0, _react.useMemo)(() => { if (typeof rawMask === 'object' && rawMask !== null && 'instanceOf' in rawMask && 'maskParams' in rawMask && rawMask.instanceOf === 'createNumberMask' && rawMask.maskParams) { const mp = rawMask.maskParams; return createMaskitoNumberOptions(mp); } const mask = normalizeMask(rawMask); if (!mask) { return null; } const overflowAwareMask = allowOverflow ? withOverflowSupport(mask) : mask; const preprocessors = Array.isArray(mask) ? [({ elementState, data }, actionType) => { if (actionType === 'deleteBackward') { const value = elementState.value; const [selectionStart, selectionEnd] = elementState.selection; const isSeparatorBeforeCaret = (0, _includes.default)(separatorTokens).call(separatorTokens, value[selectionStart - 1]); if (isSeparatorBeforeCaret) { const newCaretPosition = selectionStart - 1; const valueBeforeCaret = value.slice(0, selectionStart); const valueAfterCaret = value.slice(selectionEnd); return { elementState: { value: valueAfterCaret ? `${valueBeforeCaret}${valueAfterCaret}` : valueBeforeCaret.slice(0, -1), selection: [newCaretPosition, newCaretPosition] }, data }; } return { elementState, data }; } return { elementState, data }; }] : []; return { mask: overflowAwareMask, ...(preprocessors.length > 0 ? { preprocessors } : {}), ...(overwriteMode != null ? { overwriteMode } : {}) }; }, [rawMask, allowOverflow, overwriteMode, maskParams?.thousandsSeparatorSymbol, maskParams?.decimalSymbol, maskParams?.allowDecimal, maskParams?.decimalLimit]); const enhancedOptions = (0, _react.useMemo)(() => { return typeof optionsEnhancer === 'function' ? optionsEnhancer(options) : options; }, [options, optionsEnhancer]); const maskitoRef = (0, _react2.useMaskito)({ options: enhancedOptions }); const setRefs = (0, _react.useCallback)(el => { maskitoRef(el); if (inputRef) { if (typeof inputRef === 'object' && 'current' in inputRef) { inputRef.current = el; } else if (typeof inputRef === 'function') { inputRef(el); } } localRef.current = el; if (el) { inputMode.setElement(el); } }, [inputRef, maskitoRef, inputMode]); const params = (0, _react.useMemo)(() => { const baseProps = { ...rest }; if (enhancedOptions && typeof baseProps.value === 'string' && baseProps.value != null) { const raw = String(baseProps.value); const sel = [raw.length, raw.length]; const { value: formatted } = (0, _core.maskitoTransform)({ value: raw, selection: sel }, enhancedOptions); baseProps.value = formatted; } baseProps.onChange = e => { if (localRef.current && enhancedOptions) { var _element$selectionSta, _element$selectionEnd; const element = localRef.current; const selection = [(_element$selectionSta = element.selectionStart) !== null && _element$selectionSta !== void 0 ? _element$selectionSta : 0, (_element$selectionEnd = element.selectionEnd) !== null && _element$selectionEnd !== void 0 ? _element$selectionEnd : 0]; const elementState = { value: e.target.value, selection }; const validated = (0, _core.maskitoTransform)(elementState, enhancedOptions); (0, _core.maskitoUpdateElement)(element, validated); } if (typeof rest.onChange === 'function') { if (typeof stripValue === 'function') { const v = stripValue(e.target.value); rest.onChange({ target: { value: v } }); } else { rest.onChange(e); } } }; return baseProps; }, [rest, enhancedOptions, stripValue]); const prevValueRef = (0, _react.useRef)(undefined); (0, _react.useEffect)(() => { var _el$selectionStart, _el$selectionEnd; if (!localRef.current || !enhancedOptions) { return; } const el = localRef.current; const valueToTransform = value; if (valueToTransform === undefined || valueToTransform === null) { if (prevValueRef.current != null && el.value !== '') { (0, _core.maskitoUpdateElement)(el, { value: '', selection: [0, 0] }); } prevValueRef.current = valueToTransform; return; } const cleanValue = cleanNumericValue(String(valueToTransform), rawMask); const selection = [(_el$selectionStart = el.selectionStart) !== null && _el$selectionStart !== void 0 ? _el$selectionStart : cleanValue.length, (_el$selectionEnd = el.selectionEnd) !== null && _el$selectionEnd !== void 0 ? _el$selectionEnd : cleanValue.length]; const validated = (0, _core.maskitoTransform)({ value: cleanValue, selection }, enhancedOptions); if (el.value !== validated.value) { (0, _core.maskitoUpdateElement)(el, validated); } prevValueRef.current = valueToTransform; }, [enhancedOptions, value, rawMask]); const defaultValue = (0, _react.useMemo)(() => { const raw = typeof value === 'string' ? value : undefined; if (!raw && showMask) { if (ghostPlaceholder) { return ghostPlaceholder; } if (typeof rawMask === 'object' && rawMask !== null && 'maskParams' in rawMask) { var _mp$prefix, _mp$suffix; const mp = rawMask.maskParams; const prefix = (_mp$prefix = mp.prefix) !== null && _mp$prefix !== void 0 ? _mp$prefix : ''; const suffix = (_mp$suffix = mp.suffix) !== null && _mp$suffix !== void 0 ? _mp$suffix : ''; if (prefix || suffix) { return prefix + suffix; } } return undefined; } if (!raw) { return undefined; } if (!options) { return raw; } const hasFormatting = Array.isArray(options.postprocessors) && options.postprocessors.length > 0; if (!hasFormatting) { return raw; } const cleanValue = cleanNumericValue(raw, rawMask); const { value: formatted } = (0, _core.maskitoTransform)({ value: cleanValue, selection: [cleanValue.length, cleanValue.length] }, options); return formatted; }, [value, showMask, options, rawMask, ghostPlaceholder]); return inputElement ? ((0, _react.cloneElement)(inputElement, { ...params, defaultValue, ref: setRefs })) : (0, _jsxRuntime.jsx)("input", { ref: setRefs, defaultValue: defaultValue, ...params }); } function withOverflowSupport(mask) { if (Array.isArray(mask)) { const baseMask = [...mask]; return state => { var _state$value; return appendOverflowTokens(baseMask, (_state$value = state?.value) !== null && _state$value !== void 0 ? _state$value : ''); }; } if (typeof mask === 'function') { return state => { var _state$value2; const resolved = mask(state); if (!Array.isArray(resolved)) { return resolved; } return appendOverflowTokens(resolved, (_state$value2 = state?.value) !== null && _state$value2 !== void 0 ? _state$value2 : ''); }; } return mask; } const ALL_CHARACTERS = /[\s\S]/; function appendOverflowTokens(maskExpression, currentValue) { if (!Array.isArray(maskExpression)) { return maskExpression; } const userSlots = countUserSlots(maskExpression); const userInputLength = countUserInputLength(currentValue, maskExpression); const overflow = Math.max(0, userInputLength - userSlots); if (overflow <= 0) { return maskExpression; } const lastUserSlot = getLastUserSlot(maskExpression); return maskExpression.concat(Array.from({ length: overflow }, () => lastUserSlot)); } function getLastUserSlot(maskExpression) { if (!Array.isArray(maskExpression)) { return ALL_CHARACTERS; } for (let i = maskExpression.length - 1; i >= 0; i--) { if (maskExpression[i] instanceof RegExp) { return maskExpression[i]; } } return ALL_CHARACTERS; } function countUserSlots(maskExpression) { if (!Array.isArray(maskExpression)) { return 0; } return maskExpression.reduce((count, token) => { return count + (typeof token === 'string' ? 0 : 1); }, 0); } function getSeparatorTokens(maskExpression) { if (!Array.isArray(maskExpression)) { return []; } return maskExpression.filter(token => typeof token === 'string' && token.length > 0); } function countUserInputLength(value, maskExpression) { if (typeof value !== 'string') { return value == null ? 0 : String(value).length; } if (!Array.isArray(maskExpression) || !maskExpression.length) { return value.length; } let strippedValue = value; for (const token of maskExpression) { if (typeof token === 'string' && token.length > 0) { const escaped = escapeRegExp(token); strippedValue = strippedValue.replace(new RegExp(escaped, 'g'), ''); } } return strippedValue.length; } function escapeRegExp(input) { return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function normalizeMask(maskProp) { if (maskProp && typeof maskProp === 'object' && 'mask' in maskProp && !Array.isArray(maskProp)) { return normalizeMask(maskProp.mask); } if (maskProp === false) { return /^.*$/; } if (Array.isArray(maskProp)) { return maskProp.filter(t => t !== '[]'); } if (maskProp instanceof RegExp) { return maskProp; } return /^.*$/; } function stripAffixes(value, prefix, suffix) { let result = value; if (prefix && result.startsWith(prefix)) { result = result.slice(prefix.length); } if (suffix && result.endsWith(suffix)) { result = result.slice(0, -suffix.length); } else if (suffix && (0, _includes.default)(suffix).call(suffix, ' ')) { const suffixParts = suffix.split(' '); const lastPart = suffixParts[suffixParts.length - 1]; if (lastPart && result.endsWith(lastPart)) { result = result.slice(0, -lastPart.length).trim(); } } return result; } function cleanNumericValue(value, rawMask) { if (typeof rawMask === 'object' && rawMask !== null && 'maskParams' in rawMask) { var _mp$prefix2, _mp$suffix2; const mp = rawMask.maskParams; return stripAffixes(value, (_mp$prefix2 = mp.prefix) !== null && _mp$prefix2 !== void 0 ? _mp$prefix2 : '', (_mp$suffix2 = mp.suffix) !== null && _mp$suffix2 !== void 0 ? _mp$suffix2 : ''); } return value; } function createMaskitoNumberOptions(mp) { var _mp$decimalSymbol, _mp$thousandsSeparato, _mp$prefix3, _mp$suffix3, _mp$decimalLimit; const decimal = (_mp$decimalSymbol = mp.decimalSymbol) !== null && _mp$decimalSymbol !== void 0 ? _mp$decimalSymbol : ','; const thousand = (_mp$thousandsSeparato = mp.thousandsSeparatorSymbol) !== null && _mp$thousandsSeparato !== void 0 ? _mp$thousandsSeparato : ' '; const prefix = (_mp$prefix3 = mp.prefix) !== null && _mp$prefix3 !== void 0 ? _mp$prefix3 : ''; const suffix = (_mp$suffix3 = mp.suffix) !== null && _mp$suffix3 !== void 0 ? _mp$suffix3 : ''; const allowNegative = mp.allowNegative !== false; const min = allowNegative ? Number.MIN_SAFE_INTEGER : 0; const defaultDecimalLimit = mp.allowDecimal === true ? 2 : 0; const maximumFractionDigits = Math.max(0, Number((_mp$decimalLimit = mp.decimalLimit) !== null && _mp$decimalLimit !== void 0 ? _mp$decimalLimit : defaultDecimalLimit)); const suffixStartsWithComma = suffix && suffix.startsWith(','); const postfixToUse = suffixStartsWithComma ? suffix.slice(1) : suffix; const decimalPseudoSeparators = ['·']; const base = (0, _kit.maskitoNumberOptionsGenerator)({ min, max: Number.MAX_SAFE_INTEGER, thousandSeparator: thousand, decimalSeparator: decimal, maximumFractionDigits, minimumFractionDigits: 0, prefix, postfix: postfixToUse, decimalPseudoSeparators, minusSign: '-' }); const caretGuard = (0, _kit.maskitoCaretGuard)(value => { const left = prefix ? prefix.length : 0; const right = postfixToUse ? postfixToUse.length : 0; const max = Math.max(left, value.length - right); return [left, max]; }); const clearEmptyPostprocessor = elementState => { const value = elementState.value; const withoutAffixes = stripAffixes(value, prefix, postfixToUse).trim(); if (withoutAffixes === '' || withoutAffixes === decimal) { return { ...elementState, value: '', selection: [0, 0] }; } return elementState; }; const postprocessors = suffixStartsWithComma ? [...(base.postprocessors || []), elementState => { const prefixLen = prefix ? prefix.length : 0; const postfixLen = postfixToUse ? postfixToUse.length : 0; const valueWithoutAffixes = elementState.value.slice(prefixLen, elementState.value.length - postfixLen); const newValue = prefix + valueWithoutAffixes + ',' + postfixToUse; return { ...elementState, value: newValue }; }, clearEmptyPostprocessor] : [...(base.postprocessors || []), clearEmptyPostprocessor]; const stripFormatting = new RegExp(`[^\\d\\-.${decimal === '.' ? '' : '\\' + decimal}]`, 'g'); const rejectBeyondSafeInteger = ({ elementState, data }, actionType) => { if (actionType !== 'insert' || !data) { return { elementState, data }; } const { value, selection } = elementState; let raw = (value.slice(0, selection[0]) + data + value.slice(selection[1])).replace(stripFormatting, ''); if (decimal !== '.') { raw = raw.replace(decimal, '.'); } const num = Number(raw); if (!isNaN(num) && (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER)) { mp.onRejectSafeInteger?.(num); return { elementState, data: '' }; } return { elementState, data }; }; const disambiguateMixed = value => { const hasDot = (0, _includes.default)(value).call(value, '.'); const hasComma = (0, _includes.default)(value).call(value, ','); if (!hasDot || !hasComma) { return null; } const lastDot = value.lastIndexOf('.'); const lastComma = value.lastIndexOf(','); if (lastDot > lastComma) { return value.replace(/,/g, '').replace('.', decimal); } else { return value.replace(/\./g, '').replace(',', decimal); } }; const disambiguateDot = value => { if (/(?:^|[^\d])0\./.test(value)) { return value.replace(/\./g, decimal); } return value.replace(/\.(\d{3})(?=\D|$)/g, '$1').replace(/\./g, decimal); }; const disambiguateComma = value => { const commaGroupCount = (value.match(/,\d{3}(?=\D|$)/g) || []).length; if (commaGroupCount >= 2) { return value.replace(/,(\d{3})(?=\D|$)/g, '$1'); } return value; }; const disambiguateSeparatorsPreprocessor = ({ elementState, data }, actionType) => { var _context; if (decimal === '.' || thousand === '.') { return { elementState, data }; } if (actionType === 'insert' && data) { const mixed = disambiguateMixed(data); if (mixed !== null) { return { elementState, data: mixed }; } let d = disambiguateDot(data); if (decimal === ',' && thousand !== ',') { d = disambiguateComma(d); } return { elementState, data: d }; } if (actionType === 'validation' && (0, _includes.default)(_context = elementState.value).call(_context, '.')) { const mixed = disambiguateMixed(elementState.value); if (mixed !== null) { return { elementState: { ...elementState, value: mixed }, data }; } return { elementState: { ...elementState, value: disambiguateDot(elementState.value) }, data }; } return { elementState, data }; }; const preprocessors = [disambiguateSeparatorsPreprocessor, ...(base.preprocessors || []), rejectBeyondSafeInteger]; const plugins = [...(base.plugins || []), caretGuard]; return { ...base, plugins, preprocessors, postprocessors }; } //# sourceMappingURL=TextMask.js.map