188 lines
7.6 KiB
JavaScript
188 lines
7.6 KiB
JavaScript
|
import dateFnsFormat from 'date-fns/format';
|
||
|
import dateFnsParse from 'date-fns/parse';
|
||
|
import dateFnsIsValid from 'date-fns/isValid';
|
||
|
import { extractDateParts, parseDate as _parseDate } from '@vaadin/date-picker/src/vaadin-date-picker-helper.js';
|
||
|
|
||
|
(function () {
|
||
|
const tryCatchWrapper = function (callback) {
|
||
|
return window.Vaadin.Flow.tryCatchWrapper(callback, 'Vaadin Date Picker');
|
||
|
};
|
||
|
|
||
|
window.Vaadin.Flow.datepickerConnector = {
|
||
|
initLazy: (datepicker) =>
|
||
|
tryCatchWrapper(function (datepicker) {
|
||
|
// Check whether the connector was already initialized for the datepicker
|
||
|
if (datepicker.$connector) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
datepicker.$connector = {};
|
||
|
|
||
|
const createLocaleBasedDateFormat = function (locale) {
|
||
|
try {
|
||
|
// Check whether the locale is supported or not
|
||
|
new Date().toLocaleDateString(locale);
|
||
|
} catch (e) {
|
||
|
console.warn('The locale is not supported, using default format setting (ISO 8601).');
|
||
|
return 'yyyy-MM-dd';
|
||
|
}
|
||
|
|
||
|
// format test date and convert to date-fns pattern
|
||
|
const testDate = new Date(Date.UTC(1234, 4, 6));
|
||
|
let pattern = testDate.toLocaleDateString(locale, { timeZone: 'UTC' });
|
||
|
pattern = pattern
|
||
|
// escape date-fns pattern letters by enclosing them in single quotes
|
||
|
.replace(/([a-zA-Z]+)/g, "'$1'")
|
||
|
// insert date placeholder
|
||
|
.replace('06', 'dd')
|
||
|
.replace('6', 'd')
|
||
|
// insert month placeholder
|
||
|
.replace('05', 'MM')
|
||
|
.replace('5', 'M')
|
||
|
// insert year placeholder
|
||
|
.replace('1234', 'yyyy');
|
||
|
const isValidPattern = pattern.includes('d') && pattern.includes('M') && pattern.includes('y');
|
||
|
if (!isValidPattern) {
|
||
|
console.warn('The locale is not supported, using default format setting (ISO 8601).');
|
||
|
return 'yyyy-MM-dd';
|
||
|
}
|
||
|
|
||
|
return pattern;
|
||
|
};
|
||
|
|
||
|
const createFormatterAndParser = tryCatchWrapper(function (formats) {
|
||
|
if (!formats || formats.length === 0) {
|
||
|
throw new Error('Array of custom date formats is null or empty');
|
||
|
}
|
||
|
|
||
|
function getShortYearFormat(format) {
|
||
|
if (format.includes('yyyy') && !format.includes('yyyyy')) {
|
||
|
return format.replace('yyyy', 'yy');
|
||
|
}
|
||
|
if (format.includes('YYYY') && !format.includes('YYYYY')) {
|
||
|
return format.replace('YYYY', 'YY');
|
||
|
}
|
||
|
return undefined;
|
||
|
}
|
||
|
|
||
|
function isFormatWithYear(format) {
|
||
|
return format.includes('y') || format.includes('Y');
|
||
|
}
|
||
|
|
||
|
function isShortYearFormat(format) {
|
||
|
// Format is long if it includes a four-digit year.
|
||
|
return !format.includes('yyyy') && !format.includes('YYYY');
|
||
|
}
|
||
|
|
||
|
function getExtendedFormats(formats) {
|
||
|
return formats.reduce((acc, format) => {
|
||
|
// We first try to match the date with the shorter version,
|
||
|
// as short years are supported with the long date format.
|
||
|
if (isFormatWithYear(format) && !isShortYearFormat(format)) {
|
||
|
acc.push(getShortYearFormat(format));
|
||
|
}
|
||
|
acc.push(format);
|
||
|
return acc;
|
||
|
}, []);
|
||
|
}
|
||
|
|
||
|
function correctFullYear(date) {
|
||
|
// The last parsed date check handles the case where a four-digit year is parsed, then formatted
|
||
|
// as a two-digit year, and then parsed again. In this case we want to keep the century of the
|
||
|
// originally parsed year, instead of using the century of the reference date.
|
||
|
|
||
|
// Do not apply any correction if the previous parse attempt was failed.
|
||
|
if (datepicker.$connector._lastParseStatus === 'error') {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Update century if the last parsed date is the same except the century.
|
||
|
if (datepicker.$connector._lastParseStatus === 'successful') {
|
||
|
if (
|
||
|
datepicker.$connector._lastParsedDate.day === date.getDate() &&
|
||
|
datepicker.$connector._lastParsedDate.month === date.getMonth() &&
|
||
|
datepicker.$connector._lastParsedDate.year % 100 === date.getFullYear() % 100
|
||
|
) {
|
||
|
date.setFullYear(datepicker.$connector._lastParsedDate.year);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Update century if this is the first parse after overlay open.
|
||
|
const currentValue = _parseDate(datepicker.value);
|
||
|
if (
|
||
|
dateFnsIsValid(currentValue) &&
|
||
|
currentValue.getDate() === date.getDate() &&
|
||
|
currentValue.getMonth() === date.getMonth() &&
|
||
|
currentValue.getFullYear() % 100 === date.getFullYear() % 100
|
||
|
) {
|
||
|
date.setFullYear(currentValue.getFullYear());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function formatDate(dateParts) {
|
||
|
const format = formats[0];
|
||
|
const date = _parseDate(`${dateParts.year}-${dateParts.month + 1}-${dateParts.day}`);
|
||
|
|
||
|
return dateFnsFormat(date, format);
|
||
|
}
|
||
|
|
||
|
function doParseDate(dateString, format, referenceDate) {
|
||
|
// When format does not contain a year, then current year should be used.
|
||
|
const refDate = isFormatWithYear(format) ? referenceDate : new Date();
|
||
|
const date = dateFnsParse(dateString, format, refDate);
|
||
|
if (dateFnsIsValid(date)) {
|
||
|
if (isFormatWithYear(format) && isShortYearFormat(format)) {
|
||
|
correctFullYear(date);
|
||
|
}
|
||
|
return {
|
||
|
day: date.getDate(),
|
||
|
month: date.getMonth(),
|
||
|
year: date.getFullYear()
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function parseDate(dateString) {
|
||
|
const referenceDate = _getReferenceDate();
|
||
|
for (let format of getExtendedFormats(formats)) {
|
||
|
const parsedDate = doParseDate(dateString, format, referenceDate);
|
||
|
if (parsedDate) {
|
||
|
datepicker.$connector._lastParseStatus = 'successful';
|
||
|
datepicker.$connector._lastParsedDate = parsedDate;
|
||
|
return parsedDate;
|
||
|
}
|
||
|
}
|
||
|
datepicker.$connector._lastParseStatus = 'error';
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
formatDate: formatDate,
|
||
|
parseDate: parseDate
|
||
|
};
|
||
|
});
|
||
|
|
||
|
function _getReferenceDate() {
|
||
|
const { referenceDate } = datepicker.i18n;
|
||
|
return referenceDate ? new Date(referenceDate.year, referenceDate.month, referenceDate.day) : new Date();
|
||
|
}
|
||
|
|
||
|
datepicker.$connector.updateI18n = tryCatchWrapper(function (locale, i18n) {
|
||
|
// Either use custom formats specified in I18N, or create format from locale
|
||
|
const hasCustomFormats = i18n && i18n.dateFormats && i18n.dateFormats.length > 0;
|
||
|
if (i18n && i18n.referenceDate) {
|
||
|
i18n.referenceDate = extractDateParts(new Date(i18n.referenceDate));
|
||
|
}
|
||
|
const usedFormats = hasCustomFormats ? i18n.dateFormats : [createLocaleBasedDateFormat(locale)];
|
||
|
const formatterAndParser = createFormatterAndParser(usedFormats);
|
||
|
|
||
|
// Merge current web component I18N settings with new I18N settings and the formatting and parsing functions
|
||
|
datepicker.i18n = Object.assign({}, datepicker.i18n, i18n, formatterAndParser);
|
||
|
});
|
||
|
|
||
|
datepicker.addEventListener('opened-changed', () => (datepicker.$connector._lastParseStatus = undefined));
|
||
|
})(datepicker)
|
||
|
};
|
||
|
})();
|