UNPKG

@base-ui-components/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

214 lines (201 loc) 8.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UNICODE_PLUS_SIGNS = exports.UNICODE_MINUS_SIGNS = exports.SPACE_SEPARATOR_RE = exports.PLUS_SIGNS_WITH_ASCII = exports.PERSIAN_RE = exports.PERSIAN_NUMERALS = exports.PERSIAN_DETECT_RE = exports.PERMILLE_RE = exports.PERMILLE = exports.PERCENT_RE = exports.PERCENTAGES = exports.MINUS_SIGNS_WITH_ASCII = exports.HAN_RE = exports.HAN_NUMERAL_TO_DIGIT = exports.HAN_NUMERALS = exports.HAN_DETECT_RE = exports.FULLWIDTH_RE = exports.FULLWIDTH_NUMERALS = exports.FULLWIDTH_GROUP = exports.FULLWIDTH_DETECT_RE = exports.FULLWIDTH_DECIMAL = exports.BASE_NON_NUMERIC_SYMBOLS = exports.ARABIC_RE = exports.ARABIC_NUMERALS = exports.ARABIC_DETECT_RE = exports.ANY_PLUS_RE = exports.ANY_PLUS_DETECT_RE = exports.ANY_MINUS_RE = exports.ANY_MINUS_DETECT_RE = void 0; exports.getNumberLocaleDetails = getNumberLocaleDetails; exports.parseNumber = parseNumber; var _formatNumber = require("../../utils/formatNumber"); const HAN_NUMERALS = exports.HAN_NUMERALS = ['零', '〇', '一', '二', '三', '四', '五', '六', '七', '八', '九']; // Map Han numeral characters to ASCII digits. Includes both forms of zero. const HAN_NUMERAL_TO_DIGIT = exports.HAN_NUMERAL_TO_DIGIT = { 零: '0', 〇: '0', 一: '1', 二: '2', 三: '3', 四: '4', 五: '5', 六: '6', 七: '7', 八: '8', 九: '9' }; const ARABIC_NUMERALS = exports.ARABIC_NUMERALS = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']; const PERSIAN_NUMERALS = exports.PERSIAN_NUMERALS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; const FULLWIDTH_NUMERALS = exports.FULLWIDTH_NUMERALS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; const PERCENTAGES = exports.PERCENTAGES = ['%', '٪', '%', '﹪']; const PERMILLE = exports.PERMILLE = ['‰', '؉']; const UNICODE_MINUS_SIGNS = exports.UNICODE_MINUS_SIGNS = ['−', '-', '‒', '–', '—', '﹣']; const UNICODE_PLUS_SIGNS = exports.UNICODE_PLUS_SIGNS = ['+', '﹢']; // Fullwidth punctuation common in CJK inputs const FULLWIDTH_DECIMAL = exports.FULLWIDTH_DECIMAL = '.'; // U+FF0E const FULLWIDTH_GROUP = exports.FULLWIDTH_GROUP = ','; // U+FF0C const ARABIC_RE = exports.ARABIC_RE = new RegExp(`[${ARABIC_NUMERALS.join('')}]`, 'g'); const PERSIAN_RE = exports.PERSIAN_RE = new RegExp(`[${PERSIAN_NUMERALS.join('')}]`, 'g'); const FULLWIDTH_RE = exports.FULLWIDTH_RE = new RegExp(`[${FULLWIDTH_NUMERALS.join('')}]`, 'g'); const HAN_RE = exports.HAN_RE = new RegExp(`[${HAN_NUMERALS.join('')}]`, 'g'); const PERCENT_RE = exports.PERCENT_RE = new RegExp(`[${PERCENTAGES.join('')}]`); const PERMILLE_RE = exports.PERMILLE_RE = new RegExp(`[${PERMILLE.join('')}]`); // Detection regexes (non-global to avoid lastIndex side effects) const ARABIC_DETECT_RE = exports.ARABIC_DETECT_RE = /[٠١٢٣٤٥٦٧٨٩]/; const PERSIAN_DETECT_RE = exports.PERSIAN_DETECT_RE = /[۰۱۲۳۴۵۶۷۸۹]/; const HAN_DETECT_RE = exports.HAN_DETECT_RE = /[零〇一二三四五六七八九]/; const FULLWIDTH_DETECT_RE = exports.FULLWIDTH_DETECT_RE = new RegExp(`[${FULLWIDTH_NUMERALS.join('')}]`); const BASE_NON_NUMERIC_SYMBOLS = exports.BASE_NON_NUMERIC_SYMBOLS = ['.', ',', FULLWIDTH_DECIMAL, FULLWIDTH_GROUP, '٫', '٬']; const SPACE_SEPARATOR_RE = exports.SPACE_SEPARATOR_RE = /\p{Zs}/u; const PLUS_SIGNS_WITH_ASCII = exports.PLUS_SIGNS_WITH_ASCII = ['+', ...UNICODE_PLUS_SIGNS]; const MINUS_SIGNS_WITH_ASCII = exports.MINUS_SIGNS_WITH_ASCII = ['-', ...UNICODE_MINUS_SIGNS]; const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const escapeClassChar = s => s.replace(/[-\\\]^]/g, m => `\\${m}`); // escape for use inside [...] const charClassFrom = chars => `[${chars.map(escapeClassChar).join('')}]`; const ANY_MINUS_CLASS = charClassFrom(['-'].concat(UNICODE_MINUS_SIGNS)); const ANY_PLUS_CLASS = charClassFrom(['+'].concat(UNICODE_PLUS_SIGNS)); const ANY_MINUS_RE = exports.ANY_MINUS_RE = new RegExp(ANY_MINUS_CLASS, 'gu'); const ANY_PLUS_RE = exports.ANY_PLUS_RE = new RegExp(ANY_PLUS_CLASS, 'gu'); const ANY_MINUS_DETECT_RE = exports.ANY_MINUS_DETECT_RE = new RegExp(ANY_MINUS_CLASS); const ANY_PLUS_DETECT_RE = exports.ANY_PLUS_DETECT_RE = new RegExp(ANY_PLUS_CLASS); function getNumberLocaleDetails(locale, options) { const parts = (0, _formatNumber.getFormatter)(locale, options).formatToParts(11111.1); const result = {}; parts.forEach(part => { result[part.type] = part.value; }); // The formatting options may result in not returning a decimal. (0, _formatNumber.getFormatter)(locale).formatToParts(0.1).forEach(part => { if (part.type === 'decimal') { result[part.type] = part.value; } }); return result; } function parseNumber(formattedNumber, locale, options) { if (formattedNumber == null) { return null; } // Normalize control characters and whitespace; remove bidi/format controls let input = String(formattedNumber).replace(/\p{Cf}/gu, '').trim(); // Normalize unicode minus/plus to ASCII, handle leading/trailing signs input = input.replace(ANY_MINUS_RE, '-').replace(ANY_PLUS_RE, '+'); let isNegative = false; // Trailing sign, e.g. "1234-" / "1234+" const trailing = input.match(/([+-])\s*$/); if (trailing) { if (trailing[1] === '-') { isNegative = true; } input = input.replace(/([+-])\s*$/, ''); } // Leading sign const leading = input.match(/^\s*([+-])/); if (leading) { if (leading[1] === '-') { isNegative = true; } input = input.replace(/^\s*[+-]/, ''); } // Heuristic locale detection let computedLocale = locale; if (computedLocale === undefined) { if (ARABIC_DETECT_RE.test(input) || PERSIAN_DETECT_RE.test(input)) { computedLocale = 'ar'; } else if (HAN_DETECT_RE.test(input)) { computedLocale = 'zh'; } } const { group, decimal, currency } = getNumberLocaleDetails(computedLocale, options); // Build robust unit regex from all unit parts (such as "km/h") const unitParts = (0, _formatNumber.getFormatter)(computedLocale, options).formatToParts(1).filter(p => p.type === 'unit').map(p => escapeRegExp(p.value)); const unitRegex = unitParts.length ? new RegExp(unitParts.join('|'), 'g') : null; let groupRegex = null; if (group) { // Check if the group separator is a space-like character. // If so, we'll replace all such characters with an empty string. groupRegex = /\p{Zs}/u.test(group) ? /\p{Zs}/gu : new RegExp(escapeRegExp(group), 'g'); } const replacements = [{ regex: group ? groupRegex : null, replacement: '' }, { regex: decimal ? new RegExp(escapeRegExp(decimal), 'g') : null, replacement: '.' }, // Fullwidth punctuation { regex: /./g, replacement: '.' }, // FULLWIDTH_DECIMAL { regex: /,/g, replacement: '' }, // FULLWIDTH_GROUP // Arabic punctuation { regex: /٫/g, replacement: '.' }, // ARABIC DECIMAL SEPARATOR (U+066B) { regex: /٬/g, replacement: '' }, // ARABIC THOUSANDS SEPARATOR (U+066C) // Currency & unit labels { regex: currency ? new RegExp(escapeRegExp(currency), 'g') : null, replacement: '' }, { regex: unitRegex, replacement: '' }, // Numeral systems to ASCII digits { regex: ARABIC_RE, replacement: ch => String(ARABIC_NUMERALS.indexOf(ch)) }, { regex: PERSIAN_RE, replacement: ch => String(PERSIAN_NUMERALS.indexOf(ch)) }, { regex: FULLWIDTH_RE, replacement: ch => String(FULLWIDTH_NUMERALS.indexOf(ch)) }, { regex: HAN_RE, replacement: ch => HAN_NUMERAL_TO_DIGIT[ch] }]; let unformatted = replacements.reduce((acc, { regex, replacement }) => { return regex ? acc.replace(regex, replacement) : acc; }, input); // Mixed-locale safety: keep only the last '.' as decimal const lastDot = unformatted.lastIndexOf('.'); if (lastDot !== -1) { unformatted = `${unformatted.slice(0, lastDot).replace(/\./g, '')}.${unformatted.slice(lastDot + 1).replace(/\./g, '')}`; } // Guard against Infinity inputs (ASCII and symbol) if (/^[-+]?Infinity$/i.test(input) || /[∞]/.test(input)) { return null; } const parseTarget = (isNegative ? '-' : '') + unformatted; let num = parseFloat(parseTarget); const style = options?.style; const isUnitPercent = style === 'unit' && options?.unit === 'percent'; const hasPercentSymbol = PERCENT_RE.test(formattedNumber) || style === 'percent'; const hasPermilleSymbol = PERMILLE_RE.test(formattedNumber); if (hasPermilleSymbol) { num /= 1000; } else if (!isUnitPercent && hasPercentSymbol) { num /= 100; } if (Number.isNaN(num)) { return null; } return num; }