Files
magistr/frontend/admin/js/date-input.js
dipatrik10 7e29c53ce9 комит
2026-07-27 00:33:16 +03:00

181 lines
5.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}