UNPKG

fonteva-design-guide

Version:

## Dev, Build and Test

815 lines (727 loc) 35.4 kB
/** * Created by alain on 8/6/2020. */ import { LightningElement, api } from 'lwc'; import LOCALE from '@salesforce/i18n/locale'; import { groupMMDDYYYY, groupDDMMYYYY } from './dateFormatsLocales'; /** labels */ import IsNotALeapYear from '@salesforce/label/c.Pfm_Date_Not_A_Leap_Year'; import InvalidMonthMin from '@salesforce/label/c.Pfm_Date_Invalid_Min_Month'; import InvalidMonthMax from '@salesforce/label/c.Pfm_Date_Invalid_Max_Month'; import InvalidDayInMonth from '@salesforce/label/c.Pfm_Date_Invalid_Day_In_Month'; import InvalidMinDay from '@salesforce/label/c.Pfm_Date_Invalid_Min_Day'; import InvalidMinAllowableYear from '@salesforce/label/c.Pfm_Date_Invalid_Min_Allowable_Year'; import InvalidMaxAllowableYear from '@salesforce/label/c.Pfm_Date_Invalid_Max_Allowable_Year'; import EnterDateInFormat from '@salesforce/label/c.Pfm_Date_Enter_Date_In_Format'; export default class PfmInputDate extends LightningElement { @api delimiter; @api locale; @api minYear; @api maxYear; // deprecated in favor of "value" @api existingDate; @api value; @api required; // exposed for unit tests @api chosenMinYear; @api chosenMaxYear; @api fullDateSnapshot; isValid; chosenDelimiter; chosenLocale; allowableDelimitersRegexStr; localeFormat; shouldAddDelimiters; shouldReposition; lastSelectionStart; dateFormatForCountries; errors; feedbackElement; connectedCallback() { // allowable delimiters used to separate month, day, and year from one another const allowableDelimiters = ['/', '-', '_', '|']; this.chosenDelimiter = allowableDelimiters.indexOf(this.delimiter) > -1 ? this.delimiter : '/'; // get country from passed value, or from the locale set in Salesforce, or default to 'us' if(this.locale) { this.chosenLocale = this.locale.toLowerCase(); } else if(LOCALE) { if(LOCALE.split('-').length == 1) { this.chosenLocale = LOCALE.split('-')[0].toLowerCase(); } else if(LOCALE.split('-').length == 2) { this.chosenLocale = LOCALE.split('-')[1].toLowerCase(); } } else { this.chosenLocale = 'us'; } this.chosenMinYear = this.minYear || 1900; this.chosenMaxYear = this.maxYear || 2100; this.isValid = true; // regular expression portion for the allowable delimiter characters this.allowableDelimitersRegexStr = ''; // the format currently used for the date entered (e.g. MM/DD/YYYY) - defaults to that of US this.localeFormat = 'MM' + this.chosenDelimiter + 'DD' + this.chosenDelimiter + 'YYYY'; // flag to re-insert the delimiters into the raw value of the date this.shouldAddDelimiters = false; // flag to re-position the user's cursor somewhere in the date field this.shouldReposition = false; // last position of the user's cursor within the date field this.lastSelectionStart = 0; // contains different variations of MM/DD/YYYY and what countries utilize them this.dateFormatForCountries = new Map(); // list of all current user errors this.errors = []; // if minimum year is greater than maximum year // or if either is not a number // or if the minimum year is less than 1900 // or if the maximum year is greater than 2100 // then set both to current year if ( this.chosenMinYear > this.chosenMaxYear || isNaN(this.chosenMinYear) || isNaN(this.chosenMaxYear) || this.chosenMinYear < 1900 || this.chosenMaxYear > 2100 ) { this.chosenMinYear = this.chosenMaxYear = new Date().getFullYear(); } // build the regular expression portion for the allowable delimiters for (let i = 0; i < allowableDelimiters.length; i++) { this.allowableDelimitersRegexStr += '\\' + allowableDelimiters[i]; } this.allowableDelimitersRegexStr = '[' + this.allowableDelimitersRegexStr + ']'; // set key/value pairs where key is the MM+delimiter+DD+delimiter+YYYY format and the value is a list of participating countries (via their abbreviations) this.dateFormatForCountries.set('MM' + this.chosenDelimiter + 'DD' + this.chosenDelimiter + 'YYYY', groupMMDDYYYY); this.dateFormatForCountries.set('DD' + this.chosenDelimiter + 'MM' + this.chosenDelimiter + 'YYYY', groupDDMMYYYY); // look through all supported date formats this.dateFormatForCountries.forEach((countries, dateFormat) => { // if a country is found for a given format if (countries.indexOf(this.chosenLocale) > -1) { // set the appropriate placeholder format for the date input field this.localeFormat = dateFormat; } }); } @api updateValue(value) { if(value) { this.value = value; this.updateDateField(); } } updateDateField() { this.feedbackElement = this.template.querySelector('.slds-form-element__help'); // break up the passed in date into day, month, and year const existingYear = this.value ? this.value.split('-')[0] : 0; const existingMonth = this.value ? this.value.split('-')[1] : 0; const existingDay = this.value ? this.value.split('-')[2] : 0; // if default month, day, and year are passed into the component and are numbers if (Number(existingMonth) > 0 && Number(existingDay) > 0 && Number(existingYear) > 0) { // ensure that month, day, and year are adjusted to their proper limits when going beyond them let month, day, year; const adjustMonth = this.adjustMonth(existingMonth); if (adjustMonth.isAdjusted) { this.logToScreen(this.feedbackElement, adjustMonth.feedback + '<br>'); month = this.adjustMonth(existingMonth).month; } else { month = existingMonth; } const adjustDay = this.adjustDay(existingDay, month); if (adjustDay.isAdjusted) { this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + adjustDay.feedback + '<br>'); day = adjustDay.day; } else { day = existingDay; } const adjustYear = this.adjustYear(existingYear); if (adjustYear.isAdjusted) { this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + adjustYear.feedback + '<br>'); year = adjustYear.year; } else { year = existingYear; } // if existing day greater than 28 in february and when not in a leap year if (!this.isLeapYear(year) && +month === 2 && +day > 28) { // adjust the day to the maximum of 28 const new_day = 28; const feedback = IsNotALeapYear.replace('{0}', year).replace('{1}', new_day); // message to user about year not being a leap year this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + feedback); day = new_day; } // add leading 0's to a month or day that is under 10 month = month < 10 && month[0] != '0' ? '0' + month : month; day = day < 10 && day[0] != '0' ? '0' + day : day; // get the raw format of the locale (e.g. MMDDYYYY) const rawLocaleFormat = this.removeDelimiters(this.localeFormat); // populate the date field with the provided date and in the right locale format if (rawLocaleFormat === 'MMDDYYYY') { this.template.querySelector('input').value = month + this.chosenDelimiter + day + this.chosenDelimiter + year; } else if (rawLocaleFormat === 'DDMMYYYY') { this.template.querySelector('input').value = day + this.chosenDelimiter + month + this.chosenDelimiter + year; } // set validity of input field to true this.isValid = true; // remove html error flag this.template.querySelector('.slds-form-element').classList.remove('slds-has-error'); } } renderedCallback() { this.updateDateField(); } handleDateChange() { let self = this; const evt = new CustomEvent('datechange', { detail: self.getDateInSFFormat() }); this.dispatchEvent(evt); } // slated to react to non-input keys such as backspace and delete handleKeyDown(evt) { // get keyboard key pressed const userInput = evt.key; // date field const dateField = evt.target; // only digit representation of entered date including leading 0's let rawDate = this.removeDelimiters(dateField.value); // ignore space bar if (evt.key === ' ' || evt.key === 'Spacebar') { evt.preventDefault(); } // ignore any 1-length character that is not a number if (/[^0-9]/g.test(userInput) && userInput.length === 1) { evt.preventDefault(); } // flag adding delimiters only for entered numbers if (/[0-9]/g.test(userInput)) { this.shouldAddDelimiters = true; } else { this.shouldAddDelimiters = false; } const selectionStart = dateField.selectionStart; // if user hits backspace if (evt.key === 'Backspace') { // if user has not selected any characters if (dateField.selectionEnd - dateField.selectionStart === 0) { // if input field cursor is at the end of the input right after a delimiter if ( (rawDate.length === 2 && dateField.selectionStart === 3) || (rawDate.length === 4 && dateField.selectionStart === 6) ) { // take out one more character (that of the delimiter) on top of whatever is being deleted by the backspace dateField.value = dateField.value.slice(0, -1); } // if user is about to backspace into a delimiter else if (dateField.value[dateField.selectionStart - 1] === this.chosenDelimiter) { // prevent natural order of deletion evt.preventDefault(); // save where the cursor was at last this.lastSelectionStart = dateField.selectionStart; // get all date characters up to right before character to be deleted const beforeRange = dateField.value.substring(0, dateField.selectionStart - 2); // get all date characters after the deleted character to end of input const afterRange = dateField.value.substring(dateField.selectionStart, dateField.value.length); // join the before and after range parts of the date input, sanitize them, and re-introduce the delimiters. then, use it to replace the current content in the date input field dateField.value = this.addDelimiters( this.removeDelimiters(beforeRange + afterRange), this.chosenDelimiter ); // set the cursor to just right before the deleted character dateField.setSelectionRange(this.lastSelectionStart - 2, this.lastSelectionStart - 2); } // if user is about to backspace away from a delimiter on its right else if (dateField.value[dateField.selectionStart] === this.chosenDelimiter) { // prevent natural order of deletion evt.preventDefault(); // get the position of the user's cursor const selectionStart = dateField.selectionStart; // get all date characters up to right before character to be deleted const beforeRange = dateField.value.substring(0, dateField.selectionStart - 1); // get all date characters after the deleted character to end of input const afterRange = dateField.value.substring(dateField.selectionStart, dateField.value.length); // join the before and after range parts of the date input and store as the new date text dateField.value = beforeRange + afterRange; // set the cursor to the position right before the deleted character dateField.setSelectionRange(selectionStart - 1, selectionStart - 1); // flag to add delimiters to the date input this.shouldAddDelimiters = true; } else if (dateField.value.length > 0) { // prevent natural order of deletion evt.preventDefault(); // save where the cursor was at last this.lastSelectionStart = dateField.selectionStart; // get all date characters up to right before character to be deleted const beforeRange = dateField.value.substring(0, dateField.selectionStart - 1); // get all date characters after the deleted character to end of input const afterRange = dateField.value.substring(dateField.selectionStart, dateField.value.length); // join the before and after range parts of the date input, sanitize them, and re-introduce the delimiters. then, use it to replace the current content in the date input field dateField.value = this.addDelimiters( this.removeDelimiters(beforeRange + afterRange), this.chosenDelimiter ); // set the cursor to just right before the deleted character dateField.setSelectionRange(this.lastSelectionStart - 1, this.lastSelectionStart - 1); } // if user selects 1 or more characters } else { this.shouldAddDelimiters = true; this.shouldReposition = true; } // if user selects entire string in date field or if date field is empty if (dateField.selectionEnd - dateField.selectionStart === dateField.value.length) { // do not flag the cursor to be repositioned this.shouldReposition = false; } } // if the user presses the delete key if (evt.key === 'Delete' || evt.key === 'Del') { // if the user's cursor is right behind a delimiter and the user has not selected any characters if ( dateField.value[dateField.selectionStart] === this.chosenDelimiter && dateField.selectionStart === dateField.selectionEnd ) { // prevent natural order of deletion evt.preventDefault(); // save where the cursor was at last this.lastSelectionStart = dateField.selectionStart; // get all date characters up to right before character to be deleted const beforeRange = dateField.value.substring(0, dateField.selectionStart); // get all date characters after the deleted character to end of input const afterRange = dateField.value.substring(dateField.selectionStart + 2, dateField.value.length); // join the before and after range parts of the date input, sanitize them, and re-introduce the delimiters. then, use it to replace the current content in the date input field dateField.value = this.addDelimiters( this.removeDelimiters(beforeRange + afterRange), this.chosenDelimiter ); // set the cursor's position to right after the next delimiter dateField.setSelectionRange(this.lastSelectionStart + 1, this.lastSelectionStart + 1); } else { // if user selected entire string in date field or if date field is empty if ( dateField.selectionEnd - dateField.selectionStart === dateField.value.length || dateField.value.length <= 1 ) { // do not flag the cursor to be repositioned this.shouldReposition = false; } else { // else flag the cursor to be repositioned this.shouldReposition = true; } // set flag to add delimiters back to the date input this.shouldAddDelimiters = true; } } // get the last position of the user's cursor this.lastSelectionStart = dateField.selectionStart; // get the last snapshot of the value in the date field this.fullDateSnapshot = dateField.value; } // handle the digit inputs that made it into the date input field handleInput(evt) { // date field const dateField = evt.target; // get the raw form of the inputted date (e.g. 01012020) let rawDate = this.removeDelimiters(dateField.value); // get the result of the date format validator const formatResult = this.validateDateFormat(rawDate, this.allowableDelimitersRegexStr, this.localeFormat); // if a delimiter was inputted into the beginning of the date field if (dateField.value[0] === this.chosenDelimiter) { // then remove delimiter evt.target.value = dateField.value.replace(this.chosenDelimiter, ''); // set cursor to beginning of date input field dateField.setSelectionRange(0, 0); } if (this.shouldAddDelimiters) { // remove anything that is not a digit from the inputted date rawDate = this.sanitize(rawDate); // add the delimiters and populate the date field accordingly evt.target.value = this.addDelimiters(rawDate, this.chosenDelimiter); // reset addDelimiters flag this.shouldAddDelimiters = false; } // if the cursor needs to be repositioned if (this.shouldReposition) { // set the position to that which was last set in the keydown event dateField.setSelectionRange(this.lastSelectionStart, this.lastSelectionStart); // reset shouldReposition flag this.shouldReposition = false; } // if the digits in the date exceed the limit if (rawDate.length > 8) { // replace the value in the date field to that of the last snapshot evt.target.value = this.fullDateSnapshot; // set the position to that which was last set in the keydown event dateField.setSelectionRange(this.lastSelectionStart, this.lastSelectionStart); // nothing left to do here exit callback return; } // if the date has an invalid format if (formatResult.status === false) { // let the user know this.logToScreen(this.feedbackElement, formatResult.msg); } else { // otherwise clear all messages to the user this.logToScreen(this.feedbackElement, ''); // check for the actual date value constraints and auto-adjust the user's input to conform to date standards this.adjustForDateConstraints(rawDate, evt.target); } // if the date input field is empty if (rawDate.length === 0) { // clear all messages to the user this.logToScreen(this.feedbackElement, ''); } // get the last snapshot of the value in the date field this.fullDateSnapshot = dateField.value; } // handle the last event of pressing a key handleKeyUp(evt) { // reference to date field const dateField = evt.target; // validate the format of the date const result = this.validateDateFormat(dateField.value, this.allowableDelimitersRegexStr, this.localeFormat); // set the current validity of the input field this.isValid = result.status || dateField.value.length === 0 ? true : false; if (this.isValid) { // remove html error flag this.template.querySelector('.slds-form-element').classList.remove('slds-has-error'); } else { // add html error flag this.template.querySelector('.slds-form-element').classList.add('slds-has-error'); } // if the date has been partially entered if (dateField.value.length > 0 && dateField.value.length < 10 && !result.status) { // indicate to user that date is not in proper format this.logToScreen(this.feedbackElement, result.msg); } // if the date field is empty show no messages to the user if (dateField.value.length === 0) { this.logToScreen(this.feedbackElement, ''); // remove html error flag this.template.querySelector('.slds-form-element').classList.remove('slds-has-error'); } this.handleDateChange(); } /** * Logs feedback inside of a given HTML element * @param {HTMLInputElement} el The HTML element that hosts the message * @param {string} msg The message to be passed to the HTML element */ logToScreen(el, msg) { // existing error message const existingErrorInDateField = this.template.querySelector('.slds-form-element__help').innerHTML; // if the message is different from that which already exists in the error feedback element if (msg !== existingErrorInDateField) { el.innerHTML = msg; // add html error flag this.template.querySelector('.slds-form-element').classList.add('slds-has-error'); } } /** * Returns the current validity state of the date input * @return {boolean} denoting whether the date is valid or not */ @api checkValidity() { const input = this.template.querySelector('input'); if (this.required && input && !input.value) { return false; } return this.isValid; } /** * slated to report validity in human readable text * wrapper function implemented to conform to behavior set by pfmInput.js, validate() */ @api reportValidity() { // get the date input value from the field const dateInputVal = this.template.querySelector('input').value; // get the result of validating the date against the proper format const dateValidationResult = this.validateDateFormat( dateInputVal, this.allowableDelimitersRegexStr, this.localeFormat ); return dateValidationResult.msg; } // disable pasting into the date field handlePaste(evt) { evt.preventDefault(); } /** * Returns fully entered date in Salesforce's format * @return {string} date in YYYY-MM-DD format */ @api getDateInSFFormat() { // get current locale (e.g. us, uk, etc.) const localeFormat = this.removeDelimiters(this.localeFormat); // split the day, month, and year into an array const splitDate = this.fullDateSnapshot.split(this.chosenDelimiter); // get the date without any delimiters (e.g. 12202021) const rawDate = this.removeDelimiters(this.fullDateSnapshot); // return null when date has not been fully entered if (rawDate.length < 8) { return null; } let SFDate; // get the correct order of day, month, and year depending on the locale's format if (localeFormat === 'MMDDYYYY') { SFDate = splitDate[2] + '-' + splitDate[0] + '-' + splitDate[1]; } else if (localeFormat === 'DDMMYYYY') { SFDate = splitDate[2] + '-' + splitDate[1] + '-' + splitDate[0]; } return SFDate; } /** * Returns input stripped from delimiters * @param {string} input The date as appears in the date field * @return {string} input stripped of all delimiters */ @api removeDelimiters(input) { if(typeof input !== 'string') { return null; } const allowableDelimitersRegex = new RegExp(this.allowableDelimitersRegexStr, 'g'); return input.replace(allowableDelimitersRegex, ''); } /** * Returns input stripped from non-numbers * @param {string} input The date as appears in the date field * @return {string} input stripped of all non-numbers */ @api sanitize(input) { return typeof input === 'string' ? input.replace(/\D/g, ''):null; } /** * Returns input with delimiters added * @param {string} input The date in its digit raw form * @return {string} input with added delimiters */ @api addDelimiters(input, delimiter) { if(typeof input !== 'string') { return null; } if (input.length < 2) { return input; } else if (input.length >= 2 && input.length < 4) { return input.slice(0, 2) + delimiter + (input[2] || '') + (input[3] || ''); } else if (input.length >= 4 && input.length <= 8) { return ( input.slice(0, 2) + delimiter + input.slice(2, 4) + delimiter + (input[4] || '') + (input[5] || '') + (input[6] || '') + (input[7] || '') ); } } /** * Returns an object with a status and message for the user's inputted date * @param {string} input The date in its non-sanitized form * @param {string} input The date in its non-sanitized form * @param {string} input The date in its non-sanitized form * @return {Object} object with a status and message */ @api validateDateFormat(input, delimiterRegexStr, format) { if(typeof input !== 'string') { return { status:null, feedback:null }; } // build regular expression string that validates date input const dateRegexStr = '\\d{2}' + delimiterRegexStr + '?' + '\\d{2}' + delimiterRegexStr + '?' + '\\d{4}'; // build a regular expression object const dateRegex = new RegExp(dateRegexStr); const feedback = EnterDateInFormat.replace('{0}', format); // if date input conforms to regular expression and length is > 0 if (dateRegex.test(input) === false) { return { status: false, msg: feedback }; } // if date input does not validate else { return { status: true, msg: '' }; } } /** * Runs a full check on the validity of the entered date and re-adjusts user input where needed * @param {string} rawDate The date in its digit form * @param {HTMLInputElement} dateField Reference to the date field in the DOM */ @api adjustForDateConstraints(rawDate, dateField) { let day, month, year; // get year of inputted date as 4 digits year = rawDate.slice(4, 8); // if current format is MM DD YYYY (with delimiters instead of spaces) if (this.localeFormat === 'MM' + this.chosenDelimiter + 'DD' + this.chosenDelimiter + 'YYYY') { // get month as 2 digits month = rawDate.slice(0, 2); // get day as 2 digits day = rawDate.slice(2, 4); } // if current format is DD MM YYYY (with delimiters instead of spaces) else if (this.localeFormat === 'DD' + this.chosenDelimiter + 'MM' + this.chosenDelimiter + 'YYYY') { // get month as 2 digits month = rawDate.slice(2, 4); // get day as 2 digits day = rawDate.slice(0, 2); } const adjustMonth = this.adjustMonth(month); if (adjustMonth.isAdjusted) { this.logToScreen(this.feedbackElement, adjustMonth.feedback + '<br>'); // adjust month if beyond boundaries month = this.adjustMonth(month).month; } const adjustDay = this.adjustDay(day, month); if (adjustDay.isAdjusted) { this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + adjustDay.feedback + '<br>'); day = adjustDay.day; } const adjustYear = this.adjustYear(year); if (adjustYear.isAdjusted) { this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + adjustYear.feedback + '<br>'); // adjust year within the specified min and max range year = adjustYear.year; } // if user entered a day greater than 28 in february and when not in a leap year if (!this.isLeapYear(year) && +month === 2 && +day > 28) { // adjust the day to the maximum of 28 const new_day = 28; const feedback = IsNotALeapYear.replace('{0}', year).replace('{1}', new_day); // message to user about year not being a leap year this.logToScreen(this.feedbackElement, this.feedbackElement.innerHTML + feedback); day = new_day; } // if current format is MM DD YYYY (with delimiters instead of spaces) if (this.localeFormat === 'MM' + this.chosenDelimiter + 'DD' + this.chosenDelimiter + 'YYYY') { // store the newly adjusted day, month, and year with their delimiters dateField.value = month + this.chosenDelimiter + day + this.chosenDelimiter + year; } // if current format is DD MM YYYY (with delimiters instead of spaces) else if (this.localeFormat === 'DD' + this.chosenDelimiter + 'MM' + this.chosenDelimiter + 'YYYY') { // store the newly adjusted day, month, and year with their delimiters dateField.value = day + this.chosenDelimiter + month + this.chosenDelimiter + year; } } /** * Returns an adjusted version of the inputted month * @param {month} The month in its digit form * @return {object} contains status, feedback, and adjusted month */ @api adjustMonth(month) { let adjusted = false; let feedback = ''; // if month is > 12 then adjust to 12 if (+month > 12) { feedback = InvalidMonthMax.replace('{0}', month); month = '12'; adjusted = true; } // if month is 0 then adjust to 1 if (+month === 0) { feedback = InvalidMonthMin.replace('{0}', month); month = '01'; adjusted = true; } return { isAdjusted: adjusted, feedback: feedback, month: month }; } /** * Returns an adjusted version of the inputted day given its month * @param {number} day The day in its digit form * @param {number} month The month in its digit form * @return {object} contains status, feedback, and adjusted day */ @api adjustDay(day, month) { let adjusted = false; let feedback = ''; // convert month to integer month = +month; // if inputted day exceeds limit for its month if (+day > this.getMaxDayInMonth(month)) { const max_day = this.getMaxDayInMonth(month); feedback = InvalidDayInMonth.replace('{0}', day).replace('{1}', month).replace('{2}', max_day); day = max_day.toString(); adjusted = true; } // if inputted day is 0 if (+day === 0) { feedback = InvalidMinDay.replace('{0}', day); day = '01'; adjusted = true; } return { isAdjusted: adjusted, feedback: feedback, day: day }; } /** * Returns an adjusted version of the inputted year * @param {number} year The year in its digit form * @return {object} contains status, feedback, and adjusted year */ @api adjustYear(year) { let adjusted = false; let feedback = ''; // set minimum allowable year const minAllowableYear = +this.chosenMinYear; // set maximum allowable year const maxAllowableYear = +this.chosenMaxYear; // adjust year if inputted year is less than allowable minimum if (+year < minAllowableYear) { feedback = InvalidMinAllowableYear.replace('{0}', year).replace('{1}', minAllowableYear); year = minAllowableYear; adjusted = true; } // adjust year if inputted year is greater than allowable maximum if (+year > maxAllowableYear) { feedback = InvalidMaxAllowableYear.replace('{0}', year).replace('{1}', maxAllowableYear); year = maxAllowableYear; adjusted = true; } return { isAdjusted: adjusted, feedback: feedback, year: year }; } /** * Returns a number specifiying the maximum day a month can have * @param {number} month The month in digits * @return {number} maximum day for a given month */ @api getMaxDayInMonth(month) { if(typeof month !== 'number') { return null; } // pick a leap year (1904) to get the possible maximum day in any given month return new Date(1904, +month, 0).getDate(); } /** * Returns the leap year status of the inputted year * @param {number} year The year in its digit form * @return {boolean} whether the inputted year is a leap year */ @api isLeapYear(year) { if(isNaN(Number(year))) { return null; } return year % 100 === 0 ? year % 400 === 0 : year % 4 === 0; } }