комит

This commit is contained in:
dipatrik10
2026-07-27 00:33:16 +03:00
parent 92ff87a917
commit 7e29c53ce9
21 changed files with 890 additions and 91 deletions

View File

@@ -0,0 +1,180 @@
const DISPLAY_DATE_PATTERN = /^(\d{2})\.(\d{2})\.(\d{4})$/;
const ISO_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
const boundDateInputs = new WeakSet();
export function formatDateInputValue(value) {
const text = String(value || '');
const isoMatch = text.match(ISO_DATE_PATTERN);
if (isoMatch) {
return `${isoMatch[3]}.${isoMatch[2]}.${isoMatch[1]}`;
}
const digits = text.replace(/\D/g, '').slice(0, 8);
if (digits.length <= 2) return digits;
if (digits.length <= 4) return `${digits.slice(0, 2)}.${digits.slice(2)}`;
return `${digits.slice(0, 2)}.${digits.slice(2, 4)}.${digits.slice(4)}`;
}
export function dateInputValueToIso(value) {
const text = String(value || '').trim();
if (!text) return '';
const match = text.match(DISPLAY_DATE_PATTERN);
if (!match) {
throw new TypeError('Введите дату полностью в формате ДД.ММ.ГГГГ (год — 4 цифры)');
}
const [, dayText, monthText, yearText] = match;
const day = Number(dayText);
const month = Number(monthText);
const year = Number(yearText);
if (year < 1 || month < 1 || month > 12 || day < 1 || day > daysInMonth(month, year)) {
throw new TypeError('Введите существующую дату в формате ДД.ММ.ГГГГ');
}
return `${yearText}-${monthText}-${dayText}`;
}
export function isoDateToInputValue(value) {
const text = String(value || '').trim();
if (!text) return '';
const match = text.match(ISO_DATE_PATTERN);
if (!match) {
throw new TypeError('Ожидалась дата в формате ГГГГ-ММ-ДД');
}
const [, year, month, day] = match;
dateInputValueToIso(`${day}.${month}.${year}`);
return `${day}.${month}.${year}`;
}
export function bindDateInputMask(input) {
if (!input || boundDateInputs.has(input)) return;
boundDateInputs.add(input);
input.addEventListener('input', () => {
const rawValue = input.value;
const caret = input.selectionStart ?? rawValue.length;
const digitsBeforeCaret = rawValue.slice(0, caret).replace(/\D/g, '').length;
const caretWasAtEnd = caret === rawValue.length;
const formattedValue = formatDateInputValue(rawValue);
input.value = formattedValue;
clearDateInputError(input);
const nextCaret = caretPositionAfterDigits(formattedValue, digitsBeforeCaret, caretWasAtEnd);
input.setSelectionRange?.(nextCaret, nextCaret);
});
input.addEventListener('keydown', event => {
if (event.key === 'Backspace'
&& input.selectionStart === input.selectionEnd
&& input.selectionStart > 0
&& input.value[input.selectionStart - 1] === '.') {
event.preventDefault();
const nextCaret = input.selectionStart - 1;
input.setSelectionRange(nextCaret, nextCaret);
}
if (event.key === 'Delete'
&& input.selectionStart === input.selectionEnd
&& input.value[input.selectionStart] === '.') {
event.preventDefault();
const nextCaret = input.selectionStart + 1;
input.setSelectionRange(nextCaret, nextCaret);
}
});
input.addEventListener('blur', () => {
if (!input.value) {
clearDateInputError(input);
return;
}
try {
const isoValue = dateInputValueToIso(input.value);
input.value = isoDateToInputValue(isoValue);
clearDateInputError(input);
} catch (error) {
setDateInputError(input, error.message);
}
});
input.form?.addEventListener('reset', () => clearDateInputError(input));
}
export function readDateInputIso(input, label = 'Дата') {
try {
const value = dateInputValueToIso(input?.value);
clearDateInputError(input);
return value;
} catch (error) {
const message = `${label}: ${lowercaseFirst(error.message)}`;
setDateInputError(input, message);
input?.focus();
throw new Error(message);
}
}
export function setDateInputIso(input, value) {
if (!input) return;
input.value = isoDateToInputValue(value);
clearDateInputError(input);
}
function daysInMonth(month, year) {
const monthLengths = [
31,
isLeapYear(year) ? 29 : 28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
];
return monthLengths[month - 1] || 0;
}
function isLeapYear(year) {
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
}
function caretPositionAfterDigits(value, digitCount, preferEnd) {
if (digitCount <= 0) return 0;
let seenDigits = 0;
for (let index = 0; index < value.length; index += 1) {
if (!/\d/.test(value[index])) continue;
seenDigits += 1;
if (seenDigits === digitCount) {
let position = index + 1;
if (preferEnd) {
while (value[position] === '.') position += 1;
}
return position;
}
}
return value.length;
}
function setDateInputError(input, message) {
if (!input) return;
input.setCustomValidity?.(message);
input.setAttribute?.('aria-invalid', 'true');
}
function clearDateInputError(input) {
if (!input) return;
input.setCustomValidity?.('');
input.removeAttribute?.('aria-invalid');
}
function lowercaseFirst(value) {
const text = String(value || '');
return text ? text[0].toLowerCase() + text.slice(1) : text;
}