@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
372 lines (371 loc) • 13.8 kB
JavaScript
"use client";
import React, { cloneElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useMaskito } from '@maskito/react';
import { maskitoTransform, maskitoUpdateElement } from '@maskito/core';
import { maskitoCaretGuard, maskitoNumberOptionsGenerator } from '@maskito/kit';
import InputModeNumber from "./text-mask/InputModeNumber.js";
import { jsx as _jsx } from "react/jsx-runtime";
export default function TextMask(props) {
const {
inputElement,
inputRef,
mask: rawMask,
value,
showMask,
optionsEnhancer,
ghostPlaceholder,
stripValue,
allowOverflow,
overwriteMode,
...rest
} = props;
const localRef = useRef(null);
const [inputMode] = useState(() => new InputModeNumber());
useEffect(() => () => inputMode.remove(), [inputMode]);
const maskParams = typeof rawMask === 'object' && rawMask !== null && 'maskParams' in rawMask ? rawMask.maskParams : undefined;
const separatorTokens = useMemo(() => getSeparatorTokens(rawMask), [rawMask]);
const options = 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 = separatorTokens.includes(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 === null || maskParams === void 0 ? void 0 : maskParams.thousandsSeparatorSymbol, maskParams === null || maskParams === void 0 ? void 0 : maskParams.decimalSymbol, maskParams === null || maskParams === void 0 ? void 0 : maskParams.allowDecimal, maskParams === null || maskParams === void 0 ? void 0 : maskParams.decimalLimit]);
const enhancedOptions = useMemo(() => {
return typeof optionsEnhancer === 'function' ? optionsEnhancer(options) : options;
}, [options, optionsEnhancer]);
const maskitoRef = useMaskito({
options: enhancedOptions
});
const setRefs = 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 = 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
} = 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 = maskitoTransform(elementState, enhancedOptions);
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]);
useEffect(() => {
var _el$selectionStart, _el$selectionEnd;
if (!localRef.current || !enhancedOptions) {
return;
}
const el = localRef.current;
const valueToTransform = value;
if (valueToTransform === undefined || valueToTransform === null) {
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 = maskitoTransform({
value: cleanValue,
selection
}, enhancedOptions);
if (el.value !== validated.value) {
maskitoUpdateElement(el, validated);
}
}, [enhancedOptions, value, rawMask]);
const defaultValue = 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
} = maskitoTransform({
value: cleanValue,
selection: [cleanValue.length, cleanValue.length]
}, options);
return formatted;
}, [value, showMask, options, rawMask, ghostPlaceholder]);
return inputElement ? (cloneElement(inputElement, {
...params,
defaultValue,
ref: setRefs
})) : _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 === null || state === void 0 ? void 0 : 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 === null || state === void 0 ? void 0 : 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;
}
return maskExpression.concat(Array.from({
length: overflow
}, () => 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 && suffix.includes(' ')) {
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 - 1 : 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 base = maskitoNumberOptionsGenerator({
min,
max: Number.MAX_SAFE_INTEGER + 1,
thousandSeparator: thousand,
decimalSeparator: decimal,
maximumFractionDigits,
minimumFractionDigits: 0,
prefix,
postfix: postfixToUse,
decimalPseudoSeparators: ['.', ',', '·'],
minusSign: '-'
});
const caretGuard = 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 plugins = [...(base.plugins || []), caretGuard];
return {
...base,
plugins,
postprocessors
};
}
//# sourceMappingURL=TextMask.js.map