vuetify
Version:
Vue Material Component Framework
240 lines (236 loc) • 8.63 kB
JavaScript
/* eslint-disable complexity */
// Utilities
import { escapeForRegex, extractNumber } from "../../util/index.js";
function stripGrouping(text, groupSeparator) {
return text.replaceAll(groupSeparator, '');
}
/** Fallback: fixed 3-digit groups */
function formatWithoutLocale(digits, groupSeparator, grouping) {
if (grouping === 'min2' && digits.length <= 4) {
return digits;
}
const groups = [];
for (let i = digits.length; i > 0; i -= 3) {
groups.unshift(digits.slice(Math.max(0, i - 3), i));
}
return groups.join(groupSeparator);
}
function addGrouping(raw, groupSeparator, decimalSeparator, grouping, locale) {
if (!grouping) return raw;
const decimalIndex = raw.indexOf(decimalSeparator);
const integerPart = decimalIndex >= 0 ? raw.slice(0, decimalIndex) : raw;
const rest = decimalIndex >= 0 ? raw.slice(decimalIndex) : '';
const sign = integerPart.startsWith('-') ? '-' : '';
const digits = sign ? integerPart.slice(1) : integerPart;
if (!digits) return raw;
if (locale) {
const num = Number(digits);
if (!Number.isFinite(num)) return raw;
const grouped = new Intl.NumberFormat(locale, {
useGrouping: grouping,
numberingSystem: 'latn'
}).formatToParts(num).map(p => p.type === 'group' ? groupSeparator : p.value).join('');
return sign + grouped + rest;
}
return sign + formatWithoutLocale(digits, groupSeparator, grouping) + rest;
}
/** Count non-separator characters before displayPosition */
function toLogicalPosition(text, groupSeparator, displayPosition) {
let logical = 0;
for (let i = 0; i < displayPosition && i < text.length; i++) {
if (text[i] !== groupSeparator) logical++;
}
return logical;
}
function toDisplayPosition(text, groupSeparator, logicalPosition) {
let logical = 0;
for (let i = 0; i <= text.length; i++) {
if (logical === logicalPosition) return i;
if (i < text.length && text[i] !== groupSeparator) logical++;
}
return text.length;
}
/**
* Process a beforeinput event for plain (non-grouped) number input.
* Returns { text, cursor } when the input needs correction, or null to let browser handle it.
*/
export function processPlainInput(data, value, selectionStart, selectionEnd, options) {
if (!data) return null;
const {
decimalSeparator,
precision
} = options;
const beforePart = value.slice(0, selectionStart);
let cleanData = extractNumber(data, precision, decimalSeparator);
if (cleanData.startsWith('-') && beforePart.length > 0) cleanData = cleanData.slice(1);
if (cleanData.includes(decimalSeparator) && beforePart.includes(decimalSeparator)) {
cleanData = cleanData.replace(decimalSeparator, '');
}
const candidate = beforePart + cleanData + value.slice(selectionEnd);
const validPattern = new RegExp(`^-?\\d*${escapeForRegex(decimalSeparator)}?\\d*$`);
if (!validPattern.test(candidate)) {
return {
text: extractNumber(candidate, precision, decimalSeparator),
cursor: selectionStart
};
}
if (precision != null && (candidate.split(decimalSeparator)[1]?.length ?? 0) > precision) {
const text = extractNumber(candidate, precision, decimalSeparator);
return {
text,
cursor: text === beforePart + value.slice(selectionEnd) ? selectionStart : selectionStart + cleanData.length
};
}
if (cleanData === data) return null;
return {
text: candidate,
cursor: selectionStart + cleanData.length
};
}
/**
* Process a beforeinput event for grouped number input.
* Returns { text, cursor } to apply, or null for interactions that do not need override
*/
export function processGroupedInput(inputType, data, value, selectionStart, selectionEnd, options) {
const {
groupSeparator,
decimalSeparator,
precision,
grouping,
locale
} = options;
const raw = stripGrouping(value, groupSeparator);
const logicalStart = toLogicalPosition(value, groupSeparator, selectionStart);
const logicalEnd = toLogicalPosition(value, groupSeparator, selectionEnd);
const hasSelection = logicalStart !== logicalEnd;
let newRaw;
let newLogicalCursor;
switch (inputType) {
case 'insertText':
{
if (!data) return null;
newRaw = raw.slice(0, logicalStart) + data + raw.slice(logicalEnd);
newLogicalCursor = logicalStart + data.length;
break;
}
case 'insertFromPaste':
case 'insertFromDrop':
{
if (!data) return null;
const rawBefore = raw.slice(0, logicalStart);
let cleanData = extractNumber(data, precision, decimalSeparator);
if (cleanData.startsWith('-') && rawBefore.length > 0) {
cleanData = cleanData.slice(1);
}
if (cleanData.includes(decimalSeparator) && rawBefore.includes(decimalSeparator)) {
cleanData = cleanData.replace(decimalSeparator, '');
}
newRaw = rawBefore + cleanData + raw.slice(logicalEnd);
newLogicalCursor = logicalStart + cleanData.length;
break;
}
case 'deleteContentBackward':
{
if (hasSelection) {
newRaw = raw.slice(0, logicalStart) + raw.slice(logicalEnd);
newLogicalCursor = logicalStart;
} else if (logicalStart > 0) {
newRaw = raw.slice(0, logicalStart - 1) + raw.slice(logicalStart);
newLogicalCursor = logicalStart - 1;
} else {
// At the start, nothing to delete
newRaw = raw;
newLogicalCursor = 0;
}
break;
}
case 'deleteContentForward':
{
if (hasSelection) {
newRaw = raw.slice(0, logicalStart) + raw.slice(logicalEnd);
newLogicalCursor = logicalStart;
} else if (logicalStart < raw.length) {
newRaw = raw.slice(0, logicalStart) + raw.slice(logicalStart + 1);
newLogicalCursor = logicalStart;
} else {
// At the end, nothing to delete
newRaw = raw;
newLogicalCursor = logicalStart;
}
break;
}
case 'deleteByCut':
{
newRaw = raw.slice(0, logicalStart) + raw.slice(logicalEnd);
newLogicalCursor = logicalStart;
break;
}
case 'deleteWordBackward':
{
if (hasSelection) {
newRaw = raw.slice(0, logicalStart) + raw.slice(logicalEnd);
newLogicalCursor = logicalStart;
} else {
let deleteEnd = logicalStart;
// Skip non-digits first (e.g., decimal separator)
while (deleteEnd > 0 && !/\d/.test(raw[deleteEnd - 1])) deleteEnd--;
// Then skip digits
while (deleteEnd > 0 && /\d/.test(raw[deleteEnd - 1])) deleteEnd--;
newRaw = raw.slice(0, deleteEnd) + raw.slice(logicalStart);
newLogicalCursor = deleteEnd;
}
break;
}
case 'deleteWordForward':
{
if (hasSelection) {
newRaw = raw.slice(0, logicalStart) + raw.slice(logicalEnd);
newLogicalCursor = logicalStart;
} else {
let deleteEnd = logicalStart;
while (deleteEnd < raw.length && !/\d/.test(raw[deleteEnd])) deleteEnd++;
while (deleteEnd < raw.length && /\d/.test(raw[deleteEnd])) deleteEnd++;
newRaw = raw.slice(0, logicalStart) + raw.slice(deleteEnd);
newLogicalCursor = logicalStart;
}
break;
}
case 'deleteSoftLineBackward':
{
newRaw = raw.slice(logicalEnd);
newLogicalCursor = 0;
break;
}
case 'deleteSoftLineForward':
{
newRaw = raw.slice(0, logicalStart);
newLogicalCursor = logicalStart;
break;
}
default:
return null;
}
const validPattern = new RegExp(`^-?\\d*${escapeForRegex(decimalSeparator)}?\\d*$`);
if (!validPattern.test(newRaw)) {
newRaw = extractNumber(newRaw, precision, decimalSeparator);
newLogicalCursor = Math.min(newLogicalCursor, newRaw.length);
}
if (precision != null) {
const parts = newRaw.split(decimalSeparator);
if (parts[1]?.length > precision) {
newRaw = parts[0] + decimalSeparator + parts[1].slice(0, precision);
newLogicalCursor = Math.min(newLogicalCursor, newRaw.length);
}
if (precision === 0 && newRaw.endsWith(decimalSeparator)) {
newRaw = newRaw.slice(0, -1);
newLogicalCursor = Math.min(newLogicalCursor, newRaw.length);
}
}
const formatted = addGrouping(newRaw, groupSeparator, decimalSeparator, grouping, locale);
const cursor = toDisplayPosition(formatted, groupSeparator, newLogicalCursor);
return {
text: formatted,
cursor
};
}
//# sourceMappingURL=typing.js.map