95d9d2f9a8
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2483 lines
76 KiB
JavaScript
2483 lines
76 KiB
JavaScript
(self["webpackChunkapp"] = self["webpackChunkapp"] || []).push([[7950],{
|
|
|
|
/***/ 52037:
|
|
/*!************************************************************!*\
|
|
!*** ./node_modules/@ionic/core/dist/esm/data-caf38df0.js ***!
|
|
\************************************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "A": () => (/* binding */ getDayColumnData),
|
|
/* harmony export */ "B": () => (/* binding */ getYearColumnData),
|
|
/* harmony export */ "C": () => (/* binding */ isMonthFirstLocale),
|
|
/* harmony export */ "D": () => (/* binding */ getTimeColumnsData),
|
|
/* harmony export */ "E": () => (/* binding */ isLocaleDayPeriodRTL),
|
|
/* harmony export */ "F": () => (/* binding */ getDaysOfWeek),
|
|
/* harmony export */ "G": () => (/* binding */ getMonthAndYear),
|
|
/* harmony export */ "H": () => (/* binding */ getDaysOfMonth),
|
|
/* harmony export */ "I": () => (/* binding */ generateMonths),
|
|
/* harmony export */ "J": () => (/* binding */ is24Hour),
|
|
/* harmony export */ "K": () => (/* binding */ getLocalizedTime),
|
|
/* harmony export */ "L": () => (/* binding */ getMonthAndDay),
|
|
/* harmony export */ "M": () => (/* binding */ formatValue),
|
|
/* harmony export */ "N": () => (/* binding */ getNextYear),
|
|
/* harmony export */ "O": () => (/* binding */ getPreviousYear),
|
|
/* harmony export */ "P": () => (/* binding */ clampDate),
|
|
/* harmony export */ "Q": () => (/* binding */ parseAmPm),
|
|
/* harmony export */ "R": () => (/* binding */ calculateHourFromAMPM),
|
|
/* harmony export */ "S": () => (/* binding */ getLocalizedDateTime),
|
|
/* harmony export */ "T": () => (/* binding */ getMonthDayAndYear),
|
|
/* harmony export */ "a": () => (/* binding */ getDay),
|
|
/* harmony export */ "b": () => (/* binding */ isAfter),
|
|
/* harmony export */ "c": () => (/* binding */ isSameDay),
|
|
/* harmony export */ "d": () => (/* binding */ getPreviousMonth),
|
|
/* harmony export */ "e": () => (/* binding */ getNextMonth),
|
|
/* harmony export */ "f": () => (/* binding */ getPartsFromCalendarDay),
|
|
/* harmony export */ "g": () => (/* binding */ generateDayAriaLabel),
|
|
/* harmony export */ "h": () => (/* binding */ getEndOfWeek),
|
|
/* harmony export */ "i": () => (/* binding */ isBefore),
|
|
/* harmony export */ "j": () => (/* binding */ getStartOfWeek),
|
|
/* harmony export */ "k": () => (/* binding */ getPreviousDay),
|
|
/* harmony export */ "l": () => (/* binding */ getNextDay),
|
|
/* harmony export */ "m": () => (/* binding */ getPreviousWeek),
|
|
/* harmony export */ "n": () => (/* binding */ getNextWeek),
|
|
/* harmony export */ "o": () => (/* binding */ parseMaxParts),
|
|
/* harmony export */ "p": () => (/* binding */ parseMinParts),
|
|
/* harmony export */ "q": () => (/* binding */ parseDate),
|
|
/* harmony export */ "r": () => (/* binding */ convertToArrayOfNumbers),
|
|
/* harmony export */ "s": () => (/* binding */ convertDataToISO),
|
|
/* harmony export */ "t": () => (/* binding */ getToday),
|
|
/* harmony export */ "u": () => (/* binding */ getClosestValidDate),
|
|
/* harmony export */ "v": () => (/* binding */ validateParts),
|
|
/* harmony export */ "w": () => (/* binding */ warnIfValueOutOfBounds),
|
|
/* harmony export */ "x": () => (/* binding */ getNumDaysInMonth),
|
|
/* harmony export */ "y": () => (/* binding */ getCombinedDateColumnData),
|
|
/* harmony export */ "z": () => (/* binding */ getMonthColumnData)
|
|
/* harmony export */ });
|
|
/* harmony import */ var _index_c4b11676_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index-c4b11676.js */ 99273);
|
|
/*!
|
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
*/
|
|
|
|
/**
|
|
* Returns true if the selected day is equal to the reference day
|
|
*/
|
|
|
|
const isSameDay = (baseParts, compareParts) => {
|
|
return baseParts.month === compareParts.month && baseParts.day === compareParts.day && baseParts.year === compareParts.year;
|
|
};
|
|
/**
|
|
* Returns true is the selected day is before the reference day.
|
|
*/
|
|
|
|
|
|
const isBefore = (baseParts, compareParts) => {
|
|
return !!(baseParts.year < compareParts.year || baseParts.year === compareParts.year && baseParts.month < compareParts.month || baseParts.year === compareParts.year && baseParts.month === compareParts.month && baseParts.day !== null && baseParts.day < compareParts.day);
|
|
};
|
|
/**
|
|
* Returns true is the selected day is after the reference day.
|
|
*/
|
|
|
|
|
|
const isAfter = (baseParts, compareParts) => {
|
|
return !!(baseParts.year > compareParts.year || baseParts.year === compareParts.year && baseParts.month > compareParts.month || baseParts.year === compareParts.year && baseParts.month === compareParts.month && baseParts.day !== null && baseParts.day > compareParts.day);
|
|
};
|
|
|
|
const warnIfValueOutOfBounds = (value, min, max) => {
|
|
const valueArray = Array.isArray(value) ? value : [value];
|
|
|
|
for (const val of valueArray) {
|
|
if (min !== undefined && isBefore(val, min) || max !== undefined && isAfter(val, max)) {
|
|
(0,_index_c4b11676_js__WEBPACK_IMPORTED_MODULE_0__.p)('The value provided to ion-datetime is out of bounds.\n\n' + `Min: ${JSON.stringify(min)}\n` + `Max: ${JSON.stringify(max)}\n` + `Value: ${JSON.stringify(value)}`);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Determines if given year is a
|
|
* leap year. Returns `true` if year
|
|
* is a leap year. Returns `false`
|
|
* otherwise.
|
|
*/
|
|
|
|
|
|
const isLeapYear = year => {
|
|
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
};
|
|
|
|
const is24Hour = (locale, hourCycle) => {
|
|
/**
|
|
* If developer has explicitly enabled h23 time
|
|
* then return early and do not look at the system default.
|
|
*/
|
|
if (hourCycle !== undefined) {
|
|
return hourCycle === 'h23';
|
|
}
|
|
/**
|
|
* If hourCycle was not specified, check the locale
|
|
* that is set on the user's device. We first check the
|
|
* Intl.DateTimeFormat hourCycle option as developers can encode this
|
|
* option into the locale string. Example: `en-US-u-hc-h23`
|
|
*/
|
|
|
|
|
|
const formatted = new Intl.DateTimeFormat(locale, {
|
|
hour: 'numeric'
|
|
});
|
|
const options = formatted.resolvedOptions();
|
|
|
|
if (options.hourCycle !== undefined) {
|
|
return options.hourCycle === 'h23';
|
|
}
|
|
/**
|
|
* If hourCycle is not specified (either through lack
|
|
* of browser support or locale information) then fall
|
|
* back to this slower hourCycle check.
|
|
*/
|
|
|
|
|
|
const date = new Date('5/18/2021 00:00');
|
|
const parts = formatted.formatToParts(date);
|
|
const hour = parts.find(p => p.type === 'hour');
|
|
|
|
if (!hour) {
|
|
throw new Error('Hour value not found from DateTimeFormat');
|
|
}
|
|
|
|
return hour.value === '00';
|
|
};
|
|
/**
|
|
* Given a date object, returns the number
|
|
* of days in that month.
|
|
* Month value begin at 1, not 0.
|
|
* i.e. January = month 1.
|
|
*/
|
|
|
|
|
|
const getNumDaysInMonth = (month, year) => {
|
|
return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : month === 2 ? isLeapYear(year) ? 29 : 28 : 31;
|
|
};
|
|
/**
|
|
* Certain locales display month then year while
|
|
* others display year then month.
|
|
* We can use Intl.DateTimeFormat to determine
|
|
* the ordering for each locale.
|
|
* The formatOptions param can be used to customize
|
|
* which pieces of a date to compare against the month
|
|
* with. For example, some locales render dd/mm/yyyy
|
|
* while others render mm/dd/yyyy. This function can be
|
|
* used for variations of the same "month first" check.
|
|
*/
|
|
|
|
|
|
const isMonthFirstLocale = (locale, formatOptions = {
|
|
month: 'numeric',
|
|
year: 'numeric'
|
|
}) => {
|
|
/**
|
|
* By setting month and year we guarantee that only
|
|
* month, year, and literal (slashes '/', for example)
|
|
* values are included in the formatToParts results.
|
|
*
|
|
* The ordering of the parts will be determined by
|
|
* the locale. So if the month is the first value,
|
|
* then we know month should be shown first. If the
|
|
* year is the first value, then we know year should be shown first.
|
|
*
|
|
* This ordering can be controlled by customizing the locale property.
|
|
*/
|
|
const parts = new Intl.DateTimeFormat(locale, formatOptions).formatToParts(new Date());
|
|
return parts[0].type === 'month';
|
|
};
|
|
/**
|
|
* Determines if the given locale formats the day period (am/pm) to the
|
|
* left or right of the hour.
|
|
* @param locale The locale to check.
|
|
* @returns `true` if the locale formats the day period to the left of the hour.
|
|
*/
|
|
|
|
|
|
const isLocaleDayPeriodRTL = locale => {
|
|
const parts = new Intl.DateTimeFormat(locale, {
|
|
hour: 'numeric'
|
|
}).formatToParts(new Date());
|
|
return parts[0].type === 'dayPeriod';
|
|
};
|
|
|
|
const ISO_8601_REGEXP = // eslint-disable-next-line no-useless-escape
|
|
/^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/; // eslint-disable-next-line no-useless-escape
|
|
|
|
const TIME_REGEXP = /^((\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
|
|
/**
|
|
* Use to convert a string of comma separated numbers or
|
|
* an array of numbers, and clean up any user input
|
|
*/
|
|
|
|
const convertToArrayOfNumbers = input => {
|
|
if (input === undefined) {
|
|
return;
|
|
}
|
|
|
|
let processedInput = input;
|
|
|
|
if (typeof input === 'string') {
|
|
// convert the string to an array of strings
|
|
// auto remove any whitespace and [] characters
|
|
processedInput = input.replace(/\[|\]|\s/g, '').split(',');
|
|
}
|
|
|
|
let values;
|
|
|
|
if (Array.isArray(processedInput)) {
|
|
// ensure each value is an actual number in the returned array
|
|
values = processedInput.map(num => parseInt(num, 10)).filter(isFinite);
|
|
} else {
|
|
values = [processedInput];
|
|
}
|
|
|
|
return values;
|
|
};
|
|
/**
|
|
* Extracts date information
|
|
* from a .calendar-day element
|
|
* into DatetimeParts.
|
|
*/
|
|
|
|
|
|
const getPartsFromCalendarDay = el => {
|
|
return {
|
|
month: parseInt(el.getAttribute('data-month'), 10),
|
|
day: parseInt(el.getAttribute('data-day'), 10),
|
|
year: parseInt(el.getAttribute('data-year'), 10),
|
|
dayOfWeek: parseInt(el.getAttribute('data-day-of-week'), 10)
|
|
};
|
|
};
|
|
|
|
function parseDate(val) {
|
|
if (Array.isArray(val)) {
|
|
return val.map(valStr => parseDate(valStr));
|
|
} // manually parse IS0 cuz Date.parse cannot be trusted
|
|
// ISO 8601 format: 1994-12-15T13:47:20Z
|
|
|
|
|
|
let parse = null;
|
|
|
|
if (val != null && val !== '') {
|
|
// try parsing for just time first, HH:MM
|
|
parse = TIME_REGEXP.exec(val);
|
|
|
|
if (parse) {
|
|
// adjust the array so it fits nicely with the datetime parse
|
|
parse.unshift(undefined, undefined);
|
|
parse[2] = parse[3] = undefined;
|
|
} else {
|
|
// try parsing for full ISO datetime
|
|
parse = ISO_8601_REGEXP.exec(val);
|
|
}
|
|
}
|
|
|
|
if (parse === null) {
|
|
// wasn't able to parse the ISO datetime
|
|
return undefined;
|
|
} // ensure all the parse values exist with at least 0
|
|
|
|
|
|
for (let i = 1; i < 8; i++) {
|
|
parse[i] = parse[i] !== undefined ? parseInt(parse[i], 10) : undefined;
|
|
}
|
|
|
|
let tzOffset = 0;
|
|
|
|
if (parse[9] && parse[10]) {
|
|
// hours
|
|
tzOffset = parseInt(parse[10], 10) * 60;
|
|
|
|
if (parse[11]) {
|
|
// minutes
|
|
tzOffset += parseInt(parse[11], 10);
|
|
}
|
|
|
|
if (parse[9] === '-') {
|
|
// + or -
|
|
tzOffset *= -1;
|
|
}
|
|
} // can also get second and millisecond from parse[6] and parse[7] if needed
|
|
|
|
|
|
return {
|
|
year: parse[1],
|
|
month: parse[2],
|
|
day: parse[3],
|
|
hour: parse[4],
|
|
minute: parse[5],
|
|
tzOffset,
|
|
ampm: parse[4] < 12 ? 'am' : 'pm'
|
|
};
|
|
}
|
|
|
|
const clampDate = (dateParts, minParts, maxParts) => {
|
|
if (minParts && isBefore(dateParts, minParts)) {
|
|
return minParts;
|
|
} else if (maxParts && isAfter(dateParts, maxParts)) {
|
|
return maxParts;
|
|
}
|
|
|
|
return dateParts;
|
|
};
|
|
/**
|
|
* Parses an hour and returns if the value is in the morning (am) or afternoon (pm).
|
|
* @param hour The hour to format, should be 0-23
|
|
* @returns `pm` if the hour is greater than or equal to 12, `am` if less than 12.
|
|
*/
|
|
|
|
|
|
const parseAmPm = hour => {
|
|
return hour >= 12 ? 'pm' : 'am';
|
|
};
|
|
/**
|
|
* Takes a max date string and creates a DatetimeParts
|
|
* object, filling in any missing information.
|
|
* For example, max="2012" would fill in the missing
|
|
* month, day, hour, and minute information.
|
|
*/
|
|
|
|
|
|
const parseMaxParts = (max, todayParts) => {
|
|
const {
|
|
month,
|
|
day,
|
|
year,
|
|
hour,
|
|
minute
|
|
} = parseDate(max);
|
|
/**
|
|
* When passing in `max` or `min`, developers
|
|
* can pass in any ISO-8601 string. This means
|
|
* that not all of the date/time fields are defined.
|
|
* For example, passing max="2012" is valid even though
|
|
* there is no month, day, hour, or minute data.
|
|
* However, all of this data is required when clamping the date
|
|
* so that the correct initial value can be selected. As a result,
|
|
* we need to fill in any omitted data with the min or max values.
|
|
*/
|
|
|
|
const yearValue = year !== null && year !== void 0 ? year : todayParts.year;
|
|
const monthValue = month !== null && month !== void 0 ? month : 12;
|
|
return {
|
|
month: monthValue,
|
|
day: day !== null && day !== void 0 ? day : getNumDaysInMonth(monthValue, yearValue),
|
|
|
|
/**
|
|
* Passing in "HH:mm" is a valid ISO-8601
|
|
* string, so we just default to the current year
|
|
* in this case.
|
|
*/
|
|
year: yearValue,
|
|
hour: hour !== null && hour !== void 0 ? hour : 23,
|
|
minute: minute !== null && minute !== void 0 ? minute : 59
|
|
};
|
|
};
|
|
/**
|
|
* Takes a min date string and creates a DatetimeParts
|
|
* object, filling in any missing information.
|
|
* For example, min="2012" would fill in the missing
|
|
* month, day, hour, and minute information.
|
|
*/
|
|
|
|
|
|
const parseMinParts = (min, todayParts) => {
|
|
const {
|
|
month,
|
|
day,
|
|
year,
|
|
hour,
|
|
minute
|
|
} = parseDate(min);
|
|
/**
|
|
* When passing in `max` or `min`, developers
|
|
* can pass in any ISO-8601 string. This means
|
|
* that not all of the date/time fields are defined.
|
|
* For example, passing max="2012" is valid even though
|
|
* there is no month, day, hour, or minute data.
|
|
* However, all of this data is required when clamping the date
|
|
* so that the correct initial value can be selected. As a result,
|
|
* we need to fill in any omitted data with the min or max values.
|
|
*/
|
|
|
|
return {
|
|
month: month !== null && month !== void 0 ? month : 1,
|
|
day: day !== null && day !== void 0 ? day : 1,
|
|
|
|
/**
|
|
* Passing in "HH:mm" is a valid ISO-8601
|
|
* string, so we just default to the current year
|
|
* in this case.
|
|
*/
|
|
year: year !== null && year !== void 0 ? year : todayParts.year,
|
|
hour: hour !== null && hour !== void 0 ? hour : 0,
|
|
minute: minute !== null && minute !== void 0 ? minute : 0
|
|
};
|
|
};
|
|
|
|
const twoDigit = val => {
|
|
return ('0' + (val !== undefined ? Math.abs(val) : '0')).slice(-2);
|
|
};
|
|
|
|
const fourDigit = val => {
|
|
return ('000' + (val !== undefined ? Math.abs(val) : '0')).slice(-4);
|
|
};
|
|
|
|
function convertDataToISO(data) {
|
|
if (Array.isArray(data)) {
|
|
return data.map(parts => convertDataToISO(parts));
|
|
} // https://www.w3.org/TR/NOTE-datetime
|
|
|
|
|
|
let rtn = '';
|
|
|
|
if (data.year !== undefined) {
|
|
// YYYY
|
|
rtn = fourDigit(data.year);
|
|
|
|
if (data.month !== undefined) {
|
|
// YYYY-MM
|
|
rtn += '-' + twoDigit(data.month);
|
|
|
|
if (data.day !== undefined) {
|
|
// YYYY-MM-DD
|
|
rtn += '-' + twoDigit(data.day);
|
|
|
|
if (data.hour !== undefined) {
|
|
// YYYY-MM-DDTHH:mm:SS
|
|
rtn += `T${twoDigit(data.hour)}:${twoDigit(data.minute)}:00`;
|
|
|
|
if (data.tzOffset === undefined) {
|
|
// YYYY-MM-DDTHH:mm:SSZ
|
|
rtn += 'Z';
|
|
} else {
|
|
// YYYY-MM-DDTHH:mm:SS+/-HH:mm
|
|
rtn += (data.tzOffset > 0 ? '+' : '-') + twoDigit(Math.floor(Math.abs(data.tzOffset / 60))) + ':' + twoDigit(data.tzOffset % 60);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (data.hour !== undefined) {
|
|
// HH:mm
|
|
rtn = twoDigit(data.hour) + ':' + twoDigit(data.minute);
|
|
}
|
|
|
|
return rtn;
|
|
}
|
|
/**
|
|
* Converts an 12 hour value to 24 hours.
|
|
*/
|
|
|
|
|
|
const convert12HourTo24Hour = (hour, ampm) => {
|
|
if (ampm === undefined) {
|
|
return hour;
|
|
}
|
|
/**
|
|
* If AM and 12am
|
|
* then return 00:00.
|
|
* Otherwise just return
|
|
* the hour since it is
|
|
* already in 24 hour format.
|
|
*/
|
|
|
|
|
|
if (ampm === 'am') {
|
|
if (hour === 12) {
|
|
return 0;
|
|
}
|
|
|
|
return hour;
|
|
}
|
|
/**
|
|
* If PM and 12pm
|
|
* just return 12:00
|
|
* since it is already
|
|
* in 24 hour format.
|
|
* Otherwise add 12 hours
|
|
* to the time.
|
|
*/
|
|
|
|
|
|
if (hour === 12) {
|
|
return 12;
|
|
}
|
|
|
|
return hour + 12;
|
|
};
|
|
|
|
const getStartOfWeek = refParts => {
|
|
const {
|
|
dayOfWeek
|
|
} = refParts;
|
|
|
|
if (dayOfWeek === null || dayOfWeek === undefined) {
|
|
throw new Error('No day of week provided');
|
|
}
|
|
|
|
return subtractDays(refParts, dayOfWeek);
|
|
};
|
|
|
|
const getEndOfWeek = refParts => {
|
|
const {
|
|
dayOfWeek
|
|
} = refParts;
|
|
|
|
if (dayOfWeek === null || dayOfWeek === undefined) {
|
|
throw new Error('No day of week provided');
|
|
}
|
|
|
|
return addDays(refParts, 6 - dayOfWeek);
|
|
};
|
|
|
|
const getNextDay = refParts => {
|
|
return addDays(refParts, 1);
|
|
};
|
|
|
|
const getPreviousDay = refParts => {
|
|
return subtractDays(refParts, 1);
|
|
};
|
|
|
|
const getPreviousWeek = refParts => {
|
|
return subtractDays(refParts, 7);
|
|
};
|
|
|
|
const getNextWeek = refParts => {
|
|
return addDays(refParts, 7);
|
|
};
|
|
/**
|
|
* Given datetime parts, subtract
|
|
* numDays from the date.
|
|
* Returns a new DatetimeParts object
|
|
* Currently can only go backward at most 1 month.
|
|
*/
|
|
|
|
|
|
const subtractDays = (refParts, numDays) => {
|
|
const {
|
|
month,
|
|
day,
|
|
year
|
|
} = refParts;
|
|
|
|
if (day === null) {
|
|
throw new Error('No day provided');
|
|
}
|
|
|
|
const workingParts = {
|
|
month,
|
|
day,
|
|
year
|
|
};
|
|
workingParts.day = day - numDays;
|
|
/**
|
|
* If wrapping to previous month
|
|
* update days and decrement month
|
|
*/
|
|
|
|
if (workingParts.day < 1) {
|
|
workingParts.month -= 1;
|
|
}
|
|
/**
|
|
* If moving to previous year, reset
|
|
* month to December and decrement year
|
|
*/
|
|
|
|
|
|
if (workingParts.month < 1) {
|
|
workingParts.month = 12;
|
|
workingParts.year -= 1;
|
|
}
|
|
/**
|
|
* Determine how many days are in the current
|
|
* month
|
|
*/
|
|
|
|
|
|
if (workingParts.day < 1) {
|
|
const daysInMonth = getNumDaysInMonth(workingParts.month, workingParts.year);
|
|
/**
|
|
* Take num days in month and add the
|
|
* number of underflow days. This number will
|
|
* be negative.
|
|
* Example: 1 week before Jan 2, 2021 is
|
|
* December 26, 2021 so:
|
|
* 2 - 7 = -5
|
|
* 31 + (-5) = 26
|
|
*/
|
|
|
|
workingParts.day = daysInMonth + workingParts.day;
|
|
}
|
|
|
|
return workingParts;
|
|
};
|
|
/**
|
|
* Given datetime parts, add
|
|
* numDays to the date.
|
|
* Returns a new DatetimeParts object
|
|
* Currently can only go forward at most 1 month.
|
|
*/
|
|
|
|
|
|
const addDays = (refParts, numDays) => {
|
|
const {
|
|
month,
|
|
day,
|
|
year
|
|
} = refParts;
|
|
|
|
if (day === null) {
|
|
throw new Error('No day provided');
|
|
}
|
|
|
|
const workingParts = {
|
|
month,
|
|
day,
|
|
year
|
|
};
|
|
const daysInMonth = getNumDaysInMonth(month, year);
|
|
workingParts.day = day + numDays;
|
|
/**
|
|
* If wrapping to next month
|
|
* update days and increment month
|
|
*/
|
|
|
|
if (workingParts.day > daysInMonth) {
|
|
workingParts.day -= daysInMonth;
|
|
workingParts.month += 1;
|
|
}
|
|
/**
|
|
* If moving to next year, reset
|
|
* month to January and increment year
|
|
*/
|
|
|
|
|
|
if (workingParts.month > 12) {
|
|
workingParts.month = 1;
|
|
workingParts.year += 1;
|
|
}
|
|
|
|
return workingParts;
|
|
};
|
|
/**
|
|
* Given DatetimeParts, generate the previous month.
|
|
*/
|
|
|
|
|
|
const getPreviousMonth = refParts => {
|
|
/**
|
|
* If current month is January, wrap backwards
|
|
* to December of the previous year.
|
|
*/
|
|
const month = refParts.month === 1 ? 12 : refParts.month - 1;
|
|
const year = refParts.month === 1 ? refParts.year - 1 : refParts.year;
|
|
const numDaysInMonth = getNumDaysInMonth(month, year);
|
|
const day = numDaysInMonth < refParts.day ? numDaysInMonth : refParts.day;
|
|
return {
|
|
month,
|
|
year,
|
|
day
|
|
};
|
|
};
|
|
/**
|
|
* Given DatetimeParts, generate the next month.
|
|
*/
|
|
|
|
|
|
const getNextMonth = refParts => {
|
|
/**
|
|
* If current month is December, wrap forwards
|
|
* to January of the next year.
|
|
*/
|
|
const month = refParts.month === 12 ? 1 : refParts.month + 1;
|
|
const year = refParts.month === 12 ? refParts.year + 1 : refParts.year;
|
|
const numDaysInMonth = getNumDaysInMonth(month, year);
|
|
const day = numDaysInMonth < refParts.day ? numDaysInMonth : refParts.day;
|
|
return {
|
|
month,
|
|
year,
|
|
day
|
|
};
|
|
};
|
|
|
|
const changeYear = (refParts, yearDelta) => {
|
|
const month = refParts.month;
|
|
const year = refParts.year + yearDelta;
|
|
const numDaysInMonth = getNumDaysInMonth(month, year);
|
|
const day = numDaysInMonth < refParts.day ? numDaysInMonth : refParts.day;
|
|
return {
|
|
month,
|
|
year,
|
|
day
|
|
};
|
|
};
|
|
/**
|
|
* Given DatetimeParts, generate the previous year.
|
|
*/
|
|
|
|
|
|
const getPreviousYear = refParts => {
|
|
return changeYear(refParts, -1);
|
|
};
|
|
/**
|
|
* Given DatetimeParts, generate the next year.
|
|
*/
|
|
|
|
|
|
const getNextYear = refParts => {
|
|
return changeYear(refParts, 1);
|
|
};
|
|
/**
|
|
* If PM, then internal value should
|
|
* be converted to 24-hr time.
|
|
* Does not apply when public
|
|
* values are already 24-hr time.
|
|
*/
|
|
|
|
|
|
const getInternalHourValue = (hour, use24Hour, ampm) => {
|
|
if (use24Hour) {
|
|
return hour;
|
|
}
|
|
|
|
return convert12HourTo24Hour(hour, ampm);
|
|
};
|
|
/**
|
|
* Unless otherwise stated, all month values are
|
|
* 1 indexed instead of the typical 0 index in JS Date.
|
|
* Example:
|
|
* January = Month 0 when using JS Date
|
|
* January = Month 1 when using this datetime util
|
|
*/
|
|
|
|
/**
|
|
* Given the current datetime parts and a new AM/PM value
|
|
* calculate what the hour should be in 24-hour time format.
|
|
* Used when toggling the AM/PM segment since we store our hours
|
|
* in 24-hour time format internally.
|
|
*/
|
|
|
|
|
|
const calculateHourFromAMPM = (currentParts, newAMPM) => {
|
|
const {
|
|
ampm: currentAMPM,
|
|
hour
|
|
} = currentParts;
|
|
let newHour = hour;
|
|
/**
|
|
* If going from AM --> PM, need to update the
|
|
*
|
|
*/
|
|
|
|
if (currentAMPM === 'am' && newAMPM === 'pm') {
|
|
newHour = convert12HourTo24Hour(newHour, 'pm');
|
|
/**
|
|
* If going from PM --> AM
|
|
*/
|
|
} else if (currentAMPM === 'pm' && newAMPM === 'am') {
|
|
newHour = Math.abs(newHour - 12);
|
|
}
|
|
|
|
return newHour;
|
|
};
|
|
/**
|
|
* Updates parts to ensure that month and day
|
|
* values are valid. For days that do not exist,
|
|
* the closest valid day is used.
|
|
*/
|
|
|
|
|
|
const validateParts = (parts, minParts, maxParts) => {
|
|
const {
|
|
month,
|
|
day,
|
|
year
|
|
} = parts;
|
|
const partsCopy = Object.assign({}, parts);
|
|
const numDays = getNumDaysInMonth(month, year);
|
|
/**
|
|
* If the max number of days
|
|
* is greater than the day we want
|
|
* to set, update the DatetimeParts
|
|
* day field to be the max days.
|
|
*/
|
|
|
|
if (day !== null && numDays < day) {
|
|
partsCopy.day = numDays;
|
|
}
|
|
/**
|
|
* If value is same day as min day,
|
|
* make sure the time value is in bounds.
|
|
*/
|
|
|
|
|
|
if (minParts !== undefined && isSameDay(partsCopy, minParts)) {
|
|
/**
|
|
* If the hour is out of bounds,
|
|
* update both the hour and minute.
|
|
* This is done so that the new time
|
|
* is closest to what the user selected.
|
|
*/
|
|
if (partsCopy.hour !== undefined && minParts.hour !== undefined) {
|
|
if (partsCopy.hour < minParts.hour) {
|
|
partsCopy.hour = minParts.hour;
|
|
partsCopy.minute = minParts.minute;
|
|
/**
|
|
* If only the minute is out of bounds,
|
|
* set it to the min minute.
|
|
*/
|
|
} else if (partsCopy.hour === minParts.hour && partsCopy.minute !== undefined && minParts.minute !== undefined && partsCopy.minute < minParts.minute) {
|
|
partsCopy.minute = minParts.minute;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* If value is same day as max day,
|
|
* make sure the time value is in bounds.
|
|
*/
|
|
|
|
|
|
if (maxParts !== undefined && isSameDay(parts, maxParts)) {
|
|
/**
|
|
* If the hour is out of bounds,
|
|
* update both the hour and minute.
|
|
* This is done so that the new time
|
|
* is closest to what the user selected.
|
|
*/
|
|
if (partsCopy.hour !== undefined && maxParts.hour !== undefined) {
|
|
if (partsCopy.hour > maxParts.hour) {
|
|
partsCopy.hour = maxParts.hour;
|
|
partsCopy.minute = maxParts.minute;
|
|
/**
|
|
* If only the minute is out of bounds,
|
|
* set it to the max minute.
|
|
*/
|
|
} else if (partsCopy.hour === maxParts.hour && partsCopy.minute !== undefined && maxParts.minute !== undefined && partsCopy.minute > maxParts.minute) {
|
|
partsCopy.minute = maxParts.minute;
|
|
}
|
|
}
|
|
}
|
|
|
|
return partsCopy;
|
|
};
|
|
/**
|
|
* Returns the closest date to refParts
|
|
* that also meets the constraints of
|
|
* the *Values params.
|
|
* @param refParts The reference date
|
|
* @param monthValues The allowed month values
|
|
* @param dayValues The allowed day (of the month) values
|
|
* @param yearValues The allowed year values
|
|
* @param hourValues The allowed hour values
|
|
* @param minuteValues The allowed minute values
|
|
*/
|
|
|
|
|
|
const getClosestValidDate = (refParts, monthValues, dayValues, yearValues, hourValues, minuteValues) => {
|
|
const {
|
|
hour,
|
|
minute,
|
|
day,
|
|
month,
|
|
year
|
|
} = refParts;
|
|
const copyParts = Object.assign(Object.assign({}, refParts), {
|
|
dayOfWeek: undefined
|
|
});
|
|
|
|
if (monthValues !== undefined) {
|
|
copyParts.month = findClosestValue(month, monthValues);
|
|
} // Day is nullable but cannot be undefined
|
|
|
|
|
|
if (day !== null && dayValues !== undefined) {
|
|
copyParts.day = findClosestValue(day, dayValues);
|
|
}
|
|
|
|
if (yearValues !== undefined) {
|
|
copyParts.year = findClosestValue(year, yearValues);
|
|
}
|
|
|
|
if (hour !== undefined && hourValues !== undefined) {
|
|
copyParts.hour = findClosestValue(hour, hourValues);
|
|
copyParts.ampm = parseAmPm(copyParts.hour);
|
|
}
|
|
|
|
if (minute !== undefined && minuteValues !== undefined) {
|
|
copyParts.minute = findClosestValue(minute, minuteValues);
|
|
}
|
|
|
|
return copyParts;
|
|
};
|
|
/**
|
|
* Finds the value in "values" that is
|
|
* numerically closest to "reference".
|
|
* This function assumes that "values" is
|
|
* already sorted in ascending order.
|
|
* @param reference The reference number to use
|
|
* when finding the closest value
|
|
* @param values The allowed values that will be
|
|
* searched to find the closest value to "reference"
|
|
*/
|
|
|
|
|
|
const findClosestValue = (reference, values) => {
|
|
let closestValue = values[0];
|
|
let rank = Math.abs(closestValue - reference);
|
|
|
|
for (let i = 1; i < values.length; i++) {
|
|
const value = values[i];
|
|
/**
|
|
* This code prioritizes the first
|
|
* closest result. Given two values
|
|
* with the same distance from reference,
|
|
* this code will prioritize the smaller of
|
|
* the two values.
|
|
*/
|
|
|
|
const valueRank = Math.abs(value - reference);
|
|
|
|
if (valueRank < rank) {
|
|
closestValue = value;
|
|
rank = valueRank;
|
|
}
|
|
}
|
|
|
|
return closestValue;
|
|
};
|
|
|
|
const getFormattedDayPeriod = dayPeriod => {
|
|
if (dayPeriod === undefined) {
|
|
return '';
|
|
}
|
|
|
|
return dayPeriod.toUpperCase();
|
|
};
|
|
|
|
const getLocalizedTime = (locale, refParts, use24Hour) => {
|
|
const timeParts = {
|
|
hour: refParts.hour,
|
|
minute: refParts.minute
|
|
};
|
|
|
|
if (timeParts.hour === undefined || timeParts.minute === undefined) {
|
|
return 'Invalid Time';
|
|
}
|
|
|
|
return new Intl.DateTimeFormat(locale, {
|
|
hour: 'numeric',
|
|
minute: 'numeric',
|
|
timeZone: 'UTC',
|
|
|
|
/**
|
|
* We use hourCycle here instead of hour12 due to:
|
|
* https://bugs.chromium.org/p/chromium/issues/detail?id=1347316&q=hour12&can=2
|
|
*/
|
|
hourCycle: use24Hour ? 'h23' : 'h12'
|
|
}).format(new Date(convertDataToISO(Object.assign(Object.assign({
|
|
/**
|
|
* JS uses a simplified ISO 8601 format which allows for
|
|
* date-only formats and date-time formats, but not
|
|
* time-only formats: https://tc39.es/ecma262/#sec-date-time-string-format
|
|
* As a result, developers who only pass a time will get
|
|
* an "Invalid Date" error. To account for this, we make sure that
|
|
* year/day/month values are set when passing to new Date().
|
|
* The Intl.DateTimeFormat call above only uses the hour/minute
|
|
* values, so passing these date values should have no impact
|
|
* on the time output.
|
|
*/
|
|
year: 2023,
|
|
day: 1,
|
|
month: 1
|
|
}, timeParts), {
|
|
// TODO: FW-1831 will remove the need to manually set the tzOffset to undefined
|
|
tzOffset: undefined
|
|
}))));
|
|
};
|
|
/**
|
|
* Adds padding to a time value so
|
|
* that it is always 2 digits.
|
|
*/
|
|
|
|
|
|
const addTimePadding = value => {
|
|
const valueToString = value.toString();
|
|
|
|
if (valueToString.length > 1) {
|
|
return valueToString;
|
|
}
|
|
|
|
return `0${valueToString}`;
|
|
};
|
|
/**
|
|
* Formats 24 hour times so that
|
|
* it always has 2 digits. For
|
|
* 12 hour times it ensures that
|
|
* hour 0 is formatted as '12'.
|
|
*/
|
|
|
|
|
|
const getFormattedHour = (hour, use24Hour) => {
|
|
if (use24Hour) {
|
|
return addTimePadding(hour);
|
|
}
|
|
/**
|
|
* If using 12 hour
|
|
* format, make sure hour
|
|
* 0 is formatted as '12'.
|
|
*/
|
|
|
|
|
|
if (hour === 0) {
|
|
return '12';
|
|
}
|
|
|
|
return hour.toString();
|
|
};
|
|
/**
|
|
* Generates an aria-label to be read by screen readers
|
|
* given a local, a date, and whether or not that date is
|
|
* today's date.
|
|
*/
|
|
|
|
|
|
const generateDayAriaLabel = (locale, today, refParts) => {
|
|
if (refParts.day === null) {
|
|
return null;
|
|
}
|
|
/**
|
|
* MM/DD/YYYY will return midnight in the user's timezone.
|
|
*/
|
|
|
|
|
|
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
|
const labelString = new Intl.DateTimeFormat(locale, {
|
|
weekday: 'long',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
timeZone: 'UTC'
|
|
}).format(date);
|
|
/**
|
|
* If date is today, prepend "Today" so screen readers indicate
|
|
* that the date is today.
|
|
*/
|
|
|
|
return today ? `Today, ${labelString}` : labelString;
|
|
};
|
|
/**
|
|
* Gets the day of the week, month, and day
|
|
* Used for the header in MD mode.
|
|
*/
|
|
|
|
|
|
const getMonthAndDay = (locale, refParts) => {
|
|
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
|
return new Intl.DateTimeFormat(locale, {
|
|
weekday: 'short',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
timeZone: 'UTC'
|
|
}).format(date);
|
|
};
|
|
/**
|
|
* Given a locale and a date object,
|
|
* return a formatted string that includes
|
|
* the month name and full year.
|
|
* Example: May 2021
|
|
*/
|
|
|
|
|
|
const getMonthAndYear = (locale, refParts) => {
|
|
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
|
|
return new Intl.DateTimeFormat(locale, {
|
|
month: 'long',
|
|
year: 'numeric',
|
|
timeZone: 'UTC'
|
|
}).format(date);
|
|
};
|
|
/**
|
|
* Given a locale and a date object,
|
|
* return a formatted string that includes
|
|
* the short month, numeric day, and full year.
|
|
* Example: Apr 22, 2021
|
|
*/
|
|
|
|
|
|
const getMonthDayAndYear = (locale, refParts) => {
|
|
return getLocalizedDateTime(locale, refParts, {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric'
|
|
});
|
|
};
|
|
/**
|
|
* Given a locale and a date object,
|
|
* return a formatted string that includes
|
|
* the numeric day.
|
|
* Note: Some languages will add literal characters
|
|
* to the end. This function removes those literals.
|
|
* Example: 29
|
|
*/
|
|
|
|
|
|
const getDay = (locale, refParts) => {
|
|
return getLocalizedDateTimeParts(locale, refParts, {
|
|
day: 'numeric'
|
|
}).find(obj => obj.type === 'day').value;
|
|
};
|
|
/**
|
|
* Given a locale and a date object,
|
|
* return a formatted string that includes
|
|
* the numeric year.
|
|
* Example: 2022
|
|
*/
|
|
|
|
|
|
const getYear = (locale, refParts) => {
|
|
return getLocalizedDateTime(locale, refParts, {
|
|
year: 'numeric'
|
|
});
|
|
};
|
|
|
|
const getNormalizedDate = refParts => {
|
|
const timeString = refParts.hour !== undefined && refParts.minute !== undefined ? ` ${refParts.hour}:${refParts.minute}` : '';
|
|
return new Date(`${refParts.month}/${refParts.day}/${refParts.year}${timeString} GMT+0000`);
|
|
};
|
|
/**
|
|
* Given a locale, DatetimeParts, and options
|
|
* format the DatetimeParts according to the options
|
|
* and locale combination. This returns a string. If
|
|
* you want an array of the individual pieces
|
|
* that make up the localized date string, use
|
|
* getLocalizedDateTimeParts.
|
|
*/
|
|
|
|
|
|
const getLocalizedDateTime = (locale, refParts, options) => {
|
|
const date = getNormalizedDate(refParts);
|
|
return getDateTimeFormat(locale, options).format(date);
|
|
};
|
|
/**
|
|
* Given a locale, DatetimeParts, and options
|
|
* format the DatetimeParts according to the options
|
|
* and locale combination. This returns an array of
|
|
* each piece of the date.
|
|
*/
|
|
|
|
|
|
const getLocalizedDateTimeParts = (locale, refParts, options) => {
|
|
const date = getNormalizedDate(refParts);
|
|
return getDateTimeFormat(locale, options).formatToParts(date);
|
|
};
|
|
/**
|
|
* Wrapper function for Intl.DateTimeFormat.
|
|
* Allows developers to apply an allowed format to DatetimeParts.
|
|
* This function also has built in safeguards for older browser bugs
|
|
* with Intl.DateTimeFormat.
|
|
*/
|
|
|
|
|
|
const getDateTimeFormat = (locale, options) => {
|
|
return new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, options), {
|
|
timeZone: 'UTC'
|
|
}));
|
|
};
|
|
/**
|
|
* Gets a localized version of "Today"
|
|
* Falls back to "Today" in English for
|
|
* browsers that do not support RelativeTimeFormat.
|
|
*/
|
|
|
|
|
|
const getTodayLabel = locale => {
|
|
if ('RelativeTimeFormat' in Intl) {
|
|
const label = new Intl.RelativeTimeFormat(locale, {
|
|
numeric: 'auto'
|
|
}).format(0, 'day');
|
|
return label.charAt(0).toUpperCase() + label.slice(1);
|
|
} else {
|
|
return 'Today';
|
|
}
|
|
};
|
|
/**
|
|
* When calling toISOString(), the browser
|
|
* will convert the date to UTC time by either adding
|
|
* or subtracting the time zone offset.
|
|
* To work around this, we need to either add
|
|
* or subtract the time zone offset to the Date
|
|
* object prior to calling toISOString().
|
|
* This allows us to get an ISO string
|
|
* that is in the user's time zone.
|
|
*
|
|
* Example:
|
|
* Time zone offset is 240
|
|
* Meaning: The browser needs to add 240 minutes
|
|
* to the Date object to get UTC time.
|
|
* What Ionic does: We subtract 240 minutes
|
|
* from the Date object. The browser then adds
|
|
* 240 minutes in toISOString(). The result
|
|
* is a time that is in the user's time zone
|
|
* and not UTC.
|
|
*
|
|
* Note: Some timezones include minute adjustments
|
|
* such as 30 or 45 minutes. This is why we use setMinutes
|
|
* instead of setHours.
|
|
* Example: India Standard Time
|
|
* Timezone offset: -330 = -5.5 hours.
|
|
*
|
|
* List of timezones with 30 and 45 minute timezones:
|
|
* https://www.timeanddate.com/time/time-zones-interesting.html
|
|
*/
|
|
|
|
|
|
const removeDateTzOffset = date => {
|
|
const tzOffset = date.getTimezoneOffset();
|
|
date.setMinutes(date.getMinutes() - tzOffset);
|
|
return date;
|
|
};
|
|
|
|
const DATE_AM = removeDateTzOffset(new Date('2022T01:00'));
|
|
const DATE_PM = removeDateTzOffset(new Date('2022T13:00'));
|
|
/**
|
|
* Formats the locale's string representation of the day period (am/pm) for a given
|
|
* ref parts day period.
|
|
*
|
|
* @param locale The locale to format the day period in.
|
|
* @param value The date string, in ISO format.
|
|
* @returns The localized day period (am/pm) representation of the given value.
|
|
*/
|
|
|
|
const getLocalizedDayPeriod = (locale, dayPeriod) => {
|
|
const date = dayPeriod === 'am' ? DATE_AM : DATE_PM;
|
|
const localizedDayPeriod = new Intl.DateTimeFormat(locale, {
|
|
hour: 'numeric',
|
|
timeZone: 'UTC'
|
|
}).formatToParts(date).find(part => part.type === 'dayPeriod');
|
|
|
|
if (localizedDayPeriod) {
|
|
return localizedDayPeriod.value;
|
|
}
|
|
|
|
return getFormattedDayPeriod(dayPeriod);
|
|
};
|
|
/**
|
|
* Formats the datetime's value to a string, for use in the native input.
|
|
*
|
|
* @param value The value to format, either an ISO string or an array thereof.
|
|
*/
|
|
|
|
|
|
const formatValue = value => {
|
|
return Array.isArray(value) ? value.join(',') : value;
|
|
};
|
|
/**
|
|
* Returns the current date as
|
|
* an ISO string in the user's
|
|
* time zone.
|
|
*/
|
|
|
|
|
|
const getToday = () => {
|
|
/**
|
|
* ion-datetime intentionally does not
|
|
* parse time zones/do automatic time zone
|
|
* conversion when accepting user input.
|
|
* However when we get today's date string,
|
|
* we want it formatted relative to the user's
|
|
* time zone.
|
|
*
|
|
* When calling toISOString(), the browser
|
|
* will convert the date to UTC time by either adding
|
|
* or subtracting the time zone offset.
|
|
* To work around this, we need to either add
|
|
* or subtract the time zone offset to the Date
|
|
* object prior to calling toISOString().
|
|
* This allows us to get an ISO string
|
|
* that is in the user's time zone.
|
|
*/
|
|
return removeDateTzOffset(new Date()).toISOString();
|
|
};
|
|
|
|
const minutes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59];
|
|
const hour12 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
|
const hour23 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23];
|
|
/**
|
|
* Given a locale and a mode,
|
|
* return an array with formatted days
|
|
* of the week. iOS should display days
|
|
* such as "Mon" or "Tue".
|
|
* MD should display days such as "M"
|
|
* or "T".
|
|
*/
|
|
|
|
const getDaysOfWeek = (locale, mode, firstDayOfWeek = 0) => {
|
|
/**
|
|
* Nov 1st, 2020 starts on a Sunday.
|
|
* ion-datetime assumes weeks start on Sunday,
|
|
* but is configurable via `firstDayOfWeek`.
|
|
*/
|
|
const weekdayFormat = mode === 'ios' ? 'short' : 'narrow';
|
|
const intl = new Intl.DateTimeFormat(locale, {
|
|
weekday: weekdayFormat
|
|
});
|
|
const startDate = new Date('11/01/2020');
|
|
const daysOfWeek = [];
|
|
/**
|
|
* For each day of the week,
|
|
* get the day name.
|
|
*/
|
|
|
|
for (let i = firstDayOfWeek; i < firstDayOfWeek + 7; i++) {
|
|
const currentDate = new Date(startDate);
|
|
currentDate.setDate(currentDate.getDate() + i);
|
|
daysOfWeek.push(intl.format(currentDate));
|
|
}
|
|
|
|
return daysOfWeek;
|
|
};
|
|
/**
|
|
* Returns an array containing all of the
|
|
* days in a month for a given year. Values are
|
|
* aligned with a week calendar starting on
|
|
* the firstDayOfWeek value (Sunday by default)
|
|
* using null values.
|
|
*/
|
|
|
|
|
|
const getDaysOfMonth = (month, year, firstDayOfWeek) => {
|
|
const numDays = getNumDaysInMonth(month, year);
|
|
const firstOfMonth = new Date(`${month}/1/${year}`).getDay();
|
|
/**
|
|
* To get the first day of the month aligned on the correct
|
|
* day of the week, we need to determine how many "filler" days
|
|
* to generate. These filler days as empty/disabled buttons
|
|
* that fill the space of the days of the week before the first
|
|
* of the month.
|
|
*
|
|
* There are two cases here:
|
|
*
|
|
* 1. If firstOfMonth = 4, firstDayOfWeek = 0 then the offset
|
|
* is (4 - (0 + 1)) = 3. Since the offset loop goes from 0 to 3 inclusive,
|
|
* this will generate 4 filler days (0, 1, 2, 3), and then day of week 4 will have
|
|
* the first day of the month.
|
|
*
|
|
* 2. If firstOfMonth = 2, firstDayOfWeek = 4 then the offset
|
|
* is (6 - (4 - 2)) = 4. Since the offset loop goes from 0 to 4 inclusive,
|
|
* this will generate 5 filler days (0, 1, 2, 3, 4), and then day of week 5 will have
|
|
* the first day of the month.
|
|
*/
|
|
|
|
const offset = firstOfMonth >= firstDayOfWeek ? firstOfMonth - (firstDayOfWeek + 1) : 6 - (firstDayOfWeek - firstOfMonth);
|
|
let days = [];
|
|
|
|
for (let i = 1; i <= numDays; i++) {
|
|
days.push({
|
|
day: i,
|
|
dayOfWeek: (offset + i) % 7
|
|
});
|
|
}
|
|
|
|
for (let i = 0; i <= offset; i++) {
|
|
days = [{
|
|
day: null,
|
|
dayOfWeek: null
|
|
}, ...days];
|
|
}
|
|
|
|
return days;
|
|
};
|
|
/**
|
|
* Given a local, reference datetime parts and option
|
|
* max/min bound datetime parts, calculate the acceptable
|
|
* hour and minute values according to the bounds and locale.
|
|
*/
|
|
|
|
|
|
const generateTime = (refParts, hourCycle = 'h12', minParts, maxParts, hourValues, minuteValues) => {
|
|
const use24Hour = hourCycle === 'h23';
|
|
let processedHours = use24Hour ? hour23 : hour12;
|
|
let processedMinutes = minutes;
|
|
let isAMAllowed = true;
|
|
let isPMAllowed = true;
|
|
|
|
if (hourValues) {
|
|
processedHours = processedHours.filter(hour => hourValues.includes(hour));
|
|
}
|
|
|
|
if (minuteValues) {
|
|
processedMinutes = processedMinutes.filter(minute => minuteValues.includes(minute));
|
|
}
|
|
|
|
if (minParts) {
|
|
/**
|
|
* If ref day is the same as the
|
|
* minimum allowed day, filter hour/minute
|
|
* values according to min hour and minute.
|
|
*/
|
|
if (isSameDay(refParts, minParts)) {
|
|
/**
|
|
* Users may not always set the hour/minute for
|
|
* min value (i.e. 2021-06-02) so we should allow
|
|
* all hours/minutes in that case.
|
|
*/
|
|
if (minParts.hour !== undefined) {
|
|
processedHours = processedHours.filter(hour => {
|
|
const convertedHour = refParts.ampm === 'pm' ? (hour + 12) % 24 : hour;
|
|
return (use24Hour ? hour : convertedHour) >= minParts.hour;
|
|
});
|
|
isAMAllowed = minParts.hour < 13;
|
|
}
|
|
|
|
if (minParts.minute !== undefined) {
|
|
/**
|
|
* The minimum minute range should not be enforced when
|
|
* the hour is greater than the min hour.
|
|
*
|
|
* For example with a minimum range of 09:30, users
|
|
* should be able to select 10:00-10:29 and beyond.
|
|
*/
|
|
let isPastMinHour = false;
|
|
|
|
if (minParts.hour !== undefined && refParts.hour !== undefined) {
|
|
if (refParts.hour > minParts.hour) {
|
|
isPastMinHour = true;
|
|
}
|
|
}
|
|
|
|
processedMinutes = processedMinutes.filter(minute => {
|
|
if (isPastMinHour) {
|
|
return true;
|
|
}
|
|
|
|
return minute >= minParts.minute;
|
|
});
|
|
}
|
|
/**
|
|
* If ref day is before minimum
|
|
* day do not render any hours/minute values
|
|
*/
|
|
|
|
} else if (isBefore(refParts, minParts)) {
|
|
processedHours = [];
|
|
processedMinutes = [];
|
|
isAMAllowed = isPMAllowed = false;
|
|
}
|
|
}
|
|
|
|
if (maxParts) {
|
|
/**
|
|
* If ref day is the same as the
|
|
* maximum allowed day, filter hour/minute
|
|
* values according to max hour and minute.
|
|
*/
|
|
if (isSameDay(refParts, maxParts)) {
|
|
/**
|
|
* Users may not always set the hour/minute for
|
|
* max value (i.e. 2021-06-02) so we should allow
|
|
* all hours/minutes in that case.
|
|
*/
|
|
if (maxParts.hour !== undefined) {
|
|
processedHours = processedHours.filter(hour => {
|
|
const convertedHour = refParts.ampm === 'pm' ? (hour + 12) % 24 : hour;
|
|
return (use24Hour ? hour : convertedHour) <= maxParts.hour;
|
|
});
|
|
isPMAllowed = maxParts.hour >= 12;
|
|
}
|
|
|
|
if (maxParts.minute !== undefined && refParts.hour === maxParts.hour) {
|
|
// The available minutes should only be filtered when the hour is the same as the max hour.
|
|
// For example if the max hour is 10:30 and the current hour is 10:00,
|
|
// users should be able to select 00-30 minutes.
|
|
// If the current hour is 09:00, users should be able to select 00-60 minutes.
|
|
processedMinutes = processedMinutes.filter(minute => minute <= maxParts.minute);
|
|
}
|
|
/**
|
|
* If ref day is after minimum
|
|
* day do not render any hours/minute values
|
|
*/
|
|
|
|
} else if (isAfter(refParts, maxParts)) {
|
|
processedHours = [];
|
|
processedMinutes = [];
|
|
isAMAllowed = isPMAllowed = false;
|
|
}
|
|
}
|
|
|
|
return {
|
|
hours: processedHours,
|
|
minutes: processedMinutes,
|
|
am: isAMAllowed,
|
|
pm: isPMAllowed
|
|
};
|
|
};
|
|
/**
|
|
* Given DatetimeParts, generate the previous,
|
|
* current, and and next months.
|
|
*/
|
|
|
|
|
|
const generateMonths = refParts => {
|
|
return [getPreviousMonth(refParts), {
|
|
month: refParts.month,
|
|
year: refParts.year,
|
|
day: refParts.day
|
|
}, getNextMonth(refParts)];
|
|
};
|
|
|
|
const getMonthColumnData = (locale, refParts, minParts, maxParts, monthValues, formatOptions = {
|
|
month: 'long'
|
|
}) => {
|
|
const {
|
|
year
|
|
} = refParts;
|
|
const months = [];
|
|
|
|
if (monthValues !== undefined) {
|
|
let processedMonths = monthValues;
|
|
|
|
if ((maxParts === null || maxParts === void 0 ? void 0 : maxParts.month) !== undefined) {
|
|
processedMonths = processedMonths.filter(month => month <= maxParts.month);
|
|
}
|
|
|
|
if ((minParts === null || minParts === void 0 ? void 0 : minParts.month) !== undefined) {
|
|
processedMonths = processedMonths.filter(month => month >= minParts.month);
|
|
}
|
|
|
|
processedMonths.forEach(processedMonth => {
|
|
const date = new Date(`${processedMonth}/1/${year} GMT+0000`);
|
|
const monthString = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, formatOptions), {
|
|
timeZone: 'UTC'
|
|
})).format(date);
|
|
months.push({
|
|
text: monthString,
|
|
value: processedMonth
|
|
});
|
|
});
|
|
} else {
|
|
const maxMonth = maxParts && maxParts.year === year ? maxParts.month : 12;
|
|
const minMonth = minParts && minParts.year === year ? minParts.month : 1;
|
|
|
|
for (let i = minMonth; i <= maxMonth; i++) {
|
|
/**
|
|
*
|
|
* There is a bug on iOS 14 where
|
|
* Intl.DateTimeFormat takes into account
|
|
* the local timezone offset when formatting dates.
|
|
*
|
|
* Forcing the timezone to 'UTC' fixes the issue. However,
|
|
* we should keep this workaround as it is safer. In the event
|
|
* this breaks in another browser, we will not be impacted
|
|
* because all dates will be interpreted in UTC.
|
|
*
|
|
* Example:
|
|
* new Intl.DateTimeFormat('en-US', { month: 'long' }).format(new Date('Sat Apr 01 2006 00:00:00 GMT-0400 (EDT)')) // "March"
|
|
* new Intl.DateTimeFormat('en-US', { month: 'long', timeZone: 'UTC' }).format(new Date('Sat Apr 01 2006 00:00:00 GMT-0400 (EDT)')) // "April"
|
|
*
|
|
* In certain timezones, iOS 14 shows the wrong
|
|
* date for .toUTCString(). To combat this, we
|
|
* force all of the timezones to GMT+0000 (UTC).
|
|
*
|
|
* Example:
|
|
* Time Zone: Central European Standard Time
|
|
* new Date('1/1/1992').toUTCString() // "Tue, 31 Dec 1991 23:00:00 GMT"
|
|
* new Date('1/1/1992 GMT+0000').toUTCString() // "Wed, 01 Jan 1992 00:00:00 GMT"
|
|
*/
|
|
const date = new Date(`${i}/1/${year} GMT+0000`);
|
|
const monthString = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, formatOptions), {
|
|
timeZone: 'UTC'
|
|
})).format(date);
|
|
months.push({
|
|
text: monthString,
|
|
value: i
|
|
});
|
|
}
|
|
}
|
|
|
|
return months;
|
|
};
|
|
/**
|
|
* Returns information regarding
|
|
* selectable dates (i.e 1st, 2nd, 3rd, etc)
|
|
* within a reference month.
|
|
* @param locale The locale to format the date with
|
|
* @param refParts The reference month/year to generate dates for
|
|
* @param minParts The minimum bound on the date that can be returned
|
|
* @param maxParts The maximum bound on the date that can be returned
|
|
* @param dayValues The allowed date values
|
|
* @returns Date data to be used in ion-picker-column-internal
|
|
*/
|
|
|
|
|
|
const getDayColumnData = (locale, refParts, minParts, maxParts, dayValues, formatOptions = {
|
|
day: 'numeric'
|
|
}) => {
|
|
const {
|
|
month,
|
|
year
|
|
} = refParts;
|
|
const days = [];
|
|
/**
|
|
* If we have max/min bounds that in the same
|
|
* month/year as the refParts, we should
|
|
* use the define day as the max/min day.
|
|
* Otherwise, fallback to the max/min days in a month.
|
|
*/
|
|
|
|
const numDaysInMonth = getNumDaysInMonth(month, year);
|
|
const maxDay = (maxParts === null || maxParts === void 0 ? void 0 : maxParts.day) !== null && (maxParts === null || maxParts === void 0 ? void 0 : maxParts.day) !== undefined && maxParts.year === year && maxParts.month === month ? maxParts.day : numDaysInMonth;
|
|
const minDay = (minParts === null || minParts === void 0 ? void 0 : minParts.day) !== null && (minParts === null || minParts === void 0 ? void 0 : minParts.day) !== undefined && minParts.year === year && minParts.month === month ? minParts.day : 1;
|
|
|
|
if (dayValues !== undefined) {
|
|
let processedDays = dayValues;
|
|
processedDays = processedDays.filter(day => day >= minDay && day <= maxDay);
|
|
processedDays.forEach(processedDay => {
|
|
const date = new Date(`${month}/${processedDay}/${year} GMT+0000`);
|
|
const dayString = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, formatOptions), {
|
|
timeZone: 'UTC'
|
|
})).format(date);
|
|
days.push({
|
|
text: dayString,
|
|
value: processedDay
|
|
});
|
|
});
|
|
} else {
|
|
for (let i = minDay; i <= maxDay; i++) {
|
|
const date = new Date(`${month}/${i}/${year} GMT+0000`);
|
|
const dayString = new Intl.DateTimeFormat(locale, Object.assign(Object.assign({}, formatOptions), {
|
|
timeZone: 'UTC'
|
|
})).format(date);
|
|
days.push({
|
|
text: dayString,
|
|
value: i
|
|
});
|
|
}
|
|
}
|
|
|
|
return days;
|
|
};
|
|
|
|
const getYearColumnData = (locale, refParts, minParts, maxParts, yearValues) => {
|
|
var _a, _b;
|
|
|
|
let processedYears = [];
|
|
|
|
if (yearValues !== undefined) {
|
|
processedYears = yearValues;
|
|
|
|
if ((maxParts === null || maxParts === void 0 ? void 0 : maxParts.year) !== undefined) {
|
|
processedYears = processedYears.filter(year => year <= maxParts.year);
|
|
}
|
|
|
|
if ((minParts === null || minParts === void 0 ? void 0 : minParts.year) !== undefined) {
|
|
processedYears = processedYears.filter(year => year >= minParts.year);
|
|
}
|
|
} else {
|
|
const {
|
|
year
|
|
} = refParts;
|
|
const maxYear = (_a = maxParts === null || maxParts === void 0 ? void 0 : maxParts.year) !== null && _a !== void 0 ? _a : year;
|
|
const minYear = (_b = minParts === null || minParts === void 0 ? void 0 : minParts.year) !== null && _b !== void 0 ? _b : year - 100;
|
|
|
|
for (let i = maxYear; i >= minYear; i--) {
|
|
processedYears.push(i);
|
|
}
|
|
}
|
|
|
|
return processedYears.map(year => ({
|
|
text: getYear(locale, {
|
|
year,
|
|
month: refParts.month,
|
|
day: refParts.day
|
|
}),
|
|
value: year
|
|
}));
|
|
};
|
|
/**
|
|
* Given a starting date and an upper bound,
|
|
* this functions returns an array of all
|
|
* month objects in that range.
|
|
*/
|
|
|
|
|
|
const getAllMonthsInRange = (currentParts, maxParts) => {
|
|
if (currentParts.month === maxParts.month && currentParts.year === maxParts.year) {
|
|
return [currentParts];
|
|
}
|
|
|
|
return [currentParts, ...getAllMonthsInRange(getNextMonth(currentParts), maxParts)];
|
|
};
|
|
/**
|
|
* Creates and returns picker items
|
|
* that represent the days in a month.
|
|
* Example: "Thu, Jun 2"
|
|
*/
|
|
|
|
|
|
const getCombinedDateColumnData = (locale, todayParts, minParts, maxParts, dayValues, monthValues) => {
|
|
let items = [];
|
|
let parts = [];
|
|
/**
|
|
* Get all month objects from the min date
|
|
* to the max date. Note: Do not use getMonthColumnData
|
|
* as that function only generates dates within a
|
|
* single year.
|
|
*/
|
|
|
|
let months = getAllMonthsInRange(minParts, maxParts);
|
|
/**
|
|
* Filter out any disallowed month values.
|
|
*/
|
|
|
|
if (monthValues) {
|
|
months = months.filter(({
|
|
month
|
|
}) => monthValues.includes(month));
|
|
}
|
|
/**
|
|
* Get all of the days in the month.
|
|
* From there, generate an array where
|
|
* each item has the month, date, and day
|
|
* of work as the text.
|
|
*/
|
|
|
|
|
|
months.forEach(monthObject => {
|
|
const referenceMonth = {
|
|
month: monthObject.month,
|
|
day: null,
|
|
year: monthObject.year
|
|
};
|
|
const monthDays = getDayColumnData(locale, referenceMonth, minParts, maxParts, dayValues, {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
weekday: 'short'
|
|
});
|
|
const dateParts = [];
|
|
const dateColumnItems = [];
|
|
monthDays.forEach(dayObject => {
|
|
const isToday = isSameDay(Object.assign(Object.assign({}, referenceMonth), {
|
|
day: dayObject.value
|
|
}), todayParts);
|
|
/**
|
|
* Today's date should read as "Today" (localized)
|
|
* not the actual date string
|
|
*/
|
|
|
|
dateColumnItems.push({
|
|
text: isToday ? getTodayLabel(locale) : dayObject.text,
|
|
value: `${referenceMonth.year}-${referenceMonth.month}-${dayObject.value}`
|
|
});
|
|
/**
|
|
* When selecting a date in the wheel picker
|
|
* we need access to the raw datetime parts data.
|
|
* The picker column only accepts values of
|
|
* type string or number, so we need to return
|
|
* two sets of data: A data set to be passed
|
|
* to the picker column, and a data set to
|
|
* be used to reference the raw data when
|
|
* updating the picker column value.
|
|
*/
|
|
|
|
dateParts.push({
|
|
month: referenceMonth.month,
|
|
year: referenceMonth.year,
|
|
day: dayObject.value
|
|
});
|
|
});
|
|
parts = [...parts, ...dateParts];
|
|
items = [...items, ...dateColumnItems];
|
|
});
|
|
return {
|
|
parts,
|
|
items
|
|
};
|
|
};
|
|
|
|
const getTimeColumnsData = (locale, refParts, hourCycle, minParts, maxParts, allowedHourValues, allowedMinuteValues) => {
|
|
const use24Hour = is24Hour(locale, hourCycle);
|
|
const {
|
|
hours,
|
|
minutes,
|
|
am,
|
|
pm
|
|
} = generateTime(refParts, use24Hour ? 'h23' : 'h12', minParts, maxParts, allowedHourValues, allowedMinuteValues);
|
|
const hoursItems = hours.map(hour => {
|
|
return {
|
|
text: getFormattedHour(hour, use24Hour),
|
|
value: getInternalHourValue(hour, use24Hour, refParts.ampm)
|
|
};
|
|
});
|
|
const minutesItems = minutes.map(minute => {
|
|
return {
|
|
text: addTimePadding(minute),
|
|
value: minute
|
|
};
|
|
});
|
|
const dayPeriodItems = [];
|
|
|
|
if (am && !use24Hour) {
|
|
dayPeriodItems.push({
|
|
text: getLocalizedDayPeriod(locale, 'am'),
|
|
value: 'am'
|
|
});
|
|
}
|
|
|
|
if (pm && !use24Hour) {
|
|
dayPeriodItems.push({
|
|
text: getLocalizedDayPeriod(locale, 'pm'),
|
|
value: 'pm'
|
|
});
|
|
}
|
|
|
|
return {
|
|
minutesData: minutesItems,
|
|
hoursData: hoursItems,
|
|
dayPeriodData: dayPeriodItems
|
|
};
|
|
};
|
|
|
|
|
|
|
|
/***/ }),
|
|
|
|
/***/ 99273:
|
|
/*!*************************************************************!*\
|
|
!*** ./node_modules/@ionic/core/dist/esm/index-c4b11676.js ***!
|
|
\*************************************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "a": () => (/* binding */ printRequiredElementError),
|
|
/* harmony export */ "b": () => (/* binding */ printIonError),
|
|
/* harmony export */ "p": () => (/* binding */ printIonWarning)
|
|
/* harmony export */ });
|
|
/*!
|
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
*/
|
|
|
|
/**
|
|
* Logs a warning to the console with an Ionic prefix
|
|
* to indicate the library that is warning the developer.
|
|
*
|
|
* @param message - The string message to be logged to the console.
|
|
*/
|
|
const printIonWarning = (message, ...params) => {
|
|
return console.warn(`[Ionic Warning]: ${message}`, ...params);
|
|
};
|
|
/*
|
|
* Logs an error to the console with an Ionic prefix
|
|
* to indicate the library that is warning the developer.
|
|
*
|
|
* @param message - The string message to be logged to the console.
|
|
* @param params - Additional arguments to supply to the console.error.
|
|
*/
|
|
|
|
|
|
const printIonError = (message, ...params) => {
|
|
return console.error(`[Ionic Error]: ${message}`, ...params);
|
|
};
|
|
/**
|
|
* Prints an error informing developers that an implementation requires an element to be used
|
|
* within a specific selector.
|
|
*
|
|
* @param el The web component element this is requiring the element.
|
|
* @param targetSelectors The selector or selectors that were not found.
|
|
*/
|
|
|
|
|
|
const printRequiredElementError = (el, ...targetSelectors) => {
|
|
return console.error(`<${el.tagName.toLowerCase()}> must be used inside ${targetSelectors.join(' or ')}.`);
|
|
};
|
|
|
|
|
|
|
|
/***/ }),
|
|
|
|
/***/ 17950:
|
|
/*!************************************************************************!*\
|
|
!*** ./node_modules/@ionic/core/dist/esm/ion-datetime-button.entry.js ***!
|
|
\************************************************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "ion_datetime_button": () => (/* binding */ DatetimeButton)
|
|
/* harmony export */ });
|
|
/* harmony import */ var _Volumes_case_workspace_work_APPS_KA_APP_WORK_NOW_KA_PASS_V2_2_6_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js */ 71670);
|
|
/* harmony import */ var _index_8e692445_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index-8e692445.js */ 91559);
|
|
/* harmony import */ var _ionic_global_c74e4951_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ionic-global-c74e4951.js */ 95823);
|
|
/* harmony import */ var _helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./helpers-3b390e48.js */ 29259);
|
|
/* harmony import */ var _index_c4b11676_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./index-c4b11676.js */ 99273);
|
|
/* harmony import */ var _theme_7670341c_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./theme-7670341c.js */ 50320);
|
|
/* harmony import */ var _data_caf38df0_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./data-caf38df0.js */ 52037);
|
|
|
|
|
|
/*!
|
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const iosDatetimeButtonCss = ":host{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}:host button{border-radius:8px;padding-left:12px;padding-right:12px;padding-top:6px;padding-bottom:6px;margin-left:2px;margin-right:2px;margin-top:0px;margin-bottom:0px;position:relative;-webkit-transition:150ms color ease-in-out;transition:150ms color ease-in-out;border:none;background:var(--ion-color-step-300, #edeef0);color:var(--ion-text-color, #000);font-family:inherit;font-size:inherit;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;overflow:hidden}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){:host button{padding-left:unset;padding-right:unset;-webkit-padding-start:12px;padding-inline-start:12px;-webkit-padding-end:12px;padding-inline-end:12px}}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){:host button{margin-left:unset;margin-right:unset;-webkit-margin-start:2px;margin-inline-start:2px;-webkit-margin-end:2px;margin-inline-end:2px}}:host(.time-active) #time-button,:host(.date-active) #date-button{color:var(--ion-color-base)}:host(.datetime-button-disabled){pointer-events:none}:host(.datetime-button-disabled) button{opacity:0.4}";
|
|
const mdDatetimeButtonCss = ":host{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}:host button{border-radius:8px;padding-left:12px;padding-right:12px;padding-top:6px;padding-bottom:6px;margin-left:2px;margin-right:2px;margin-top:0px;margin-bottom:0px;position:relative;-webkit-transition:150ms color ease-in-out;transition:150ms color ease-in-out;border:none;background:var(--ion-color-step-300, #edeef0);color:var(--ion-text-color, #000);font-family:inherit;font-size:inherit;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;overflow:hidden}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){:host button{padding-left:unset;padding-right:unset;-webkit-padding-start:12px;padding-inline-start:12px;-webkit-padding-end:12px;padding-inline-end:12px}}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){:host button{margin-left:unset;margin-right:unset;-webkit-margin-start:2px;margin-inline-start:2px;-webkit-margin-end:2px;margin-inline-end:2px}}:host(.time-active) #time-button,:host(.date-active) #date-button{color:var(--ion-color-base)}:host(.datetime-button-disabled){pointer-events:none}:host(.datetime-button-disabled) button{opacity:0.4}";
|
|
const DatetimeButton = class {
|
|
constructor(hostRef) {
|
|
var _this = this;
|
|
|
|
(0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_1__.r)(this, hostRef);
|
|
this.datetimeEl = null;
|
|
this.overlayEl = null;
|
|
this.datetimePresentation = 'date-time';
|
|
this.datetimeActive = false;
|
|
/**
|
|
* The color to use from your application's color palette.
|
|
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
|
* For more information on colors, see [theming](/docs/theming/basics).
|
|
*/
|
|
|
|
this.color = 'primary';
|
|
/**
|
|
* If `true`, the user cannot interact with the button.
|
|
*/
|
|
|
|
this.disabled = false;
|
|
/**
|
|
* Accepts one or more string values and converts
|
|
* them to DatetimeParts. This is done so datetime-button
|
|
* can work with an array internally and not need
|
|
* to keep checking if the datetime value is `string` or `string[]`.
|
|
*/
|
|
|
|
this.getParsedDateValues = value => {
|
|
// TODO FW-2646 Remove value === ''
|
|
if (value === '' || value === undefined || value === null) {
|
|
return [];
|
|
}
|
|
|
|
if (Array.isArray(value)) {
|
|
return value;
|
|
}
|
|
|
|
return [value];
|
|
};
|
|
/**
|
|
* Check the value property on the linked
|
|
* ion-datetime and then format it according
|
|
* to the locale specified on ion-datetime.
|
|
*/
|
|
|
|
|
|
this.setDateTimeText = () => {
|
|
const {
|
|
datetimeEl,
|
|
datetimePresentation
|
|
} = this;
|
|
|
|
if (!datetimeEl) {
|
|
return;
|
|
}
|
|
|
|
const {
|
|
value,
|
|
locale,
|
|
hourCycle,
|
|
preferWheel,
|
|
multiple,
|
|
titleSelectedDatesFormatter
|
|
} = datetimeEl;
|
|
const parsedValues = this.getParsedDateValues(value);
|
|
/**
|
|
* Both ion-datetime and ion-datetime-button default
|
|
* to today's date and time if no value is set.
|
|
*/
|
|
|
|
const parsedDatetimes = (0,_data_caf38df0_js__WEBPACK_IMPORTED_MODULE_6__.q)(parsedValues.length > 0 ? parsedValues : [(0,_data_caf38df0_js__WEBPACK_IMPORTED_MODULE_6__.t)()]);
|
|
/**
|
|
* If developers incorrectly use multiple="true"
|
|
* with non "date" datetimes, then just select
|
|
* the first value so the interface does
|
|
* not appear broken. Datetime will provide a
|
|
* warning in the console.
|
|
*/
|
|
|
|
const firstParsedDatetime = parsedDatetimes[0];
|
|
const use24Hour = (0,_data_caf38df0_js__WEBPACK_IMPORTED_MODULE_6__.J)(locale, hourCycle); // TODO(FW-1865) - Remove once FW-1831 is fixed.
|
|
|
|
parsedDatetimes.forEach(parsedDatetime => {
|
|
parsedDatetime.tzOffset = undefined;
|
|
});
|
|
this.dateText = this.timeText = undefined;
|
|
|
|
switch (datetimePresentation) {
|
|
case 'date-time':
|
|
case 'time-date':
|
|
const dateText = (0,_data_caf38df0_js__WEBPACK_IMPORTED_MODULE_6__.T)(locale, firstParsedDatetime);
|
|
const timeText = (0,_data_caf38df0_js__WEBPACK_IMPORTED_MODULE_6__.K)(locale, firstParsedDatetime, use24Hour);
|
|
|
|
if (preferWheel) {
|
|
this.dateText = `${dateText} ${timeText}`;
|
|
} else {
|
|
this.dateText = dateText;
|
|
this.timeText = timeText;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'date':
|
|
if (multiple && parsedValues.length !== 1) {
|
|
let headerText = `${parsedValues.length} days`; // default/fallback for multiple selection
|
|
|
|
if (titleSelectedDatesFormatter !== undefined) {
|
|
try {
|
|
headerText = titleSelectedDatesFormatter(parsedValues);
|
|
} catch (e) {
|
|
(0,_index_c4b11676_js__WEBPACK_IMPORTED_MODULE_4__.b)('Exception in provided `titleSelectedDatesFormatter`: ', e);
|
|
}
|
|
}
|
|
|
|
this.dateText = headerText;
|
|
} else {
|
|
this.dateText = (0,_data_caf38df0_js__WEBPACK_IMPORTED_MODULE_6__.T)(locale, firstParsedDatetime);
|
|
}
|
|
|
|
break;
|
|
|
|
case 'time':
|
|
this.timeText = (0,_data_caf38df0_js__WEBPACK_IMPORTED_MODULE_6__.K)(locale, firstParsedDatetime, use24Hour);
|
|
break;
|
|
|
|
case 'month-year':
|
|
this.dateText = (0,_data_caf38df0_js__WEBPACK_IMPORTED_MODULE_6__.G)(locale, firstParsedDatetime);
|
|
break;
|
|
|
|
case 'month':
|
|
this.dateText = (0,_data_caf38df0_js__WEBPACK_IMPORTED_MODULE_6__.S)(locale, firstParsedDatetime, {
|
|
month: 'long'
|
|
});
|
|
break;
|
|
|
|
case 'year':
|
|
this.dateText = (0,_data_caf38df0_js__WEBPACK_IMPORTED_MODULE_6__.S)(locale, firstParsedDatetime, {
|
|
year: 'numeric'
|
|
});
|
|
break;
|
|
}
|
|
};
|
|
/**
|
|
* Waits for the ion-datetime to re-render.
|
|
* This is needed in order to correctly position
|
|
* a popover relative to the trigger element.
|
|
*/
|
|
|
|
|
|
this.waitForDatetimeChanges = /*#__PURE__*/(0,_Volumes_case_workspace_work_APPS_KA_APP_WORK_NOW_KA_PASS_V2_2_6_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function* () {
|
|
const {
|
|
datetimeEl
|
|
} = _this;
|
|
|
|
if (!datetimeEl) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return new Promise(resolve => {
|
|
(0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_3__.a)(datetimeEl, 'ionRender', resolve, {
|
|
once: true
|
|
});
|
|
});
|
|
});
|
|
|
|
this.handleDateClick = /*#__PURE__*/function () {
|
|
var _ref2 = (0,_Volumes_case_workspace_work_APPS_KA_APP_WORK_NOW_KA_PASS_V2_2_6_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function* (ev) {
|
|
const {
|
|
datetimeEl,
|
|
datetimePresentation
|
|
} = _this;
|
|
|
|
if (!datetimeEl) {
|
|
return;
|
|
}
|
|
|
|
let needsPresentationChange = false;
|
|
/**
|
|
* When clicking the date button,
|
|
* we need to make sure that only a date
|
|
* picker is displayed. For presentation styles
|
|
* that display content other than a date picker,
|
|
* we need to update the presentation style.
|
|
*/
|
|
|
|
switch (datetimePresentation) {
|
|
case 'date-time':
|
|
case 'time-date':
|
|
const needsChange = datetimeEl.presentation !== 'date';
|
|
/**
|
|
* The date+time wheel picker
|
|
* shows date and time together,
|
|
* so do not adjust the presentation
|
|
* in that case.
|
|
*/
|
|
|
|
if (!datetimeEl.preferWheel && needsChange) {
|
|
datetimeEl.presentation = 'date';
|
|
needsPresentationChange = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
/**
|
|
* Track which button was clicked
|
|
* so that it can have the correct
|
|
* activated styles applied when
|
|
* the modal/popover containing
|
|
* the datetime is opened.
|
|
*/
|
|
|
|
|
|
_this.selectedButton = 'date';
|
|
|
|
_this.presentOverlay(ev, needsPresentationChange, _this.dateTargetEl);
|
|
});
|
|
|
|
return function (_x) {
|
|
return _ref2.apply(this, arguments);
|
|
};
|
|
}();
|
|
|
|
this.handleTimeClick = ev => {
|
|
const {
|
|
datetimeEl,
|
|
datetimePresentation
|
|
} = this;
|
|
|
|
if (!datetimeEl) {
|
|
return;
|
|
}
|
|
|
|
let needsPresentationChange = false;
|
|
/**
|
|
* When clicking the time button,
|
|
* we need to make sure that only a time
|
|
* picker is displayed. For presentation styles
|
|
* that display content other than a time picker,
|
|
* we need to update the presentation style.
|
|
*/
|
|
|
|
switch (datetimePresentation) {
|
|
case 'date-time':
|
|
case 'time-date':
|
|
const needsChange = datetimeEl.presentation !== 'time';
|
|
|
|
if (needsChange) {
|
|
datetimeEl.presentation = 'time';
|
|
needsPresentationChange = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
/**
|
|
* Track which button was clicked
|
|
* so that it can have the correct
|
|
* activated styles applied when
|
|
* the modal/popover containing
|
|
* the datetime is opened.
|
|
*/
|
|
|
|
|
|
this.selectedButton = 'time';
|
|
this.presentOverlay(ev, needsPresentationChange, this.timeTargetEl);
|
|
};
|
|
/**
|
|
* If the datetime is presented in an
|
|
* overlay, the datetime and overlay
|
|
* should be appropriately sized.
|
|
* These classes provide default sizing values
|
|
* that developers can customize.
|
|
* The goal is to provide an overlay that is
|
|
* reasonably sized with a datetime that
|
|
* fills the entire container.
|
|
*/
|
|
|
|
|
|
this.presentOverlay = /*#__PURE__*/function () {
|
|
var _ref3 = (0,_Volumes_case_workspace_work_APPS_KA_APP_WORK_NOW_KA_PASS_V2_2_6_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function* (ev, needsPresentationChange, triggerEl) {
|
|
const {
|
|
overlayEl
|
|
} = _this;
|
|
|
|
if (!overlayEl) {
|
|
return;
|
|
}
|
|
|
|
if (overlayEl.tagName === 'ION-POPOVER') {
|
|
/**
|
|
* When the presentation on datetime changes,
|
|
* we need to wait for the component to re-render
|
|
* otherwise the computed width/height of the
|
|
* popover content will be wrong, causing
|
|
* the popover to not align with the trigger element.
|
|
*/
|
|
if (needsPresentationChange) {
|
|
yield _this.waitForDatetimeChanges();
|
|
}
|
|
/**
|
|
* We pass the trigger button element
|
|
* so that the popover aligns with the individual
|
|
* button that was clicked, not the component container.
|
|
*/
|
|
|
|
|
|
overlayEl.present(Object.assign(Object.assign({}, ev), {
|
|
detail: {
|
|
ionShadowTarget: triggerEl
|
|
}
|
|
}));
|
|
} else {
|
|
overlayEl.present();
|
|
}
|
|
});
|
|
|
|
return function (_x2, _x3, _x4) {
|
|
return _ref3.apply(this, arguments);
|
|
};
|
|
}();
|
|
}
|
|
|
|
componentWillLoad() {
|
|
var _this2 = this;
|
|
|
|
return (0,_Volumes_case_workspace_work_APPS_KA_APP_WORK_NOW_KA_PASS_V2_2_6_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function* () {
|
|
const {
|
|
datetime
|
|
} = _this2;
|
|
|
|
if (!datetime) {
|
|
(0,_index_c4b11676_js__WEBPACK_IMPORTED_MODULE_4__.b)('An ID associated with an ion-datetime instance is required for ion-datetime-button to function properly.', _this2.el);
|
|
return;
|
|
}
|
|
|
|
const datetimeEl = _this2.datetimeEl = document.getElementById(datetime);
|
|
|
|
if (!datetimeEl) {
|
|
(0,_index_c4b11676_js__WEBPACK_IMPORTED_MODULE_4__.b)(`No ion-datetime instance found for ID '${datetime}'.`, _this2.el);
|
|
return;
|
|
}
|
|
/**
|
|
* Since the datetime can be used in any context (overlays, accordion, etc)
|
|
* we track when it is visible to determine when it is active.
|
|
* This informs which button is highlighted as well as the
|
|
* aria-expanded state.
|
|
*/
|
|
|
|
|
|
const io = new IntersectionObserver(entries => {
|
|
const ev = entries[0];
|
|
_this2.datetimeActive = ev.isIntersecting;
|
|
}, {
|
|
threshold: 0.01
|
|
});
|
|
io.observe(datetimeEl);
|
|
/**
|
|
* Get a reference to any modal/popover
|
|
* the datetime is being used in so we can
|
|
* correctly size it when it is presented.
|
|
*/
|
|
|
|
const overlayEl = _this2.overlayEl = datetimeEl.closest('ion-modal, ion-popover');
|
|
/**
|
|
* The .ion-datetime-button-overlay class contains
|
|
* styles that allow any modal/popover to be
|
|
* sized according to the dimensions of the datetime.
|
|
* If developers want a smaller/larger overlay all they need
|
|
* to do is change the width/height of the datetime.
|
|
* Additionally, this lets us avoid having to set
|
|
* explicit widths on each variant of datetime.
|
|
*/
|
|
|
|
if (overlayEl) {
|
|
overlayEl.classList.add('ion-datetime-button-overlay');
|
|
}
|
|
|
|
(0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_3__.c)(datetimeEl, () => {
|
|
const datetimePresentation = _this2.datetimePresentation = datetimeEl.presentation || 'date-time';
|
|
/**
|
|
* Set the initial display
|
|
* in the rendered buttons.
|
|
*
|
|
* From there, we need to listen
|
|
* for ionChange to be emitted
|
|
* from datetime so we know when
|
|
* to re-render the displayed
|
|
* text in the buttons.
|
|
*/
|
|
|
|
_this2.setDateTimeText();
|
|
|
|
(0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_3__.a)(datetimeEl, 'ionChange', _this2.setDateTimeText);
|
|
/**
|
|
* Configure the initial selected button
|
|
* in the event that the datetime is displayed
|
|
* without clicking one of the datetime buttons.
|
|
* For example, a datetime could be expanded
|
|
* in an accordion. In this case users only
|
|
* need to click the accordion header to show
|
|
* the datetime.
|
|
*/
|
|
|
|
switch (datetimePresentation) {
|
|
case 'date-time':
|
|
case 'date':
|
|
case 'month-year':
|
|
case 'month':
|
|
case 'year':
|
|
_this2.selectedButton = 'date';
|
|
break;
|
|
|
|
case 'time-date':
|
|
case 'time':
|
|
_this2.selectedButton = 'time';
|
|
break;
|
|
}
|
|
});
|
|
})();
|
|
}
|
|
|
|
render() {
|
|
const {
|
|
color,
|
|
dateText,
|
|
timeText,
|
|
selectedButton,
|
|
datetimeActive,
|
|
disabled
|
|
} = this;
|
|
const mode = (0,_ionic_global_c74e4951_js__WEBPACK_IMPORTED_MODULE_2__.b)(this);
|
|
return (0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_1__.h)(_index_8e692445_js__WEBPACK_IMPORTED_MODULE_1__.H, {
|
|
class: (0,_theme_7670341c_js__WEBPACK_IMPORTED_MODULE_5__.c)(color, {
|
|
[mode]: true,
|
|
[`${selectedButton}-active`]: datetimeActive,
|
|
['datetime-button-disabled']: disabled
|
|
})
|
|
}, dateText && (0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_1__.h)("button", {
|
|
class: "ion-activatable",
|
|
id: "date-button",
|
|
"aria-expanded": datetimeActive ? 'true' : 'false',
|
|
onClick: this.handleDateClick,
|
|
disabled: disabled,
|
|
part: "native",
|
|
ref: el => this.dateTargetEl = el
|
|
}, (0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_1__.h)("slot", {
|
|
name: "date-target"
|
|
}, dateText), mode === 'md' && (0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_1__.h)("ion-ripple-effect", null)), timeText && (0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_1__.h)("button", {
|
|
class: "ion-activatable",
|
|
id: "time-button",
|
|
"aria-expanded": datetimeActive ? 'true' : 'false',
|
|
onClick: this.handleTimeClick,
|
|
disabled: disabled,
|
|
part: "native",
|
|
ref: el => this.timeTargetEl = el
|
|
}, (0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_1__.h)("slot", {
|
|
name: "time-target"
|
|
}, timeText), mode === 'md' && (0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_1__.h)("ion-ripple-effect", null)));
|
|
}
|
|
|
|
get el() {
|
|
return (0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_1__.i)(this);
|
|
}
|
|
|
|
};
|
|
DatetimeButton.style = {
|
|
ios: iosDatetimeButtonCss,
|
|
md: mdDatetimeButtonCss
|
|
};
|
|
|
|
|
|
/***/ }),
|
|
|
|
/***/ 50320:
|
|
/*!*************************************************************!*\
|
|
!*** ./node_modules/@ionic/core/dist/esm/theme-7670341c.js ***!
|
|
\*************************************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "c": () => (/* binding */ createColorClasses),
|
|
/* harmony export */ "g": () => (/* binding */ getClassMap),
|
|
/* harmony export */ "h": () => (/* binding */ hostContext),
|
|
/* harmony export */ "o": () => (/* binding */ openURL)
|
|
/* harmony export */ });
|
|
/* harmony import */ var _Volumes_case_workspace_work_APPS_KA_APP_WORK_NOW_KA_PASS_V2_2_6_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js */ 71670);
|
|
|
|
|
|
/*!
|
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
*/
|
|
const hostContext = (selector, el) => {
|
|
return el.closest(selector) !== null;
|
|
};
|
|
/**
|
|
* Create the mode and color classes for the component based on the classes passed in
|
|
*/
|
|
|
|
|
|
const createColorClasses = (color, cssClassMap) => {
|
|
return typeof color === 'string' && color.length > 0 ? Object.assign({
|
|
'ion-color': true,
|
|
[`ion-color-${color}`]: true
|
|
}, cssClassMap) : cssClassMap;
|
|
};
|
|
|
|
const getClassList = classes => {
|
|
if (classes !== undefined) {
|
|
const array = Array.isArray(classes) ? classes : classes.split(' ');
|
|
return array.filter(c => c != null).map(c => c.trim()).filter(c => c !== '');
|
|
}
|
|
|
|
return [];
|
|
};
|
|
|
|
const getClassMap = classes => {
|
|
const map = {};
|
|
getClassList(classes).forEach(c => map[c] = true);
|
|
return map;
|
|
};
|
|
|
|
const SCHEME = /^[a-z][a-z0-9+\-.]*:/;
|
|
|
|
const openURL = /*#__PURE__*/function () {
|
|
var _ref = (0,_Volumes_case_workspace_work_APPS_KA_APP_WORK_NOW_KA_PASS_V2_2_6_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function* (url, ev, direction, animation) {
|
|
if (url != null && url[0] !== '#' && !SCHEME.test(url)) {
|
|
const router = document.querySelector('ion-router');
|
|
|
|
if (router) {
|
|
if (ev != null) {
|
|
ev.preventDefault();
|
|
}
|
|
|
|
return router.push(url, direction, animation);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
return function openURL(_x, _x2, _x3, _x4) {
|
|
return _ref.apply(this, arguments);
|
|
};
|
|
}();
|
|
|
|
|
|
|
|
/***/ })
|
|
|
|
}])
|
|
//# sourceMappingURL=7950.js.map
|